📜 ⬆️ ⬇️

Kaleidoscope as in childhood


Sometimes the reflection in the mirror is more real than the object itself ...
- Lewis Carroll (Alice in the looking glass)

At a young age I had a funny toy - a kaleidoscope. For hours, I examined the correct patterns, composed of multi-colored fragments of broken glass. Something bewitching was in this meditative contemplation. Now, as a father, I wanted to show my children the beauty of the right constructions of chaos.

Children are now modern, they are not interested in ordinary toys, give them a computer or a tablet. Therefore, I wanted to recreate a digital prototype of a variant of a kaleidoscope, and at the same time practice my computer graphics skills.

I invite you to plunge into the world of reflections with me.

The original idea was to build a complete physical model of a kaleidoscope. This device consists of several mirrors located at an angle to each other laid in a tube. The kaleidoscope from my childhood consisted of three mirrors, and I decided to recreate this design.
')
The most obvious solution for me was to use ray tracing. 3 mirror planes were created at an angle of 120 degrees to each other.



Placing objects beyond the far edge of the mirrors and using multiple re-reflection of the rays (about 20 reflections) we get quite a working kaleidoscope.



To create a routing, a computational shader is used. The output of the image is made in the texture, which is later displayed on the screen. Spheres are used as drawing objects, as simpler shapes. On my video card in realtime rendering mode, I managed to achieve about 20-25 FPS, and this is only with three objects and one light source, which is sad. I wanted a chaotic movement of many different shapes, as well as real-time lighting sources, but this would lead to an even greater slowdown.

After several approaches to optimization, I postponed this model as unpromising.

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));
}


In another approach, I used the periodicity property of the kaleidoscope pattern. Each vertex is always connected with two others, here three vertices are indicated by three colors.
We fill the buffer object with the coordinates of the vertices of equilateral triangles that form a rhombus.



In the figure, the colors are replaced by numbers. Please note: even and odd rows are repeated with a shift of one. We cut off excess elements, displaying only the necessary vertex indices and as a result we get a hexagon that can be easily scaled.



Next, replace the colors with the texture coordinates from the mini-texture template.



An example of filling a texture with rectangles of random colors.

To improve the display, increase the hexagon to the size of the screen, and also add axial rotation.

After a couple of minutes of contemplation from rotation in one direction, it began to stir up. To eliminate this unpleasant effect, the rotation was implemented sequentially in each direction.

Initially, the texture was filled with random elements, but then the idea came up to use color images or photographs. The display element passes through the picture in a random direction in the form of a sliding window, periodically changing direction. So the pattern is more saturated and interesting.

The result is pretty nice images







Video
(Do not sculpt a video, I apologize for the quality)










The shader program code is incredibly simple.

GLSL Shader Code
 //  #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); } 


The children were satisfied, and I hung in meditation for several evenings.

Demo (EXE for Windows)

Source: https://habr.com/ru/post/461351/


All Articles