Caleidoscopio como en la infancia


A veces, el reflejo en el espejo es más real que el objeto mismo ...
- Lewis Carroll (Alicia en el espejo)

A temprana edad tuve un juguete divertido: un caleidoscopio. Durante horas, examiné los patrones correctos formados por fragmentos multicolores de vidrios rotos. Algo hechizante estaba en esta contemplación meditativa. Ahora, como padre, quería mostrarles a mis hijos la belleza de las construcciones correctas del caos.

Los niños ahora son modernos, los juguetes ordinarios les interesan poco, les dan una computadora o una tableta. Por lo tanto, quería recrear un prototipo digital de una variante de un caleidoscopio y, al mismo tiempo, practicar mis habilidades de gráficos por computadora.

Te invito a sumergirte conmigo en el mundo de los reflejos.

La idea original era construir un modelo físico completo de un caleidoscopio. Este dispositivo consta de varios espejos ubicados en ángulo entre sí colocados en un tubo. El caleidoscopio de mi infancia consistía en tres espejos, y decidí recrear este diseño.

La solución más obvia para mí fue utilizar el trazado de rayos. Se crearon 3 planos espejo en un ángulo de 120 grados entre sí.



Colocando objetos más allá del borde más alejado de los espejos y usando la re-reflexión múltiple de los rayos (aproximadamente 20 reflexiones) obtenemos un caleidoscopio bastante funcional.



Para crear una ruta, se usa un sombreador computacional. La imagen se emite en una textura, que luego se muestra en la pantalla. Las esferas se utilizan como objetos de dibujo como formas más simples. En mi tarjeta de video en modo de representación en tiempo real, logré alcanzar unos 20-25 FPS, y esto es solo con tres objetos y una fuente de luz, lo cual es triste. Quería un movimiento caótico de muchas formas diferentes, así como fuentes de iluminación en tiempo real, pero esto conduciría a una desaceleración aún mayor.

Después de varios enfoques para la optimización, pospuse este modelo como poco prometedor.

Cálculo del código de sombreador 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));
}


En otro enfoque, utilicé la propiedad de periodicidad del patrón de caleidoscopio. Cada vértice siempre está conectado con otros dos, aquí tres vértices están indicados por tres colores.
Rellenamos el objeto tampón con las coordenadas de los vértices de los triángulos equiláteros que forman un rombo.



En la figura, los colores se reemplazan por números. Tenga en cuenta: las filas pares e impares se repiten con un cambio de uno. Cortamos elementos innecesarios, mostrando solo los índices de vértices necesarios y, como resultado, obtenemos un hexágono que se puede escalar fácilmente.



Luego, reemplace los colores con las coordenadas de textura de la plantilla de mini-textura.



Un ejemplo de relleno de una textura con rectángulos de colores aleatorios.

Para mejorar la visualización, aumente el hexágono al tamaño de la pantalla y también agregue rotación axial.

Después de un par de minutos de contemplación desde la rotación en una dirección, comenzó a agitarse. Para eliminar este efecto desagradable, la rotación se implementó secuencialmente en cada dirección.

Inicialmente, la textura se rellenaba con elementos aleatorios, pero luego surgió la idea de utilizar imágenes en color o fotografías. El elemento de visualización pasa a través de la imagen en una dirección aleatoria en forma de ventana deslizante, cambiando periódicamente de dirección. Entonces el patrón es más saturado e interesante.

El resultado son imágenes bastante bonitas







Video
(No sé cómo esculpir un video, pido disculpas por la calidad)










El código del sombreador es increíblemente simple.

Código de sombreador 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); } 


Los niños estaban satisfechos, y yo estuve meditando varias noches.

Demo (EXE para Windows)

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


All Articles