random number generation, noise and fbm, glsl

I should really change the name of this blog to Shader Shit.... anyway.. I'll get back to Unity specific crap one day. The following code is all for GLSL & will need adapting for use in a Unity fragment shader. I like to use Kodelife to test out my shader ideas. Look it up & give it some support if you like it!


This post is about fake randomness, learnt from The Book of Shaders & The Art of Coding, on the website & youtube channel respectively.

Within your main GLSL function, you might need to create a random value, often just a float value (remember you can make a vector from multiple floats). A great thing to feed such a random number generating function is your UV value. Here's an example-

float random(vec2 uv){

return fract(sin(dot(uv,vec2(52.2957001,2355.957250024))*574.9829619));

}

What we're doing here is getting the dot product of our UV coordinate, with an arbitrary vector that consists of large numbers with plenty of "complex" fractional data. We then take the sine value of the dot product (which is a single float value), multiply that by another large arbitrary number and then take just the fractional part of that - discarding the whole number component. As long as we do not change these arbitrary numbers, our returned "random" values will always be the same for our given UVs.

Random numbers are useful for lots of things - creating TV static-noise, variation, a sense of unpredictability... FBM! But first,we need a noise function...and we use our random function inside this!

float noise(vec2 uv){

    vec2    i_uv=floor(uv);

    vec2 f_uv=fract(uv);

    float a=random(i_uv);

    float b=random(i_uv+vec2(1,0));

    float c=random(i_uv+vec2(0,1));

    float d=random(i_uv+vec2(1,1));

    vec2 u=f_uv*f_uv*(3.0-2.0*f_uv);

    float n=mix(mix(a,b,smoothstep(0.0,1.0,u.x),mix(c,d,smoothstep(0.0,1.0,u.x),smoothstep(0.0,1.0,u.y));

return n;

}

Firstly, we pass the uv coords to the noise function. We then get the "tile" id's of the coordinates by taking the "floor" values and placing them in i_uv and we also store the fractional values in f_uv.

Next we generate a random float value at the corners of each tile - so we use i_uv & the offsets of 1 in each direction, to get values a,b,c and d, for each corner. We then calculate a "hermite interpolation" using the fractional value in f_uv and store this vector in "u".

This interpolation value is used in a scary looking nested mix statement. What we're doing is blending between the values stored in corners a and b, across the x direction (the bottom two points of a square). Then we blend between the corner values of c and d, also across the x direction (the top two points of a square). Finally we blend between these two blends across the y direction and return the final blended random value.

There is actually a more efficient way of writing this last bit of code, which you can look up, but for the sake of clarity and understanding what we're doing I'll leave just this version.

Now that we have a noise function, we can make one for FBM -

 

FBM stands for Fractional Brownian Motion, and we're going to be using the noise function within it! Code follows -  (the #define sits outside of the function, it is optional, you could just define octaves as a variable within the function, but I've grown to like creating these global sorta variables - probably a bad habit!)

#define OCTAVES 6

float fbm(vec2 uv){

    float value=0.0

    float amp=0.5;

    vec2 shift=vec2(90,0);

    mat2 rot=mat2(cos(0.5),-sin(0.5),sin(0.5),cos(0.5); //rotate to avoid axis bias

    for(int i=0;i<OCTAVES;i++){

    value+=amp*noise(uv+shift*i);

    uv*=rot; 

    uv*=2.0;

    amp*=0.5;

}

return value;

}

Firstly, I define OCTAVES, 5 or 6 is a good value. I find there isn't much added complexity to the noise above this value. Very high values will cause the shader to chug a bit, due to the added number of loops.

We pass our uv values to the fbm function, then immediately initialise a bunch of variables. "Value" is the variable we will be returning and we start it at zero. We set amp (for amplitude) to 0.5 and create a "shift"vector and rotation matrix with arbitrarily chosen values. You could put time into these variables for an animated effect. The rotation matrix is present to avoid any axis bias on the noise.
Next is a for loop. To our initialised value, we add a noise value, generated by our previous function, multiplied by the amplitude. We modulate the uv value that we pass to the noise function with the "shift"vector & we also multiply this by our loop iteration number i. The shift vector is actually optional, I think it provides more complexity to the resulting fbm values.
After we've added this resulting noise to our value, we alter all of the variables slightly. The uvs are rotated, and also scaled (we get a kind of "fractal" complexity this way - each time the loop runs, the uv space is larger, so we get more detail) and we also reduce the amplitude to 50%. You might want to #define the value you scale the uv's by, as well as the amplitude multiplier.

Once all our loops are done, we return the final value.

I'll follow this post with another, showing how we can use fbm to mess around with uv's and colour blends.

 

Comments

Popular posts from this blog

setting VFX graph properties using C#