// Pass B: the ice wall. Refracts pass A through a displaced surface using its depth buffer. precision highp float; uniform vec2 u_res; uniform float u_time; uniform vec2 u_mouse; uniform sampler2D u_scene; const float ICE_Z = 1.20; // front face of the ice const vec3 SIG_A = vec3(0.13,0.042,0.020); // absorption in ice (red dies first -> blue) const vec3 SIG_B = vec3(0.16,0.055,0.026); // absorption in the "depth fog" behind float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453); } float noise(vec2 p){ vec2 i=floor(p), f=fract(p); f=f*f*(3.0-2.0*f); return mix(mix(hash(i),hash(i+vec2(1,0)),f.x), mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x), f.y); } float fbm(vec2 p){ float a=0.5, s=0.0; for(int i=0;i<5;i++){ s+=a*noise(p); p*=2.02; a*=0.5; } return s; } // height of the ice surface at (x,y) — almost frozen, drifts very slowly float surf(vec2 p){ float t = u_time*0.04; return 0.055*fbm(p*1.7 + vec2(t,-t)) + 0.022*fbm(p*4.3 - vec2(t*1.7,t)); } vec3 surfaceNormal(vec2 p){ float e = 0.004; float hx = surf(p+vec2(e,0.0)) - surf(p-vec2(e,0.0)); float hy = surf(p+vec2(0.0,e)) - surf(p-vec2(0.0,e)); return normalize(vec3(-hx/(2.0*e), -hy/(2.0*e), 1.0)); } // project a world point with the scene camera (ro=(0,0,3.6), fov via 1.6) vec2 project(vec3 p){ vec3 d = p - vec3(0.0,0.0,8.5); return d.xy/(-d.z)*1.6; } void main(){ vec2 uv = (gl_FragCoord.xy - 0.5*u_res)/u_res.y; vec3 ro = vec3(0.0,0.0,3.6); vec3 rd = normalize(vec3(uv, -1.6)); // --- front surface: plane z=ICE_Z displaced by surf(), two fixed-point steps float t = (ICE_Z - ro.z)/rd.z; for(int i=0;i<2;i++){ vec3 p0 = ro + rd*t; t = (ICE_Z + surf(p0.xy) - ro.z)/rd.z; } vec3 ps = ro + rd*t; vec3 n = surfaceNormal(ps.xy); if(dot(n,-rd) < 0.0) n = -n; float cosi = clamp(dot(n,-rd), 0.0, 1.0); float fres = 0.025 + 0.975*pow(1.0 - cosi, 5.0); vec3 sky = mix(vec3(0.015,0.05,0.10), vec3(0.42,0.68,0.98), smoothstep(-0.35,0.95,n.y)); sky += vec3(0.9,0.97,1.0)*pow(cosi, 60.0); // specular glint // --- refraction with chromatic dispersion down to the back face float backZ = ICE_Z - (0.55 + 0.18*fbm(ps.xy*0.9)); vec3 sceneCol = vec3(0.0); float pathIce = 0.0; for(int c=0;c<3;c++){ float ior = 1.31 + float(c)*0.022; // 1.31 .. 1.354 vec3 d2 = refract(rd, n, 1.0/ior); if(length(d2) < 0.5) d2 = rd; // total internal reflection guard float t2 = (backZ - ps.z)/d2.z; vec3 pe = ps + d2*t2; pathIce = t2; vec2 suv = project(pe); vec2 texUV = (suv*u_res.y + 0.5*u_res)/u_res; vec4 s = texture2D(u_scene, clamp(texUV, vec2(0.001), vec2(0.999))); sceneCol[c] = s[c]; } // --- z-buffer: how much ice/fog the view ray had to cross to the object vec2 baseUV = gl_FragCoord.xy/u_res; float dScene = texture2D(u_scene, baseUV).a * 12.0; // linear depth from pass A float cell = max(0.0, dScene - (t + pathIce)); // ice-to-object gap vec3 absorbA = exp(-SIG_A*pathIce); // inside the slab vec3 absorbB = exp(-SIG_B*cell); // depth haze behind it vec3 iceTint = vec3(0.80,0.94,1.0); // pure, clean blue ice vec3 col = sceneCol * 1.10 * absorbA * absorbB * iceTint; // --- body detail: small trapped bubbles, hair cracks, frozen sheets (static, they live in the slab) vec2 q = ps.xy*2.2; float zone = smoothstep(0.42, 0.62, fbm(q*0.7 + 21.0)); // clear ice vs bubbly ice float bub = noise(q*19.0 + 3.1) * noise(q*17.0 - 1.7); float dots = smoothstep(0.045, 0.075, bub) * mix(0.10, 1.0, zone) * 0.34; float rid = abs(fbm(q*1.9 + 11.0)*2.0 - 1.0); float crack = smoothstep(0.022, 0.0, rid) * 0.45; float sheet = smoothstep(0.62, 0.78, fbm(vec2(q.y*1.3, q.x*0.35)) ); float streak = smoothstep(0.55,0.95, fbm(vec2(q.x*7.0, q.y*0.35) + 5.0)); float sheen = pow(max(0.0, dot(n, normalize(vec3(0.35,0.75,0.55)))), 40.0); col += iceTint * (dots + crack*0.22 + streak*0.05 + sheen*0.5) * (0.25 + 0.75*cosi); col += iceTint * sheet * 0.06 * smoothstep(0.0, 1.2, cell); // --- reflection on top, thicker rim where the slab turns away col = mix(col, sky, fres*0.85); col += iceTint * pow(1.0 - cosi, 2.0) * 0.10; // frosted grazing edge col = pow(clamp(col, 0.0, 1.0), vec3(0.4545)); gl_FragColor = vec4(col, 1.0); }