GLSL Programming/Blender/RGB Cube
This tutorial introduces varying variables. It builds on the tutorial about a minimal shader.
In this tutorial we will write a shader to render an RGB cube similar to the one shown to the left. The color of each point on the surface is determined by its coordinates; i.e., a point at position has the color . For example, the point is mapped to the color , i.e. pure blue. (This is the blue corner in the lower right of the figure to the left.)
Preparations
editSince we want to create an RGB cube, you first have to create a cube mesh as described in the tutorial about a minimal shader. Continue with creating a Python script and the game logic as described in the tutorial about a minimal shader.
The Shader Code
editHere is the source code of the vertex shader, which you should copy & paste into the assignment to VertexShader
in your Python script:
varying vec4 position;
// this is a varying variable in the vertex shader
void main()
{
position = 0.5 * (gl_Vertex + vec4(1.0, 1.0, 1.0, 0.0));
// Here the vertex shader writes output(!) to the
// varying variable. We add 1.0 to the x, y, and z
// coordinates and multiply by 0.5, because the
// coordinates of the cube are between -1.0 and 1.0
// but we need them between 0.0 and 1.0
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
And here is the source code of the fragment shader which should be assigned to FragmentShader
:
varying vec4 position;
// this is a varying variable in the fragment shader
void main()
{
gl_FragColor = position;
// Here the fragment shader reads intput(!) from the
// varying variable. The red, gree, blue, and alpha
// component of the fragment color are set to the
// values in the varying variable. (The alpha
// component of the fragment doesn't matter here.)
}
If your cube is not colored correctly, check the console for error messages.
Varying Variables
editThe main task of our shader is to set the output fragment color (gl_FragColor
) in the fragment shader to the position (gl_Vertex
) that is available in the vertex shader. Actually, this is not quite true: the coordinates in gl_Vertex
for Blenders's default cube are between -1.0 and +1.0 while we would like to have color components between 0.0 and 1.0; thus, we need to add 1.0 to the x, y, and z component and multiply the result with 0.5, which is done by this expression: 0.5 * (gl_Vertex + vec4(1.0, 1.0, 1.0, 0.0))
.
The main problem, however, is: how do we get any value from the vertex shader to the fragment shader? It turns out that the only way to do this is to use varying variables (or varyings for short). Output of the vertex shader can be written to a varying variable and then it can be read as input by the fragment shader. This is exactly what we need.
To specify a varying variable, it has to be defined with the modifier varying
(before the type) in the vertex and the fragment shader outside of any function; in our example: varying vec4 position;
. And here comes the most important rule about varying variables:
The type and name of a varying variable definition in the vertex shader has to match exactly the type and name of a varying variable definition in the fragment shader and vice versa. |
This is required to avoid ambiguous cases where the GLSL compiler cannot figure out which varying variable of the vertex shader should be matched to which varying variable of the fragment shader.
Variations of this Shader
editThe RGB cube represents the set of available colors (i.e. the gamut of the display). Thus, it can also be used show the effect of a color transformation. For example, a color to gray transformation would compute either the mean of the red, green, and blue color components, i.e. , and then put this value in all three color components of the fragment color to obtain a gray value of the same intensity. Instead of the mean, the relative luminance could also be used, which is . Of course, any other color transformation (changing saturation, contrast, hue, etc.) is also applicable.
Another variation of this shader could compute a CMY (cyan, magenta, yellow) cube: for position you could subtract from a pure white an amount of red that is proportional to in order to produce cyan. Furthermore, you would subtract an amount of green in proportion to the component to produce magenta and also an amount of blue in proportion to to produce yellow.
If you really want to get fancy, you could compute an HSV (hue, saturation, value) cylinder. For and coordinates between -0.5 and +0.5, you can get an angle between 0 and 360° with 180.0+degrees(atan(z, x))
in GLSL and a distance between 0 and 1 from the axis with 2.0 * sqrt(x * x + z * z)
. The coordinate for Blender's built-in cylinder is between -1 and 1 which can be translated to a value between 0 and 1 by . The computation of RGB colors from HSV coordinates is described in the article on HSV in Wikipedia.
Interpolation of Varying Variables
editThe story about varying variables is not quite over yet. If you select the cube object in the 3D View and switch to Edit Mode, you will see that that it includes only 8 vertices. Thus, the vertex shader might be called only eight times and only eight different outputs are written to the varying variable. However, there are many more colors on the cube. How did that happen?
The answer is implied by the name varying variables. They are called this way because they vary across a triangle. (Note that all polygons are decomposed into triangles before they are rendered in OpenGL.) In fact, the vertex shader is only called for each vertex of each triangle. If the vertex shader writes different values to a varying variable for different vertices, the values are interpolated across the triangle. The fragment shader is then called for each pixel that is covered by the triangle and receives interpolated values of the varying variables. The details of this interpolation are described in “Rasterization”.
If you want to make sure that a fragment shader receives one exact, non-interpolated value by a vertex shader, you have to make sure that the vertex shader writes the same value to the varying variable for all vertices of a triangle.
Summary
editAnd this is the end of this tutorial. Congratulations! Among other things, you have seen:
- What an RGB cube is.
- What varying variables are good for and how to define them.
- How the values written to a varying variable by the vertex shader are interpolated across a triangle before they are received by the fragment shader.
Further Reading
editIf you want to know more
- about the data flow in and out of vertex and fragment shaders, you should read the description of the “OpenGL ES 2.0 Pipeline”.
- about vector and matrix operations (e.g. the expression
0.5 * (gl_Vertex + vec4(1.0, 1.0, 1.0, 0.0))
), you should read “Vector and Matrix Operations”. - about the interpolation of varying variables, you should read “Rasterization”.