11.6 - Procedural Texture Mapping

Review

Texture mapping is a technique for specifying a unique color for each fragment that composes a triangle. The colors come from a mapping, which is a function that converts a set of input values into an output value. There are two basic ways this can be done:

  • Lookup the output value from a list of possible values. This is called a ‘table lookup’. In computer graphics this is called image based texture mapping.
  • Perform calculations on a set of inputs to produce an output value. This is called procedural texture mapping due to the fact that the calculations are typically performed in a “procedure.”

This lesson introduces the basic ideas behind procedural texture mapping.

Overview

Image based texture mapping and procedural based texture mapping are both widely used. In fact, the two techniques are often used together to create more realistic surfaces.

Advantages to procedural texture mapping:

  • Procedural texture mapping requires much less memory because there is no image to download or store in RAM or in the GPU’s memory.
  • “Magnification” and “minification” are less of an issue. Procedural texture mapping typically calculates reasonable details at a wide range of scales.
  • Procedural texture maps can generate a wide variety of patterns with small tweaks in their calculations. To get the same effect with image based texture mapping would require a separate image for each tweaked pattern.

Disadvantages to procedural texture mapping:

  • Procedural texture maps are calculated at rendering time for each individual fragment. If the calculations are complex, rendering speeds become slower.
  • The calculations required for procedural texture mapping can be complex and modifying the equations to achieve a specific effect can be difficult.
  • Procedural texture mapping creates patterns. If a more structured design is needed, such as a road sign containing words, image based texture mapping is typically the better approach.

Procedural texture mapping converts input values into a color. The input values can be anything related to a triangle’s attributes, such as its location, orientation, diffuse color, etc.. That being said, we typically don’t want the surface properties of a model to change because of it’s location and/or orientation. For example, the wood grain on a model should not change as a piece of wood is moved in a scene; the wood grain should look the same from any position or angle. Using texture coordinates as the inputs for calculating a procedural texture map causes the generated pattern to be the same regardless of how the model is transformed in a scene. If the geometry of a model is used for procedural texture mapping, the geometry values should be used before any scene transformations are applied to them.

Procedural texture mapping is performed by fragment shader programs. There is no limit to the complexity of such programs, but added complexity means slower rendering. If you need to render various models with different procedural texture maps, it is more efficient to implement a separate shader program for each rendering. The alternative is to use an if statement in a single shader program to select the appropriate procedural texture map at rendering time, but this slows down all rendering.

Software Overview

The basic steps to create a procedural texture map are:

  1. When building a model:
    1. If texture coordinates will be used, assign an appropriate (s,t) value to every vertex of a model.

  2. JavaScript pre-processing for a canvas rendering, if texture coordinates will be used:
    1. Create a GPU buffer object and store the texture coordinates in the GPU.
    2. Get the location of an attribute variable that will hold the texture coordinates in a shader program.

  3. JavaScript setup each time a model is rendered:
    1. Select the correct shader program with gl.useProgram().
    2. If texture coordinates will be used, attach an appropriate buffer object to the texture coordinates attribute variable.

  4. Shader program:
    1. If texture coordinates will be used, in the vertex shader, create a varying variable that will interpolate the texture coordinates across the surface of a triangle. (Or interpolate some other property of the model across the face.)
    2. In the fragment shader, use the texture coordinates (or some other interpolated value) to calculate a color.

Texture coordinates were explained in the previous two lessons. The major discussion for this lesson is the calculations used for a procedural texture map. Equations for three different patterns are explained: gradients, checkerboards, and “Perlin Noise”. Then we discuss how to overlay these patterns at different scales to create more complex textures. Please spend significant time experimenting with each of the example WebGL programs so that you fully understand the concepts.

Texture Map Patterns

Gradient Patterns

A smooth transition from one color into another color is commonly called a “gradient color”. There are many variations on this simple idea. The following WebGL program renders a simple cube where each side has texture coordinates that range from 0.0 to 1.0 across each face. Study the fragment shader and then modify it with each of the suggested gradient functions below.

Show: Code   Canvas   Run Info
../_static/11_gradient_texture/gradient_texture.html

Experiment with Gradient Texture Maps

Please use a browser that supports "canvas"
Animate
Show: Process information    Warnings    Errors
Open this webgl program in a new tab or window

Gradient Experiments

The fragment shader above uses the s component of the texture coordinates as a percentage of the face’s color. The face’s color is “hardcoded” as red in the fragment shader but the face’s color could have come from an attribute variable of the triangle. Note that the operation red * s is a component by component multiply because red is a vector and s is a scalar (a single value). That is, the result of red * s is a new vector (red[0]*s, red[1]*s, red[2]*s).

