diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index baf7df6..1937e2b 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -73,6 +73,7 @@ fn main() { let ctrl_x = create_signal(0.0); let ctrl_y = create_signal(0.0); let opacity = create_signal(0.6); + let layer_threshold = create_signal(0.0); let display = create_node_ref(); on_mount(move || { @@ -116,11 +117,14 @@ fn main() { // controls uniform vec2 ctrl; uniform float opacity; + uniform int layer_threshold; // light and camera const float focal_slope = 0.3; const vec3 light_dir = normalize(vec3(2., 2., 1.)); + // --- inversive geometry --- + struct vecInv { vec3 sp; vec2 lt; @@ -136,7 +140,26 @@ fn main() { ); } - vec4 shade_sphere(vecInv v, vec3 pt) { + // --- shading --- + + struct taggedFrag { + vec4 color; + float depth; + }; + + taggedFrag[2] sort(taggedFrag a, taggedFrag b) { + taggedFrag[2] result; + if (a.depth < b.depth) { + result[0] = a; + result[1] = b; + } else { + result[0] = b; + result[1] = a; + } + return result; + } + + taggedFrag sphere_shading(vecInv v, vec3 pt, vec3 base_color) { // the expression for normal needs to be checked. it's // supposed to give the negative gradient of the lorentz // product between the impact point vector and the sphere @@ -147,20 +170,13 @@ fn main() { vec3 color; float incidence = dot(normal_front, light_dir); - if (incidence < 0.) { - color = mix(vec3(0.2, 0.0, 0.4), vec3(0.1, 0.0, 0.2), -incidence); - } else { - color = mix(vec3(0.4, 0.0, 0.2), vec3(1.0, 0.8, 1.0), incidence); - } - return vec4(color, opacity); + float illum = mix(0.4, 1.0, max(incidence, 0.0)); + return taggedFrag(vec4(illum * base_color, opacity), -pt.z); } - void main() { - vec2 scr = (2.*gl_FragCoord.xy - resolution) / shortdim; - vec3 dir = vec3(focal_slope * scr, -1.); - - vecInv v = sphere(vec3(ctrl, -5.), 1.); - + // --- ray-casting --- + + vec2 sphere_cast(vecInv v, vec3 dir) { float a = -v.lt.s * dot(dir, dir); float b = dot(v.sp, dir); float c = -v.lt.t; @@ -168,18 +184,57 @@ fn main() { float scale = -b/(2.*a); float adjust = 4.*a*c/(b*b); - vec3 color = vec3(0.); if (adjust < 1.) { float offset = sqrt(1. - adjust); - float u_front = scale * (1. - offset); - float u_back = scale * (1. + offset); - if (u_back > 0.) { - vec4 sphere_color = shade_sphere(v, u_back * dir); - color = mix(color, sphere_color.rgb, sphere_color.a); - } - if (u_front > 0.) { - vec4 sphere_color = shade_sphere(v, u_front * dir); - color = mix(color, sphere_color.rgb, sphere_color.a); + return vec2( + scale * (1. - offset), + scale * (1. + offset) + ); + } else { + // these parameters describe points behind the camera, + // so the corresponding fragments won't be drawn + return vec2(-1., -1.); + } + } + + void main() { + vec2 scr = (2.*gl_FragCoord.xy - resolution) / shortdim; + vec3 dir = vec3(focal_slope * scr, -1.); + + // initialize two spheres + vecInv v0 = sphere(vec3(0.5, 0.5, -5. + ctrl.x), 1.); + vecInv v1 = sphere(vec3(-0.5, -0.5, -5. + ctrl.y), 1.); + vec3 color0 = vec3(1.0, 0.5, 0.0); + vec3 color1 = vec3(0.0, 0.5, 1.0); + + // cast rays through the spheres + vec2 u0 = sphere_cast(v0, dir); + vec2 u1 = sphere_cast(v1, dir); + + // shade and depth-sort the impact points + taggedFrag front_hits[2] = sort( + sphere_shading(v0, u0[0] * dir, color0), + sphere_shading(v1, u1[0] * dir, color1) + ); + taggedFrag back_hits[2] = sort( + sphere_shading(v0, u0[1] * dir, color0), + sphere_shading(v1, u1[1] * dir, color1) + ); + taggedFrag middle_frags[2] = sort(front_hits[1], back_hits[0]); + + // finish depth sorting + taggedFrag frags_by_depth[4]; + frags_by_depth[0] = front_hits[0]; + frags_by_depth[1] = middle_frags[0]; + frags_by_depth[2] = middle_frags[1]; + frags_by_depth[3] = back_hits[1]; + + // composite the sphere fragments + vec3 color = vec3(0.); + for (int i = 3; i >= layer_threshold; --i) { + if (frags_by_depth[i].depth > 0.) { + vec4 frag_color = frags_by_depth[i].color; + color = mix(color, frag_color.rgb, frag_color.a); } } outColor = vec4(color, 1.); @@ -208,6 +263,7 @@ fn main() { let shortdim_loc = ctx.get_uniform_location(&program, "shortdim"); let ctrl_loc = ctx.get_uniform_location(&program, "ctrl"); let opacity_loc = ctx.get_uniform_location(&program, "opacity"); + let layer_threshold_loc = ctx.get_uniform_location(&program, "layer_threshold"); // create a vertex array and bind it to the graphics context let vertex_array = ctx.create_vertex_array().unwrap(); @@ -238,6 +294,7 @@ fn main() { // pass the control parameters ctx.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32); + ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32); // clear the screen and draw the scene ctx.clear_color(0.0, 0.0, 0.0, 1.0); @@ -253,22 +310,28 @@ fn main() { type="range", min=-1.0, max=1.0, - step=0.01, + step=0.001, bind:valueAsNumber=ctrl_x ) input( type="range", min=-1.0, max=1.0, - step=0.01, + step=0.001, bind:valueAsNumber=ctrl_y ) input( type="range", max=1.0, - step=0.01, + step=0.001, bind:valueAsNumber=opacity ) + input( + type="range", + max=3.0, + step=1.0, + bind:valueAsNumber=layer_threshold + ) } } });