小时候的万花筒


有时镜子中的反射比物体本身更真实...
-刘易斯·卡罗尔(爱丽丝窥镜)

小时候,我有一个有趣的玩具-万花筒。 几个小时以来,我检查了由碎玻璃的多色碎片组成的正确图案。 在这种沉思中,令人着迷的是。 现在,作为父亲,我想向我的孩子们展示正确的混沌结构之美。

现在的孩子们已经很现代了,普通的玩具对他们几乎没有兴趣,给他们一台计算机或平板电脑。 因此,我想重新创建万花筒变体的​​数字原型,并同时练习我处理计算机图形学的技能。

我邀请您与我一起进入反射的世界。

最初的想法是建立万花筒的完整物理模型。 该装置由放置在管中的多个彼此成一定角度的镜子组成。 我小时候的万花筒由三个镜子组成,所以我决定重新设计。

对我来说,最明显的解决方案是使用光线跟踪。 创建了3个镜面,彼此成120度角。



将物体放置在镜子的远边缘之外,并使用光线的多次重反射(大约20次反射),我们得到了一个非常有效的万花筒镜。



要创建路由,请使用计算着色器。 图像以纹理输出,然后显示在屏幕上。 球体用作更简单形状的图形对象。 在实时渲染模式下的视频卡上,我设法达到了约20-25 FPS,而这只有三个对象和一个光源,这令人遗憾。 我想要一种具有许多不同形状的混乱运动以及实时光源,但是这会导致更大的减速。

经过几种优化方法后,我将此模型推迟为没有希望。

计算着色器代码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));
}


在另一种方法中,我使用了万花筒图案的周期性属性。 每个顶点始终彼此连接,此处三个顶点由三种颜色表示。
我们用形成菱形的等边三角形的顶点坐标填充缓冲区对象。



在图中,颜色由数字代替。 请注意:偶数行和奇数行以1的移位重复。 我们剪掉了不必要的元素,只显示必要的顶点索引,结果得到了可以轻松缩放的六边形。



接下来,将颜色替换为迷你纹理模板中的纹理坐标。



用随机颜色的矩形填充纹理的示例。

要改善显示效果,请增加六边形以达到屏幕尺寸,并增加轴向旋转。

从一个方向旋转了几分钟,它开始动起来。 为了消除这种不愉快的效果,在每个方向上依次进行旋转。

最初,纹理中填充了随机元素,但后来想到使用彩色图像或照片。 显示元件以滑动窗口的形式在随机方向上穿过图像,周期性地改变方向。 因此模式更加饱和和有趣。

结果是非常漂亮的图像







录影带
(我不知道如何雕刻视频,对此质量我深表歉意)










着色器代码非常简单。

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


孩子们很满意,我沉思了几个晚上。

演示 (适用于Windows的EXE)

Source: https://habr.com/ru/post/zh-CN461351/


All Articles