Kaleidoskop wie in der Kindheit


Manchmal ist die Reflexion im Spiegel realer als das Objekt selbst ...
- Lewis Carroll (Alice im Spiegel)

In jungen Jahren hatte ich ein lustiges Spielzeug - ein Kaleidoskop. Stundenlang untersuchte ich die richtigen Muster aus mehrfarbigen Glasscherben. In dieser meditativen Betrachtung war etwas Bezauberndes. Jetzt wollte ich als Vater meinen Kindern die Schönheit der richtigen Konstruktionen des Chaos zeigen.

Die Kinder sind jetzt modern, gewöhnliches Spielzeug ist für sie von geringem Interesse, geben Sie ihnen einen Computer oder ein Tablet. Deshalb wollte ich einen digitalen Prototyp einer Kaleidoskopvariante nachbauen und gleichzeitig meine Fähigkeiten im Umgang mit Computergrafiken üben.

Ich lade Sie ein, mit mir in die Welt der Reflexionen einzutauchen.

Die ursprüngliche Idee war, ein vollständiges physikalisches Modell eines Kaleidoskops zu erstellen. Diese Vorrichtung besteht aus mehreren Spiegeln, die in einem Winkel zueinander angeordnet sind und in einer Röhre angeordnet sind. Das Kaleidoskop aus meiner Kindheit bestand aus drei Spiegeln, und ich beschloss, dieses Design nachzubauen.

Die naheliegendste Lösung für mich war die Verwendung von Raytracing. 3 Spiegelebenen wurden in einem Winkel von 120 Grad zueinander erzeugt.



Wenn wir Objekte jenseits der äußersten Kante der Spiegel platzieren und die Strahlen mehrfach reflektieren (ca. 20 Reflexionen), erhalten wir ein ziemlich funktionierendes Kaleidoskop.



Zum Erstellen eines Routings wird ein Computational Shader verwendet. Das Bild wird in einer Textur ausgegeben, die später auf dem Bildschirm angezeigt wird. Kugeln werden als Zeichenobjekte als einfachere Formen verwendet. Auf meiner Grafikkarte im Echtzeit-Rendering-Modus habe ich ungefähr 20-25 FPS erreicht, und dies ist nur mit drei Objekten und einer Lichtquelle möglich, was traurig ist. Ich wollte eine chaotische Bewegung in vielen verschiedenen Formen sowie Echtzeit-Lichtquellen, aber dies würde zu einer noch stärkeren Verlangsamung führen.

Nach mehreren Optimierungsansätzen habe ich dieses Modell als vielversprechend verschoben.

Berechnen des Shader-Codes 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 einem anderen Ansatz habe ich die Periodizitätseigenschaft des Kaleidoskopmusters verwendet. Jeder Scheitelpunkt ist immer mit zwei anderen verbunden, hier werden drei Scheitelpunkte durch drei Farben angezeigt.
Wir füllen das Pufferobjekt mit den Koordinaten der Eckpunkte gleichseitiger Dreiecke, die eine Raute bilden.



In der Abbildung werden Farben durch Zahlen ersetzt. Bitte beachten Sie: Gerade und ungerade Zeilen werden mit einer Verschiebung von eins wiederholt. Wir schneiden unnötige Elemente ab und zeigen nur die erforderlichen Scheitelpunktindizes an. Als Ergebnis erhalten wir ein Sechseck, das leicht skaliert werden kann.



Ersetzen Sie anschließend die Farben durch die Texturkoordinaten aus der Mini-Texturvorlage.



Ein Beispiel für das Füllen einer Textur mit Rechtecken mit zufälligen Farben.

Um die Anzeige zu verbessern, vergrößern Sie das Sechseck auf die Größe des Bildschirms und fügen Sie eine axiale Drehung hinzu.

Nach ein paar Minuten des Nachdenkens von der Drehung in eine Richtung begann es sich zu rühren. Um diesen unangenehmen Effekt zu beseitigen, wurde die Drehung nacheinander in jede Richtung durchgeführt.

Anfangs war die Textur mit zufälligen Elementen gefüllt, aber dann kam die Idee, Farbbilder oder Fotografien zu verwenden. Das Anzeigeelement durchläuft das Bild in zufälliger Richtung in Form eines Schiebefensters, wobei sich die Richtung periodisch ändert. Das Muster ist also gesättigter und interessanter.

Das Ergebnis sind ziemlich schöne Bilder







Video
(Ich weiß nicht, wie man ein Video modelliert, ich entschuldige mich für die Qualität)










Der Shader-Code ist unglaublich einfach.

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


Die Kinder waren zufrieden und ich meditierte mehrere Abende.

Demo (EXE für Windows)

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


All Articles