Film Grain Shader Losing Randomness With Time
Closed this issue · 4 comments
When using the Film Grain post-processing shader, after a relatively short amount of time it will seem to converge on a set pattern, and you will be able to clearly see a set of rotating 'lines' of grain.
Reloading the shaders will fix this, but after another few minutes the pattern will emerge again.
So far I've only tested this in Bioshock 1, but I can't imagine any reason it would be game-specific...
Higher values of Grain Power / grainamount variable make this more obvious. I noticed it first with 0.15; but I have been able to see it with lower values, so I don't think that is part of the cause, it's just making it more visible.
I'm having this problem too, it'd be nice to have a noise texture instead of having to deal with RNGs that produce repeating patterns.
Interesting issue. I didn't implement the filter, but I assume it's somehow related to float precision. It should be simple enough to fix.
In fact it is: I asked about it on a forum, and someone who (unlike me) actually knows shader coding magically produced a fix...
I tried the first solution he suggested, and that seems to fix the issue completely, or at least cause it to take multiple hours to appear.
Xerophyte wrote...
Looks like a floating point precision issue. If tc is in [0,1] or so then tc + float2(timer,timer) is going to drop more and more significant digits of the seed value as timer gets large and the sine will only assume a few discrete values.
What that function seems to essentially be doing is
float4 rnm(in float2 tc)
{
float4 rgba = noise4(timer, tc.x, tc.y);
return rgba;
}
with a home-grown 3d -> 4d hash/noise function that's a bit shit. It's based on a very common rand() approximation for shaders (see this Stackoverflow question) that isn't all that good to start but works fine for screenspace xy coordinates. It apparently fails for that particular dimensional expansion and variable range.
I guess the solution is to use a better noise function. There are a bunch of perlin/gradient/simplex libraries around (a random one I googled), but I have no experience with any of them. If you're not familiar with shader languages they might be a bit of a pain, also.
You can try to just replace the initial 3d -> 1d hash with something more well-behaved.
// Option 1: Include the timer in the dot product and try not to trample the other dimensions.
float noise = sin( dot(float3(tc.x, tc.y, timer), float3(12.9898, 78.233, 0.0025216)) ) * 43758.5453;
// Option 2: Since the 2d function is well behaved for this usecase, compute an entirely separate hash for the timer. Slower but computation is cheap.
float noise = sin( dot(tc,float2(12.9898, 78.233)) ) + sin( timer*0.0025216 );
noise = noise * 43758.5453 * 0.5;
I pulled the additional coordinate out of my ass, but it should not be a divisor of the other two and be on roughly the scale of 1/timer, essentially.
I've applied the shader change proposed above.