Procedural generation of content has become more and more commonplace in the game industry. You see it in triple-a titles, but it is most easily found in the indie game developer scene. Why is that? It’s because you can leverage the strengths of a computer to generate content for you. This chapter of OpenGL 4.0 Shading Language Cookbook digs into some ways you can use procedural perlin noise generation for generating interesting shaders.
This book decided to stay away from generating these noise textures in the shaders themselves in favor of generating them on the CPU at start-up time and providing them to the shader as a texture. The benefits to this method is that it is a lot faster to execute your shader code because noise texture generation can become expensive. However, there are some downsides such as not being able to scroll infinitely in any direction and having a finite resolution to your noise texture. However, these downsides can be negligible in many cases.
The book also decided to use LibNoise to generate noise textures. Since the LibNoise project is so old and has not been maintained for some time, I decided against using it. I was not having much luck compiling debug and release versions in both x86 and 64 bit mode so I went another direction and used GLM which I was already leveraging in my codebase. Win-win for me! However, this required me to change the code a bit to account for the differences. If you want to see how I implemented the noise texture generation with GLM, you can look at the codebase for the project on GitHub. The commit hash at the time of this writing is ed4b01dd572a94cba8d264df9865be0665304f48.
In order to generate a noise texture, I had to create a new game component class in the engine that will generate the texture on start-up and then submit it to the GPU and attach it to any shader on the game object. This code uses the sum of multiple perlin noise octaves to generate this cloudy appearance. More specifically, this shader uses four octaves which are then stored in the x, y, z, and w components respectively. Here I am displaying the z component by itself.
You will notice that there are a couple of seams in the texture. This is because I am tiling the texture to make the seams more apparent. Out next example addresses this.
Seamless Noise Texture
This noise texture does not have any obvious seams despite the fact that I am tiling this texture just as I am in the example before this. Now, a keen eye can tell that this is a tiled texture, but seamless and tiling is much more acceptable than tiling without seamlessness. We can achieve a seamless texture by leveraging the continuous property of noise textures. Perlin noise can extend infinitely in any direction without any repeating patterns. For seamless textures, we take four samples for each texel. One sample is a the point we are coloring, the other four are sampled just outside of the texture space relative to the starting corner. These four points form a square in Perlin space the same size of the texture you are ultimately rendering. The influence of the four samples vary linearly when moving in the x or y direction in order to provide a seamless texture.
Cloud Noise Texture Effect
Okay, okay! I admit that this doesn’t look much like clouds. This example in the book simply demonstrates how you can use the noise texture to map it to a color gradient. The book chose a white and blue color to get a cloudy look to them. Not much is different here except that the shader is doing a mix operation between the two colors based on the noise texture sample.
Wood Grain Texture
Here we have a more realistic example of what noise textures can do. In this example, the surface of this model is shaded to look like wood by using some calculations to generate perfect rings of alternating color and peterbing the texel’s location on those perfect circles by adding the texture’s noise to the sample position. This is a case where you can get away with a smaller noise texture size because the noise texture is just being used in the shader to shift the lines around. When sampling between texels, the linear filtering will smooth it out and make it look good even with a low resolution.
Here we combine the noise texture with the discard feature in GLSL to punch holes in the model. You may notice that this looks very similar to an exercise in chapter 4. That is because it pretty much is the same way I approached that problem, but back then I used a texture I generated inside of Gimp. Here we are generating the texture at runtime. This means we ultimately save on disk space by allowing the texture to be generated on the end-user’s machine. You can also generate lower or higher resolutions depending on the user’s hardware. It would be much harder to get that type of benefit utilizing the method in chapter 4.
Paint Spatter Effect
Instead of discarding fragments that are outside of a specific range as we did in the previous example, we can decide to color them differently. This example uses a higher noise value in the texture to create the effect of small specks of paint on the model. In my example I decided to shade between two different sets of phong properties which allow me to change the albedo, specular and ambient color properties instead of simply the color alone.
Night Vision Effect
The last shader in the chapter brings together procedural texture generation with post processing effects to create a night vision effect. In this example the noise texture was created at a high frequency and used as a subtractive overlay in the post-processing step. It also takes the luminance value of each fragment and assigns that to the green channel of the fragment. It is also checked against the distance between two points with a float value to generate a binocular effect.
How have you leveraged procedurally generated content in your games or project. Let me know in the comments below!