// `previous: true` on the window shader retains the last resolved frame as a
// second layer the program samples as uPrevious, rotated each frame - a
// one-frame history. Here it draws a motion echo behind the orbiting square;
// click to toggle the echo term off and compare with the plain frame.
import { render, onFrame, createSignal } from "@solidrt/core"
import { compileShader, destroyShader, glsl, linkProgram } from "@solidrt/core/gpu"
let VERTEX = glsl`#version 300 es
precision highp float;
out vec2 vUV;
void main() {
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
// uSource/uPrevious are top-left origin; flip v so the frame lands upright.
vUV = vec2(p.x, 1.0 - p.y);
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}
`
let ECHO = glsl`
uniform sampler2D uSource;
uniform sampler2D uPrevious;
uniform float uEcho;
in vec2 vUV;
void main() {
vec4 cur = texture(uSource, vUV);
vec4 prev = texture(uPrevious, vUV);
// Brightest of the current frame and the decayed previous one: motion
// leaves a one-frame ghost trailing it.
fragColor = max(cur, prev * uEcho);
}
`
function App() {
let vs = compileShader("vertex", VERTEX)
let fs = compileShader("fragment", ECHO, { header: true })
let echoProgram = linkProgram(vs, fs, { label: "echo" })
destroyShader(vs)
destroyShader(fs)
let [angle, setAngle] = createSignal(0)
let [echo, setEcho] = createSignal(0.65)
onFrame(tick => setAngle(tick / 350))
return (
setEcho(e => (e > 0 ? 0 : 0.65))}
alignItems="center"
justifyContent="center"
>
Click to toggle the uPrevious echo
)
}
render(() => )