Experiment with the following code modifications. Hit the “Re-start” button after each change to see the results. If you introduce errors in the fragment shader program, error messages will be displayed in the “Run Info” display area below the canvas window and in the JavaScript console window. (Re-load the entire web page to get back to an error-free version.)

  • Change the variable red to a different color.
  • Use the t component of the texture coordinates to modify the color. Notice how the gradient switches directions across each face.
  • Use the s component of the texture coordinates, but reverse its direction. That is, use a percentage of (1.0 - s). Notice how the black part of each gradient switches sides on each face.
  • Try a percentage of (s + t). This produces values between 0.0 and 2.0. All values greater than 1.0 are automatically clamped to 1.0. Therefore, every location on the face where the (s+t) is greater then 1.0 will get the full color. This produces a solid red triangle on each side of the cube.
  • Try a percentage of ((s+t)/2.0). This scales the sum to always be between 0.0 and 1.0 and produces a nice gradient across the face.
  • Try a percentage of ((s+t)/3.0). This scales the sum to always be between 0.0 and (2/3) and produces a nice gradient across the face. However the color never saturates to full color.
  • Try a percentage of (s * t). This produces percentages between 0.0 and 1.0, but the values are not linear.
  • Try a percentage of sin(s). Remember that trig functions always use radians, so this is calculating the sine of angles between 0.0 and 1.0 radian (57.2958 degrees). The color never becomes saturated because the percentages are between 0.0 and 0.84.
  • Try a percentage of sin(s * PI/2.0). This calculates a percentage between 0.0 and 1.0 because the s component is scaled to values between 0.0 and pi/2 (90 degrees). PI is not a defined constant in the shader language, so you need to define it like this: float PI = 3.141592653589793;
  • Try a percentage of sin(s * 2.0*PI). This calculates percentages between 1.0 and -1.0 because it scales s to be angles between 0.0 and 360.0 degrees (2*PI). All values below 0.0 are clamped to 0.0, which produces the area that is solid black.
  • Try percentages of abs(sin(s * 2.0*PI)). This calculates percentages between 0.0 and 1.0 because of the abs() function which takes the absolute value of its argument. Notice the nice two “color bands”. What happens when 2.0*PI becomes 3.0*PI? What happens for n*PI?
  • Try percentages of (sin(s * 2.0*PI) * sin(t * 2.0*PI)). This produces gradient circles. If you add the abs() function you will get uniform circles. If you change the 2.0 factor to other values, you get that many circles. Try removing the 2.0 factor.

All of the above experiments calculate a percentage of a base color. You can make a gradient vary between two different colors using a parametric equation with the percentage as the parametric equation’s t value. (Don’t confuse the parametric t with texture coordinates (s,t) – they are totally different parameters.)

vec3 red = vec3(1.0, 0.0, 0.0);
vec3 blue = vec3(0.0, 0.0, 1.0);

percent = abs(sin(s * 2.0*PI));
return vec4( red * percent + blue * (1.0-percent), 1.0);

Please experiment with two color gradients.

You can “generalize” gradient texture maps by making the base colors uniform variables that can be set at render time. In addition, some of the equation values could be uniform variables. For example, the equation abs(sin(s * n*PI)) produces n strips of color over a face. The value n could be a uniform variable.

Checkerboard Patterns

The following WebGL program creates a checkerboard pattern. Study the fragment shader program and notice that the function called checkerboard calculates whether a texture coordinate is part of a “white” or “black” tile by determining whether the sum of the texture coordinates is either even or odd. The function returns the color of the appropriate tile. The uniform u_Scale factor determines the number of tile rows in the checkerboard pattern by scaling the texture coordinates. Experiment with this program.

Show: Code   Canvas   Run Info
../_static/11_checkerboard_texture/checkerboard_texture.html

Experiment with Checkerboard Patterns

Please use a browser that supports "canvas"
Checkerboard Scale = 2.0 0.0 10.0
Animate
Show: Process information    Warnings    Errors
Open this webgl program in a new tab or window

Perlin Noise Patterns (randomness with coherence)

../_images/random_vs_perlin_noise.png

Another technique for creating a pattern for a procedural texture map is to use randomness. However, a purely random function is not useful in CGI because, well, the values are random. Textures and patterns in nature have “coherence,” which means that color values in a pattern tend to be similar to their neighboring values. An algorithm called Perlin Noise was developed by Ken Perlin which captures the idea of randomness with the assumption that neighboring color values are somehow related. You can compare the top image to the right, which is totally random, with the image below it which was generated using “Perlin Noise”.

