Last time I promised a splash of colour. The plan was to implement per vertex colouring, but I've got a better idea. Let's do something that Warp3D can't do: procedural/algorithmic "texturing" using shaders.

We're going to write shaders that generate texels on-the-fly instead of reading them from memory. Sure, the old Warp3D could render identical images using "real" textures, but that's just not the same. Procedural textures take up almost no memory (only parameters/coefficients need to be stored). They also have infinite resolution since it's based on mathematics instead of pixels.

Let's get started...

## Step Two: Changing the Vertex Shader

The fragment shader needs texture coordinates, and it's the vertex shader's task to deliver them. For simplicity we'll use the normalized 2D position, i.e., the position scaled so that it's in the range [0,1] (zero to one inclusive). Only a few vertex shader modifications are required.

First, change the colour output parameter to:

`out vec2 pos;`

`const vec2 posScale = vec2(1.0 / 640, 1.0 / 480);`

Finally, replace the "colour = ..." line with:

`pos = vertPos * posScale;`

The line above performs a per-vector-element multiply, scaling the input position to the desired range.

```#version 140

in vec2 vertPos;

out vec2 pos;

const vec2 posScale = vec2(1.0 / 640, 1.0 / 480);

void main() {
pos = vertPos * posScale;
gl_Position = vec4(vertPos, 0.0, 1.0);
}```

## Step Three: A Simple Gradient

```#version 140

in vec2 pos;

void main() {
gl_FragColor = vec4(pos, 0.0, 1.0);
}
```

This simply writes the 2D position to the red and green channels, generating a smooth gradient. Recompile and run the program to see the result (below). Congratulations! You just created your first procedural texture. It's rather boring, though.

## Step Four: Ripples

Okay, let's up the ante a bit. Try the following fragment shader:

```#version 140

in vec2 pos;

const float M_PI = 3.14159;
const float M_4PI = 4 * M_PI;

void main() {
vec2 adjPos = 8 * (pos - vec2(0.5));
float green = 0.5 * sin(radial * M_4PI) + 0.5;
gl_FragColor = vec4(red, green, 0.0, 1.0);
}```

Line by line, the code above does the following:

• Calculates a scaled position in the range [-4,4]
• Sets the red colour to be the absolute value of adjPos' x-axis coordinate
• Sets green to be a radial ripple using the trigonometric sine function
• Outputs the calculated colour (with blue = 0.0, and alpha = 1.0)

NOTE: If you're worried about the sine function's overhead; GPUs have dedicated sine & cosine instructions (at least the AMD Southern Islands GPUs do).

With this shader, the program now generates the image below. That's a lot more interesting.

## Step Five: Go Nuts

Let's take this several steps further...

```#version 140

in vec2 pos;

const float M_PI = 3.14159;
const float M_2PI = 2 * M_PI;

void main() {
vec2 adjPos = M_2PI * (pos - vec2(0.5));
float mid = base + 0.25 * sin(4.0 * adjPos.x - M_2PI) * cos(5.0 * adjPos.y);
float high = mid + 0.125 * sin(8.0 * adjPos.x) * cos(10.0 * adjPos.y);
gl_FragColor = 2.0 * abs(fract(10.0 * (vec4(base * high, mid * high, high, 1.0))) - 0.5);
}```

The code above uses trigonometric functions at multiple frequencies to generate something quite complex (see below). The key to the banding is the fract function, while abs makes it smoother. I encourage you to experiment with the shader code. Try adjusting values and/or forumulae. Visually seeing what code changes do gives you a feel for what how it works.

## Conclusion

This tutorial has given a taste of procedural texturing. Don't get the impression that it's all about bright abstract patterns, though. Procedural textures can render real-world things such as: fractals, clouds, marble, etc.

Procedural textures have two key advantages over regular textures: they take up almost no memory, and they have infinite resolution. Having said that, fragment shader instructions are executed every pixel, so it does have its own cost. Use too complicated an algorithm and the processing time will take its toll on performance.