So I am trying to sample a film dust texture randomly to create the effect of the game playing off of an old film reel. I was using _Time.y and feeding that into a random function (frac(sin(dot(_Time.y, 989.24243)))) to get a random value from 0 - 1. Then if that value is greater than some cutoff, sample the dust texture. However, this makes the dust jump in and out too fast. If I set the target frame rate to 24 (like a film would play at) it looks great. But I would rather the shader just be able to do this on its own and let the game run at what ever frame rate I want it to.
I tried doing _Time.y / 24 and then flooring that value. I tried doing _Time.y * 24 and then flooring that value. I also tried a lot of others but it just doesn’t ever look as good as just setting the frame rate to 24. Which tells me I must be doing the math wrong. Does anyone know how to do this?
_Time progresses in game time, increasing by 1.0 per second. At 24 frames per second, that means each frame is 1/24 th of a second, roughly 0.041667.
If you do floor(_Time.y / 24) it’ll take 24 seconds before the value that comes out of the floor() increases by 1. So you don’t want that.
If you do floor(_Time.y * 24), after every 0.041667th of a second the value that comes out will increase by one. This is what you want.
But there are a few possible issues.
If the game is running at 30 fps, and you’re using the above code to only increment every 24th of a second, you’re going to have the same problem that you have converting 24 fps material to 30 or 60 fps playback. That is you’ll have inconsistent frame pacing. There will be a kind of stutter and it won’t look as “smooth”. You can’t divide 30 or 60 by 24, which is why when film is be played on 30 or 60 hz display mediums usually go through some kind of conversion, like 3:2 pulldown or more modern interpolation techniques. If you’re on a 120 hz display and run at a locked 24 fps then every frame will accurately be shown for that 1/24th of a second and you won’t have the problem. But I suspect this isn’t the issue.
Second problem is floating point precision. As the game runs longer and longer, eventually you loose precision in the time value being sent to the shaders. You can make this worse by multiplying the value by a larger number. Multiplying _Time.y by 24 means if your game, or the editor, has been running for more than 6 hours it no longer has enough precision to get individual frames and the effect will start to stutter. 6 hours later it’ll drop to a lower frame rate than intended, and eventually even stop entirely (though that takes several days). This may be the issue, but it’s unlikely since when you press play even in the editor it restarts the time for the Game view. And technically the precision issue is a problem even if you don’t multiply time before passing it to the random function you have since that’s multiplying it by 989.24243, so you’ll possibly stop getting unique random values every frame after just a few minutes even in the original setup.
The last thought I have is family of frac(sin(dot())) random value generators really suck. Really just doing frac(_Time.y * 3.0) might get you something just as effective for this use case. But you might also be getting a very different quality of randomness from passing in integer values vs floating point values. So you could try floor(_Time.y / 24.0) * 24.0 to see if that gets you something that looks better. You could also try using a pseudo random function that expects integer values.
Thank you for the very detailed answer BGolus. I am just seeing this now, but I did eventually get it working. My hash function was actually using _Time.y which was messing it up and took me longer to realize than I would like to admit (Homer Simpson Moment). After I fixed that I ended up using floor(_Time.y * 24) but it still didnt quite look right. Instead of 24 I used 10 and it looked and it looked great. Below is the code that did the trick. Scratch later gets multiplied by the color to to get the effect.