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:
@@ -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:]))
|
||||
Reference in New Issue
Block a user