top of page

First Shader (passthrough)

For our first shader, I will use a simple passthrough shader. It is the version of a "Hello World" for GLSL. Most of the information will pass through the shader unaltered, and we will assign it a color in the fragment shader. I know that this may be a boring shader, but it will give us an example to walk through the code and understand the different parts of it.

First lets understand how GLSL coding works.

GLSL is a C-based language. It's syntax is taken directly from C, with a little bit of C++ added in. Pointers and references are not available, but bitwise operations are allowed. I will not cover learning how to program, a little bit of programming knowledge is required. Most BGE developers have at least a grasp on this - programming in Python. That is more than enough to pick up GLSL, so do not feel discouraged if you do not know any of the C-style languages.

GLSL, whether in blender or another program, is sent to the GPU as a text string. The GPU compiles the code and attempts to optimize it. Others might disagree, but I will state it here - the optimization attempt is a fail. Optimize the code yourself, do not give the GPU a chance to do it. I will cover code optimization in a separate tutorial dedicated to it.

Lets present the full code:

If you need to copy the code, you can find it HERE.

As I mentioned earlier, this code is quite simple. It takes the vertices that Blender sends to the GPU, passes them through the pipeline and colors the fragments an orange color. Lets step through the code.

We start in python by importing logic and declaring 'own'. Not much needs to be said about this, I'm sure a lot of you already do something similar to this in your own projects. We use it later in the file to determine what mesh we are going to shade (owner of the script).

We then store the shaders in variables as strings. First up is the Vertex Shader, I stored it in a variable named VertexShader.

The string has to be a literal so it starts and ends with three quotation marks ("""). Line 6 is a preprocessor, it defines the version of GLSL we will use. The version number corresponds to the GLSL version (NOT THE OPENGL VERSION) minus the decimal (or multiplied by 100 whatever is easiest to remeber), so GLSL version 1.2 becomes #version 120. Different versions of GLSL have different features available. One main difference that I want to point out is that beyond version 1.2 the built-in variables are no longer available to you unless you are using the compatibility profile. To enable the compatibility profile, we simply add the word 'compatibility' as we have done in the fragment shader.

The version definition must come before anything else in the shader. After that we can add in uniform and constant qualifiers, which we will talk about in the next tutorial (we have not added any here). After those we can add any functions that we might create. Again we have none of those here, so we will discuss that at a later time.

Line 8 we start our main function. In a C-style language, we start a function by declaring the return data type - in this case it is void. The main function cannot, and will not contain any arguments.

So that leads us to line 10 and gl_Position. gl_Position is a built-in output variable that sends the vertex position to primitive assembly. It is a vec4, which means that it is an array of four floats. The first three correspond to the x, y, and z position; with the remaining float used to determine if it is a vector or a point. This last float should always be 1.0, unless you do not want the vertex to render.

In this example we determine gl_Position by multiplying gl_ModelViewProjectionMatrix by gl_Vertex. Lets start with gl_Vertex, which is the vertex position from the object origin. Again gl_Vertex is a vec4. Since the position is given from the object origin, more commonly know as being in Object/Model Space, we need to convert it to screen coordinates (known as Clip Space). If we did not do this, then the object would always display as though we were looking down on the object from the top. The GPU operates in Clip Space; in this space the X-axis is left/right of the window, the Y-axis is up/down, and the Z-axis is forward/back from the camera.

There are four different spaces to work in; Object Space (similar to Blender's local coordinates), World Space (similar to Blender's global coordinates), View Space (isometric view coordinates - over simplified), and Clip Space (perspective view coordinates). If you are using a geometry shader you can do the transformation there instead of the Vertex Shader. To transform the position into Clip Space, we simply multiply the vertex position by a transformation matrix. In GLSL version 1.2 and later versions in the compatibility profile we can use the built-in transformation matrices: gl_ModelViewMatrix (for View Space) and gl_ModelViewProjectionMatrix (for Clip Space). There is no built-in matrix to transform into World Space, but Blender supplies us with a ModelMatrix through python that we can send in as a uniform (which we will do in a later tutorial).

In case you wanted to know how these matices are calculated:

gl_ModelViewMatrix = ModelMatrix * ViewMatrix;

gl_ModelVIewProjectionMatrix = ModelMatrix * ViewMatrix * ProjectionMatrix;

And to recap the different spaces:

Object_Space = VertexPosition;

World_Space = VertexPosition * ModelMatrix;

View_Space = VertexPosition * ModelMatrix * ViewMatrix;

Clip_Space = VertexPosition * ModelMatrix * ViewMatrix * ProjectionMatrix;

Each of these matrices are mat4, which means that it is four arrays of four floats each (i.e. 4x4 matix). mat4's can only be multiplied with vec4's and other mat4's.

The Fragment Shader is similar to the Vertex Shader, we start again by defining the version. Note that the version is different from the Vertex Shader, we can use different versions at every stage of the pipeline.

This time, in the main function we have gl_FragColor. This is the built-in output variable for the color that is sent to the framebuffer. It is a vec4 that corresponds to Red, Green, Blue, and Alpha (RGBA). Since the version is 1.5, we need the compaibility profile to use this built-in variable. As stated earlier, versions beyond 1.2 (in core profiles) discontinued the use of built-in variables like this in favor of user defined variables and functions (with the exception of version 1.3 which allows it, but throws up warnings).

This last part of the code is what we use to send our shader code to the GPU with blender. For every material in our mesh, we check if it has a shader set. If it does not, we set the source as the variables that we setup. The source is then sent off to the GPU for compilation automatically.

This is what our simple shader looks like on a cube:

In this example we only used vertex and fragment shader, setting up a geometry shader is more complicated since they are not officially supported yet. I will get to Geometry Shaders at a later point because they a little more complex; it is more important to grasp the basics first before jumping into something more difficult. In the next tutorial, I will cover qualifiers and data types so that we can alter the data efficiently.

Featured Posts
Recent Posts
Search By Tags
No tags yet.
Follow Me
  • YouTube Clean
bottom of page