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:
emil28092005
2026-10-30 19:56:16 +03:00
commit 27d238cbe6
20 changed files with 1039 additions and 0 deletions
Executable
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""Render a GLSL fragment shader to PNG via headless firefox (readPixels -> 2D canvas).
usage: ./render.py shader.frag out.png [time] [width] [height] [mouseX] [mouseY]
"""
import base64, os, subprocess, sys, tempfile
HTML = """<!doctype html><html><body style="margin:0;background:#000">
<canvas id=c width=%(w)d height=%(h)d></canvas><script>
const W=%(w)d,H=%(h)d,FRAG=%(frag)s,VS='attribute vec2 p;void main(){gl_Position=vec4(p,0,1);}';
const c=document.getElementById('c'),gl=c.getContext('webgl',{antialias:false,preserveDrawingBuffer:true});
function sh(t,s){const o=gl.createShader(t);gl.shaderSource(o,s);gl.compileShader(o);
if(!gl.getShaderParameter(o,gl.COMPILE_STATUS)){document.body.insertAdjacentHTML('beforeend','<pre style="color:red;font:14px monospace">'+gl.getShaderInfoLog(o)+'</pre>');return o;}return o;}
const pr=gl.createProgram();gl.attachShader(pr,sh(gl.VERTEX_SHADER,VS));gl.attachShader(pr,sh(gl.FRAGMENT_SHADER,FRAG));
gl.linkProgram(pr);gl.useProgram(pr);
const b=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,b);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),gl.STATIC_DRAW);
const l=gl.getAttribLocation(pr,'p');gl.enableVertexAttribArray(l);gl.vertexAttribPointer(l,2,gl.FLOAT,false,0,0);
const U=n=>gl.getUniformLocation(pr,n);
gl.uniform2f(U('u_res'),W,H); gl.uniform1f(U('u_time'),%(t)s);
gl.uniform2f(U('u_mouse'),%(mx)s,%(my)s);
gl.viewport(0,0,W,H); gl.drawArrays(gl.TRIANGLE_STRIP,0,4);
const 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 ctx=out.getContext('2d'); const img=ctx.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);
ctx.putImageData(img,0,0);
c.remove(); document.body.appendChild(out);
</script></body></html>"""
def render(frag_path, out, t=0.0, w=640, h=640, mx=0.5, my=0.5):
src = open(frag_path).read().encode()
b64 = base64.b64encode(src).decode()
html = HTML % dict(w=w, h=h, t=t, mx=mx, my=my,
frag="atob('%s')" % b64)
tmp = tempfile.NamedTemporaryFile("w", suffix=".html", delete=False)
tmp.write(html); tmp.close()
subprocess.run(["firefox", "--headless", "--new-instance", "--profile", "/tmp/ff2",
"--window-size=%d,%d" % (w, h), "--screenshot=" + os.path.abspath(out),
"file://" + tmp.name],
env={**os.environ, "MOZ_NO_REMOTE": "1"},
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=120)
os.unlink(tmp.name)
if __name__ == "__main__":
a = sys.argv[1:]
render(a[0], a[1], *(float(x) for x in a[2:]))