Implementing a pattern that is random but has coherence can be done in a variety of ways using array lookup tables, texture map images, and/or calculations. In the ideal case the method would be computationally efficient and use the least amount of memory. An overview of algorithms that calculate “Perlin noise” can be found at https://en.wikipedia.org/wiki/Perlin_noise#Procedural_coherent_noise. There are patent issues with some of the algorithms, so we will use the OpenSimplex noise algorithm found here.

Definitions of “random” and “noise”

Computer graphics (and computer science in general) borrows terms from other disciplines but does not always keep the meanings of those terms consistent with their original meaning. “Randomness is the lack of pattern or predictability in events.” [1] “Noise” is randomness in a communication signal. For CGI applications, neither definition is totally correct. For randomness, “pseudo-random” numbers are actually pulled from a sequence of known values. To get a different sequence of “pseudo-random” values you start pulling values from a different part of the sequence. We call the starting value for the sequence the “seed” value. For CGI, we might want a “random pattern” on various objects in a scene, but we want the same “random pattern” every time we render the scene. Therefore we need to “seed” a “Perlin Noise” function with the same seed each time it renders. The “seed” values that give the same pattern for repeated renderings are typically the texture coordinates of a model. If a new “random pattern” is needed then the texture coordinates can be translated, scaled, or rotated to provide a different “seed”.

Please study the fragment shader in the following WebGL program. Skip over the OpenSimplex “noise” generator code in lines 1-97 and concentrate on lines 99-109. Notice that it uses a percentage value returned from the simplexNoise2() function to create shades of gray by using the percentage value for each color component, i.e., (percent, percent, percent).

Show: Code   Canvas   Run Info
../_static/11_noise/noise.html

Experiment with "Noise" Texture Maps

Please use a browser that supports "canvas"
translate (0.00, 0.00) scale 1.00
X -10.0 10.0 0.1 30.0
Y -10.0 10.0
Noise parameters: 289.0, 34.0, 1.0, 7.0
0.0 512.0
0.0 512.0
0.0 512.0
0.0 512.0
Animate
Show: Process information    Warnings    Errors
Open this webgl program in a new tab or window

“Perlin Noise” experiments

  • Make the percentage change a color value. For example:

    vec3 color = vec3(1.0, 0.0, 0.0);
    gl_FragColor = vec4(color * percent, 1.0);
    
  • Make the percentage vary between two different colors. For example:

    vec3 color1 = vec3(1.0, 0.0, 0.0);
    vec3 color2 = vec3(0.0, 1.0, 0.0);
    gl_FragColor = vec4(color1 * percent + color2 * (1.0-percent), 1.0);
    

“OpenSimplex Noise” parameters

There a four parameters that modify the “noise pattern” create by the simplexNoise2 function. The number of possible variations is basically infinite. When you find a pattern that meets your needs, the parameters would typically be hard-coded into a fragment shader.

Overlaid Patterns

Complex patterns can be created by combining the patterns we have discussed (gradients, checkerboards, and “Perlin Noise”) at different scales. This will make more sense by working though some examples. Suppose we have the following three functions:

gradient(tex_coords, scale, color)
checkerboard(tex_coords, scale, color)
simplexNoise2(tex_coords, scale, color)

Let’s assume each function returns a color value that can be combined using a “weighted sum”. If the scale factors for each pattern are varied, interesting texture patterns can be created. For example, this takes an equal percentage of three separate patterns:

float percent = 0.33 * gradient(tex_coords, 10.0, color1) +
                0.33 * checkerboard(tex_coords, 4.0, color2) +
                0.33 * simplexNoise2(tex_coords, 1.5, color3);

while the next example has a dominate “noise” pattern:

float percent = 0.20 * gradient(tex_coords, 10.0, color1) +
                0.20 * checkerboard(tex_coords, 4.0, color2) +
                0.60 * simplexNoise2(tex_coords, 1.5, color3);

In addition, the same pattern can be used at different scales, such as:

float percent = 0.33 * simplexNoise2(tex_coords, 10.0, color1) +
                0.33 * simplexNoise2(tex_coords, 4.0,  color2) +
                0.33 * simplexNoise2(tex_coords, 1.5,  color3);

The possibilities are endless.

Designing Overlay Patterns

Designing a pattern for a texture map using overlays is difficult. A good design tool is indispensable. The following WebGL program allows you to experiment with pattern overlays. The basic algorithm looks like this, where the number of overlays, n, and the available patterns to use are arbitrary:

// Set the number of patterns to overlay
const int n = 5;

vec3 color = vec3(0.0, 0.0, 0.0);  // start with no color
for (int j=0; j<n; j++) {
  color = color + amount[j] * pattern(tex_coords, scale[j], color[j]);
}

