Stateful Randomness in Shaders
I often find myself needing good random numbers in shaders, but this gets messy when you want to generate a bunch of different ones for each pixel. To get around this, I’m using a genuinely awful hack in DaeForth.
Implementation #
:m rand-given-seed (| seed |)
[ seed seed 1141 * sin ]
[ 12.9898 78.233 ]
dot sin 43758.5453 * fract
;
@float global =pixel-seed
real-position /rand-given-seed \+ =pixel-seed
:m seed .7 rand-given-seed ;
:m gen-seed
seed =>cur-seed
cur-seed rand-given-seed =>next-seed
[ next-seed ] &seed def-macro
cur-seed
;
:m next-random gen-seed pixel-seed * store rand-given-seed ;
Each time you call next-random
it generates a new, high-quality random number for the given pixel. Importantly, it does so with low overhead at runtime!
Example #
For instance, this generates two random colors and then mixes them randomly (for a total of 7 invocations):
[
[ next-random next-random next-random ]
[ next-random next-random next-random ]
next-random
mix
1
] ->fragColor
The majority of the RNG process actually occurs at compile-time, leaving each invocation with something along the lines of:
fract((sin(dot(vec2(tmp_0, sin((tmp_0) * (1141.))), vec2(12.9898, 78.233)))) * (43758.547));
How does it work? #
The magic here lies in the fact that it’s redefining the seed
macro for each iteration. This makes the compiler generate the next seed value. Because they don’t depend in any way on the pixel seed (which can only be known at runtime, obviously) it gets boiled down to a single float per invocation.
For instance, you can see the 7 ‘seeds’ used in the example here:
tmp_0 = (0.86328125) * (pixel_seed);
tmp_1 = (0.19335938) * (pixel_seed);
tmp_2 = (0.7783203) * (pixel_seed);
tmp_3 = (0.55859375) * (pixel_seed);
tmp_4 = (0.26171875) * (pixel_seed);
tmp_5 = (0.7519531) * (pixel_seed);
tmp_6 = (0.42773438) * (pixel_seed);
Happy hacking,
- Sera Brocious (Daeken)