Generative noise, and the speed limit
Hello readers, for my 3rd (and very professional) tech blog, I'm going to be talking about my shader work on my 8 week long group project for my MSc Games engineering course. As an initial Core Physics and tools programmer, I also spent a considerable amount of time in GLSL, as an acting tech artist. This article will explore noise, and how I used it to create some cool effects. Some code will be in WebGL, however it is relatively easily transferable to both GLSL and HLSL.
Noise?!
Randomness in computing is surprisingly complex. It's impossible for computers to generate truly random numbers, and for effects that we will be tackling in this game, I would need random "numbers" that felt random enough to give the perception of true randomness. The way shaders and other graphical wizardry things do randomness is through noise. One way is to have a pre-defined black and white texture where you sample the monochromatic values to give a random value at a particular x and y value.
But there is quite a lot of difference between true randomness and noise. The flowing water in a river isn't randomness, its noise. TV static on the other hand, that's randomness.
Patricio Gonzalez in his Book of Shaders (a lot of good shader theory found here) talks about a random function that can be used in GLSL. Since GLSL isnt C++, and getting a random value for every pixel from C++ isn't possible, a pseudorandomfunction is required if we want our shaders to have some randomness to them.
float random(in vec2 uv){
return fract(sin(dot(uv.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
This function will spit out a random value based on where the fragment is on a quad. The parameters in vec2 uv
usually are either the coordinates of the pixel in 2D space or a multiple of that. We then perform a few mathmatical functions onto them, dot them, take its sin and then we return the fractional part of all of that using fract()
. This gives us a random value close enough to use for our use-cases.
Its good to note that there are other random functions as well, but every one eventually yields similar results to what we have here. The visual representation of a quad where the random function is used with a few other things to give us a noise generation is shown below.
Sure, but how do I use this?
Generative noise is good and all, but how do we use this in our games? One thing I figured when playing around with Shaders are, when you convert the normal Euclid coordinate system to Polar coordinates, we get some interesting effects. The method to do that, was as follows.
vec2 toPolar(vec2 cartesian, float center, float RadialScale, float LengthScale){
float distance = length(cartesian);
vec2 delta = cartesian -vec2(center);
float radius = length(delta) * 2.0* RadialScale;
float angle = atan(delta.x, delta.y) * 1.0/6.28 * LengthScale;
return vec2(radius, angle);
}
Sending our earlier noise out put into this function we get the following output:
Seem familiar? See if you can guess the effect I am recreating with these before it's revealed.
Next, we rotate the original UV that were in polar coordinates, by time and a few artist authored variables like rotatespeed
, framerate
and velocity
. For this, we write another function named uRotate
which intakes the coordinate, the amount, and the center.
vec2 uRotate(vec2 uv, float amount, float center){
uv-=center;
amount *= PI/180.0;
float s = sin(amount);
float c = cos(amount);
mat2 rmat = mat2(c, -s, s, c);
rmat *= 0.5;
rmat += 0.5;
rmat = rmat * 2. -1.;
uv.xy = uv.xy *rmat;
uv+=center;
return uv;
}
Using the above functions, and a few magic numbers for the variables to see what feels good, we have:
Note: the effect might look a bit blurry/pixel tearing, that's just nature of noise over video.
Its already looking how we want it to look. Now all we need is a way to separate out the lines in the center with the lines near the edges so we can mask them out. To do this, we can use an inverse vignette that goes from 0 (black) in the center and then goes 1 (white) on the edges and then sample those values.
vec3 inverseLerp(float a, float b, vec3 T){
return (T-a)/(b-a);
}
It's important to note, all of these values can be sampled from an image, but generating these over code allows it to be faster since its on the GPU and allows us more control over it. For example, we can move the center of this vignette just by adjusting the UVs.
Now, we then use this, and a few more artist authored variables like lineDensity
and lineFalloff
to give us a cutoff of these lines. We then make sure all the white parts are highlighted by multiplying the white part of the mask with a float that controls how many lines will be seen, and then smoothstep()
out the sections we don't require, and we end up with something like this.
In code we can also change things like linefalloff
that controls how far in the lines go into the center of the screen.
Woah, do you go fast?
Our artistic friends call these speedlines. They are an easy way to make your game feel fast. In game development, it's all just an illusion. A speed of 100 m/sec may seem fast, but if your world is scaled up to say a few ten thousand meters, its the same as having 1 m/s over a smaller world.
Below is a representation of what the game looked with and without speedlines, as we can see, the speed seems more blistering and fast even though both clips have the same speed.
Further work ...?
Being mostly happy with the speed-line effects, there are still a few things that could be improved upon, which are:
During the bottom arc of the grapple, or when sharp turns happen, since the speed line directions are linked to the movement vector, the speed-lines reverse their direction and move into the center of the screen. An easy fix would be to take the modulus of the speed instead of a vector.
Right now, the speed lines CAN be used in any direction, however setting those up is a bit frustrating as you have to set multiple uniforms. This couldve been made easy but using math and calculating a direction depending on where you are moving.
And that's about it for this tech blog! My foray into graphics programming, and tech art continues and I will return for something related to the project above. Until then, stay warm and well fed, thank you for the read.
Subscribe to my newsletter
Read articles from Idhant Dabral directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by