Ice slab with layered plasma, two-pass refraction
Pass 1 renders eight plasma sheets into an atlas with linear depth in alpha; pass 2 marches the refracted ray to each sheet and absorbs it by real path length, so near and far layers separate under pointer parallax. Plasma cycle repeats exactly every 30 s; - / = change speed.
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Two-pass ice pipeline: scene -> texture(+depth in alpha) -> refraction -> screen.
|
||||
Writes ice.html. Open ice.html#shot&t=2 for a single still frame."""
|
||||
import json, os
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
import sys
|
||||
a = sys.argv[1:]
|
||||
scene_f, ice_f, out_f = (a + ["4_ice_scene.frag", "5_ice_refract.frag", "ice.html"])[:3]
|
||||
scene = open(os.path.join(here, scene_f)).read()
|
||||
ice = open(os.path.join(here, ice_f)).read()
|
||||
|
||||
HTML = """<!doctype html><html><head><meta charset=utf-8><title>ice</title>
|
||||
<style>html,body{margin:0;height:100%;background:#000;overflow:hidden}
|
||||
canvas{display:block;width:100vw;height:100vh}
|
||||
#osd{position:fixed;left:14px;bottom:12px;font:13px ui-monospace,monospace;color:#9cf;opacity:.7;pointer-events:none}</style>
|
||||
</head><body><canvas id=c></canvas><div id=osd></div><script>
|
||||
const FRAG_A = atob('__SCENE_B64__');
|
||||
const FRAG_B = atob('__ICE_B64__');
|
||||
const VS = 'attribute vec2 p;void main(){gl_Position=vec4(p,0,1);}';
|
||||
const SHOT = location.hash.indexOf('shot') >= 0;
|
||||
const T = parseFloat((location.hash.match(/t=([\\d.]+)/)||[0,0])[1]) || 0;
|
||||
const cv = document.getElementById('c'), osd = document.getElementById('osd');
|
||||
const gl = cv.getContext('webgl', {antialias:false, preserveDrawingBuffer:SHOT});
|
||||
|
||||
function prog(vs, fs){
|
||||
const sh=(t,s)=>{const o=gl.createShader(t);gl.shaderSource(o,s);gl.compileShader(o);
|
||||
if(!gl.getShaderParameter(o,gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(o)); return o;};
|
||||
const p=gl.createProgram();
|
||||
gl.attachShader(p,sh(gl.VERTEX_SHADER,vs)); gl.attachShader(p,sh(gl.FRAGMENT_SHADER,fs));
|
||||
gl.linkProgram(p); if(!gl.getProgramParameter(p,gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
let PA, PB;
|
||||
try { PA = prog(VS, FRAG_A); PB = prog(VS, FRAG_B); }
|
||||
catch(e){ osd.textContent = 'shader error: ' + e.message; document.title = 'ERR'; }
|
||||
|
||||
const buf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW);
|
||||
|
||||
// offscreen target: colour + depth packed into alpha
|
||||
let fbo, tex;
|
||||
function resize(){
|
||||
const d = SHOT ? 1 : (devicePixelRatio||1);
|
||||
const W = SHOT ? parseInt(location.hash.match(/w=(\\d+)/)?.[1]||700) : Math.round(innerWidth*d);
|
||||
const H = SHOT ? W : Math.round(innerHeight*d);
|
||||
cv.width=W; cv.height=H; cv.style.width = SHOT ? W+'px' : '100vw'; cv.style.height = SHOT ? H+'px' : '100vh';
|
||||
tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,W,H,0,gl.RGBA,gl.UNSIGNED_BYTE,null);
|
||||
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);
|
||||
fbo = gl.createFramebuffer();
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,tex,0);
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
}
|
||||
resize();
|
||||
|
||||
let mx=0.5, my=0.5, speed=1.0;
|
||||
if(!SHOT){
|
||||
addEventListener('keydown', e=>{
|
||||
if(e.key==='-'||e.key==='_') speed = Math.max(0.125, speed/2);
|
||||
if(e.key==='='||e.key==='+') speed = Math.min(4.0, speed*2);
|
||||
});
|
||||
addEventListener('resize', resize);
|
||||
addEventListener('mousemove', e=>{ mx=e.clientX/innerWidth; my=1-e.clientY/innerHeight; });
|
||||
}
|
||||
|
||||
function draw(p, time){
|
||||
gl.useProgram(p);
|
||||
const l = gl.getAttribLocation(p,'p');
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.enableVertexAttribArray(l); gl.vertexAttribPointer(l,2,gl.FLOAT,false,0,0);
|
||||
const u = n => gl.getUniformLocation(p,n);
|
||||
if(u('u_res')) gl.uniform2f(u('u_res'), cv.width, cv.height);
|
||||
if(u('u_time')) gl.uniform1f(u('u_time'), time);
|
||||
if(u('u_mouse'))gl.uniform2f(u('u_mouse'), mx, my);
|
||||
if(u('u_scene')){ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, tex); gl.uniform1i(u('u_scene'), 0); }
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP,0,4);
|
||||
}
|
||||
|
||||
const T0 = performance.now();
|
||||
function frame(now){
|
||||
const time = SHOT ? T : (now - T0)*0.001*speed;
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
||||
gl.viewport(0,0,cv.width,cv.height);
|
||||
draw(PA, time);
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
gl.viewport(0,0,cv.width,cv.height);
|
||||
draw(PB, time);
|
||||
osd.textContent = 'ice · speed x' + speed + ' (- / =) · move mouse';
|
||||
if(SHOT) blit(); else requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
function blit(){ // headless firefox won't composite a WebGL canvas, so hand pixels to a 2D canvas
|
||||
const W=cv.width, H=cv.height, px=new Uint8Array(W*H*4);
|
||||
gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,px);
|
||||
const out=document.createElement('canvas'); out.width=W; out.height=H; out.style.display='block';
|
||||
const x=out.getContext('2d'), img=x.createImageData(W,H);
|
||||
for(let y=0;y<H;y++) img.data.set(px.subarray((H-1-y)*W*4,(H-y)*W*4), y*W*4);
|
||||
x.putImageData(img,0,0);
|
||||
cv.remove(); document.body.appendChild(out);
|
||||
}
|
||||
|
||||
if(PA && PB) SHOT ? frame(0) : requestAnimationFrame(frame);
|
||||
</script></body></html>
|
||||
"""
|
||||
|
||||
out = (HTML.replace("__SCENE_B64__", __import__("base64").b64encode(scene.encode()).decode())
|
||||
.replace("__ICE_B64__", __import__("base64").b64encode(ice.encode()).decode()))
|
||||
open(os.path.join(here, out_f), "w").write(out)
|
||||
print(out_f, "written")
|
||||
Reference in New Issue
Block a user