Design Overlay Texture Maps

Please use a browser that supports "canvas"
Active layer Use this layer?
Pattern type? GRADIENT_PATTERN
CHECKERBOARD_PATTERN
SIMPLEX_PATTERN
Scale = 2.5 0.0 40.0
Amount = 0.50 0.0 1.0
Color = (0.74, 0.32, 0.54) 0.0 1.0
0.0 1.0
0.0 1.0
Active layer Use this layer?
Pattern type? GRADIENT_PATTERN
CHECKERBOARD_PATTERN
SIMPLEX_PATTERN
Scale = 0.2 0.0 40.0
Amount = 0.20 0.0 1.0
Color = (0.35, 0.79, 0.00) 0.0 1.0
0.0 1.0
0.0 1.0
Active layer Use this layer?
Pattern type? GRADIENT_PATTERN
CHECKERBOARD_PATTERN
SIMPLEX_PATTERN
Scale = 35.0 0.0 40.0
Amount = 0.06 0.0 1.0
Color = (1.00, 1.00, 1.00) 0.0 1.0
0.0 1.0
0.0 1.0
Active layer Use this layer?
Pattern type? GRADIENT_PATTERN
CHECKERBOARD_PATTERN
SIMPLEX_PATTERN
Scale = 0.0 0.0 40.0
Amount = 0.00 0.0 1.0
Color = (0.00, 0.00, 0.00) 0.0 1.0
0.0 1.0
0.0 1.0
Active layer Use this layer?
Pattern type? GRADIENT_PATTERN
CHECKERBOARD_PATTERN
SIMPLEX_PATTERN
Scale = 0.0 0.0 40.0
Amount = 0.00 0.0 1.0
Color = (0.00, 0.00, 0.00) 0.0 1.0
0.0 1.0
0.0 1.0
Open this webgl demo program in a new tab or window

Summary

Three basic patterns were discussed for the calculation of a texture map. In addition, the patterns were combined (i.e., overlaid) in interesting ways. This lesson hopefully gave you a foundation on which to build, but we only “scratched the surface” on what is possible (pun intended).

Glossary

procedural texture mapping
Calculate a color for a fragment based on some input values. The input values are often texture coordinates.
image based texture mapping
Get a color for a fragment from a 2D image based on texture coordinates, (s,t).
gradient pattern
In mathematics, a gradient is an increase or decrease in the magnitude of a property. In computer graphics, a gradient pattern increases or decreases the color values across the surface of a face.
checkerboard pattern
A series of alternating colored tiles arrange symmetrically on a 2D grid.
“Perlin Noise”
A pattern that is random but that also has coherence (neighboring fragments have similar colors.)
overlaid patterns
Given some patterns that can be created at various scales, combine multiple instances of the patterns at different scales to create a texture map.

Self Assessment

    Q-475: A procedural texture map is performed by …

  • calculations on input values to produce a percentage or color output value.
  • Correct.
  • calculations to create a color.
  • Not the best answer. Another choice is a better definition.
  • getting a color from a 2D image.
  • Incorrect. This is image texture mapping.
  • a procedure.
  • Not the best answer. Another choice is a better definition.

    Q-476: Using the gradient WebGL program in this lesson, what pattern does the calculation abs(sin(s * 2.0*PI)) produce?

  • Two bands of gradient color on each face.
  • Correct.
  • A checkerboard pattern.
  • Incorrect.
  • A linear change in color from one side a cube face to another.
  • Incorrect.
  • A sine wave.
  • Incorrect.

    Q-477: What is the difference between “Perlin Noise” and pure randomness?

  • “Perlin Noise” produces the same pattern for identical inputs; pure randomness would produce a different pattern every time.
  • Correct.
  • “Perlin Noise” produces a different pattern for identical inputs; pure randomness would produce the same pattern every time.
  • Incorrect.
  • “Perlin Noise” is strictly for computer graphics; randomness can be used for any application.
  • While true in one sense, there is a better answer amoung your choices.
  • “Perlin Noise” only works in shader programs; randomness can be used in any program.
  • Incorrect. “Perlin Noise” is an algorithm that can be implemented in any programming language for any purpose.

    Q-478: Which of the following are true concerning “overlay patterns”? (Select all that apply.)

  • A pattern created by the combination of other patterns at various scales.
  • Correct.
  • Each pattern can use different colors.
  • Correct.
  • All of the patterns need to be of the same type. (E.g., all “Perlin Noise”)
  • Incorrect.
  • The patterns should always be combined with equal percentages.
  • Incorrect.
Next Section - 11.7 - Procedural Texture Mapping (2)