
Parfois, le reflet dans le miroir est plus réel que l'objet lui-même ...
- Lewis Carroll (Alice au miroir)
À un jeune âge, j'avais un jouet amusant - un kaléidoscope. Pendant des heures, j'ai examiné les motifs corrects constitués de fragments multicolores de verre brisé. Quelque chose d'ensorcelant était dans cette contemplation méditative. Maintenant, en tant que père, je voulais montrer à mes enfants la beauté des bonnes constructions du chaos.
Les enfants sont désormais modernes, les jouets ordinaires ne les intéressent plus, donnez-leur un ordinateur ou une tablette. J'ai donc voulu recréer un prototype numérique d'une variante d'un kaléidoscope, et en même temps pratiquer mes compétences en infographie.
Je vous invite à plonger avec moi dans le monde des reflets.
L'idée originale était de construire un modèle physique complet d'un kaléidoscope. Ce dispositif est constitué de plusieurs miroirs disposés à angle l'un par rapport à l'autre disposés dans un tube. Le kaléidoscope de mon enfance était composé de trois miroirs, et j'ai décidé de recréer ce design.
La solution la plus évidente pour moi était d'utiliser le lancer de rayons. 3 plans de miroir ont été créés à un angle de 120 degrés les uns par rapport aux autres.

En plaçant des objets au-delà du bord éloigné des miroirs et en utilisant une ré-réflexion multiple des rayons (environ 20 réflexions), nous obtenons un kaléidoscope tout à fait fonctionnel.

Pour créer un routage, un shader de calcul est utilisé. L'image est sortie dans une texture, qui est ensuite affichée à l'écran. Les sphères sont utilisées comme objets de dessin sous forme de formes plus simples. Sur ma carte vidéo en mode de rendu en temps réel, j'ai réussi à atteindre environ 20-25 FPS, et ce n'est qu'avec trois objets et une source lumineuse, ce qui est triste. Je voulais déplacer au hasard de nombreuses formes différentes, ainsi que des sources d'éclairage en temps réel, mais cela conduirait à un ralentissement encore plus important.
Après plusieurs approches d'optimisation, j'ai reporté ce modèle comme peu prometteur.
Computing Shader Code GLSL#version 430 core
layout( local_size_x = 32, local_size_y = 32 ) in;
layout(binding = 0, rgba8) uniform image2D IMG;
layout(binding = 1, std430) buffer InSphere {vec4 Shape_obj[];};
layout(binding = 2, std430) buffer InSphere_color {vec4 Sphere_color[];};
uniform vec2 u_InvScreenSize;
uniform float u_ScreenRatio;
uniform vec3 u_LightPosition;
uniform vec3 u_CameraPosition;
//
const vec3 ray00 = vec3(-1*u_ScreenRatio,-1, -1.2);
const vec3 ray01 = vec3(-1*u_ScreenRatio,+1, -1.2);
const vec3 ray10 = vec3(+1*u_ScreenRatio,-1, -1.2);
const vec3 ray11 = vec3(+1*u_ScreenRatio,+1, -1.2);
const ivec2 size = imageSize(IMG);
const mat3 mat_rotate = mat3(-0.5, -0.86602540378443864676372317075294, 0, 0.86602540378443864676372317075294, -0.5, 0, 0, 0, 1);
struct plane {
vec3 v_plane;
vec3 n_plane;
vec3 p_plane;
};
//
plane m[3];
int last_plane;
//----------------------------------------------------------
float ray_intersect_sphere(vec3 orig, vec3 dir, vec4 Shape_obj) {
vec3 l = Shape_obj.xyz - orig;
float tca = dot(l,dir);
float d2 = dot(l,l) - tca * tca;
if (d2 > Shape_obj.w * Shape_obj.w) {return 0;}
float thc = sqrt(Shape_obj.w * Shape_obj.w - d2);
float t0 = tca - thc;
float t1 = tca + thc;
if (t0 < 0) {t0 = t1;}
if (t0 < 0) {return 0;}
return t0;
}
//---------------------------------------------------------
'float ray_intersect_plane(in vec3 orig, in vec3 dir, inout plane p) {
vec3 tested_direction = p.v_plane - orig;
float k = dot(tested_direction, p.v_plane) / dot(dir, p.v_plane);
if (k>=0) {
vec3 p0 = orig + dir * k;
// z
if ((p0.z>-80)&&(p0.z<3)) {
p.p_plane = p0;
return length(p0-orig);
}
}
return 1000000;
}'+
//---------------------------------------------------------
bool all_obj(inout vec3 loc_eye, inout vec3 dir, inout vec3 c) {
float min_len = 1000000;
uint near_id = 0;
float len;
float min_len2 = 1000000;
int near_id2 = -1;
for (int i=0; i<3; i++) {
if (i!=last_plane) {
len = ray_intersect_plane(loc_eye, dir, m[i]);
if (len<min_len2) {
min_len2 = len;
near_id2 = i;
}
}
}
//
if (near_id2>=0) {
loc_eye = m[near_id2].p_plane;
dir = reflect(dir, m[near_id2].n_plane);
last_plane =near_id2;
return true;
}
for (uint i=0; i<Shape_obj.length(); i++) {
len = ray_intersect_sphere(loc_eye, dir, Shape_obj[i]);
if ((len>0)&&(len<min_len)) {
min_len = len;
near_id = i;
}
}
//
if (min_len>=1000000) {return false;}
vec3 hit = loc_eye + dir * min_len;
vec3 Normal = normalize(hit - Shape_obj[near_id].xyz);
vec3 to_light = u_LightPosition - hit;
float to_light_len = length(to_light);
vec3 light_dir = normalize(to_light);
float diffuse_light = max(dot(light_dir, Normal), 0.0);
c = min(c + Sphere_color[near_id].xyz * (diffuse_light*0.8+0.2),1);
return false;
}
//---------------------------------------------------------
void main(void) {
if (gl_GlobalInvocationID.x >= size.x || gl_GlobalInvocationID.y >= size.y) return;
const vec2 pos = gl_GlobalInvocationID.xy * u_InvScreenSize.xy;
vec3 dir = normalize(mix(mix(ray00, ray01, pos.y), mix(ray10, ray11, pos.y), pos.x));
vec3 c = vec3(0, 0, 0);
//
vec3 eye = vec3(u_CameraPosition);
//
m[0].v_plane = vec3(0,-5,0);
m[0].n_plane = vec3(0,1,0);
m[1].v_plane = mat_rotate * m[0].v_plane;
m[1].n_plane = mat_rotate * m[0].n_plane;
m[2].v_plane = mat_rotate * m[1].v_plane;
m[2].n_plane = mat_rotate * m[1].n_plane;
//
for (int i=0; i<20; i++) {
if (!all_obj(eye, dir, c)) {break;}
}
//
imageStore(IMG, ivec2(gl_GlobalInvocationID.xy), vec4(c,1));
}
Dans une autre approche, j'ai utilisé la propriété de périodicité du motif de kaléidoscope. Chaque sommet est toujours connecté à deux autres, ici trois sommets sont indiqués par trois couleurs.
Nous remplissons l'objet tampon avec les coordonnées des sommets des triangles équilatéraux formant un losange.

Dans la figure, les couleurs sont remplacées par des nombres. Remarque: les rangées paires et impaires sont répétées avec un décalage d'un. Nous supprimons les éléments inutiles, affichant uniquement les indices de sommet nécessaires et, par conséquent, nous obtenons un hexagone qui peut être facilement mis à l'échelle.

Ensuite, remplacez les couleurs par les coordonnées de texture du modèle de mini-texture.

Un exemple de remplissage d'une texture avec des rectangles de couleurs aléatoires.
Pour améliorer l'affichage, augmentez l'hexagone à la taille de l'écran et ajoutez également une rotation axiale.
Après quelques minutes de contemplation de la rotation dans une direction, il a commencé à s'agiter. Pour éliminer cet effet désagréable, la rotation a été mise en œuvre séquentiellement dans chaque direction.
Initialement, la texture était remplie d'éléments aléatoires, mais l'idée est venue d'utiliser des images ou des photographies en couleur. L'élément d'affichage traverse l'image dans une direction aléatoire sous la forme d'une fenêtre coulissante, changeant périodiquement de direction. Le motif est donc plus saturé et intéressant.
Le résultat est de jolies images



Vidéo(Je ne sais pas sculpter une vidéo, je m'excuse pour la qualité)
Le code du shader est incroyablement simple.
Code de shader GLSL // #version 330 core layout (location = 0) in vec4 a_Position; uniform mat4 u_MVP; out vec4 v_Color; out vec2 v_TexCoords; void main() { v_TexCoords = a_Position.zw; gl_Position = u_MVP * vec4(a_Position.xy, 0, 1); } // #version 330 core precision mediump float; varying vec2 v_TexCoords; uniform sampler2D u_Texture; void main(){ gl_FragColor = texture(u_Texture, v_TexCoords); }
Les enfants étaient satisfaits et j'ai passé plusieurs soirées en méditation.
→
Démo (EXE pour Windows)