81. Shaders – learning from the built in shaders
Now I want to learn some lessons from some of the shaders provided with Codea.
If you look at the code behind those shaders, you will soon realise that the math in most of them is harder than the shader code. But there’s one shader I found very interesting – the brick pattern shader, not only because the idea is pretty simple, but it has a couple of interesting things in it.
You’ll find the code in the Shader Lab, but I’ve repeated it below anyway.
The vertex shader for the brick pattern
The vertex shader looks quite normal, passing through the colour and texture position
vColor = color; vPos = position;
Hold on! Where is the texCoord and vTexCoord? Why isn’t the texture being passed? And what is vPos?
You’ll soon see that the brick pattern doesn’t use a texture because it creates its own. So I just learned that you don’t have to pass a texture if you don’t want to.
The position variable is the x,y,z position of the vertex, and it is already defined in the vertex shader, so it is available to us to use. This will be needed in the fragment shader so we know where we are on the mesh, and i can see that if I pass it through as a varying variable, it will be interpolated for each pixel.
So I just learned we can create our own varying variables that will be interpolated for us, and I just noticed how vPos was defined, above the main function.
varying mediump vec4 vPos;
So little things can teach you (or me, anyway) a lot.
The fragment shader for the brick pattern
This one is really interesting. Here is the full code (except for the first few lines that haven’t changed).
//The interpolated vertex color for this fragment varying lowp vec4 vColor; varying mediump vec4 vPos; //Brick variables uniform vec4 brickColor; uniform vec4 mortarColor; uniform vec3 brickSize; uniform vec3 brickPct; void main() { vec3 color; vec3 position, useBrick; position = vPos.xyz / brickSize.xyz; if( fract(position.y * 0.5) amp;amp;amp;amp;> 0.5 ) { position.x += 0.5; position.z += 0.5; } position = fract(position); useBrick = step(position, brickPct.xyz); color = mix(mortarColor.rgb, brickColor.rgb, useBrick.x * useBrick.y * useBrick.z); color *= vColor.rgb; //Set the output color to the texture color gl_FragColor = vec4(color, 1.0); }
First, I note we have to define vPos again in here so we can use it. And now vPos is the interpolated position of a single pixel.
Next, there are four variables coming from Codea – brickColor, mortarColor, brickSize, and brickPct. I know this because they are all defined as uniform. If I look in the Bindings tab, I can see some sample values. Note that brickSize has an x,y and z value because it can be drawn in 3D, and I’m guessing that brickPct tells us how much of the brick is made of brick (the rest will be mortar, ie cement).
The first lines that do anything are
position = vPos.xyz / brickSize.xyz; if( fract(position.y * 0.5) amp;amp;amp;amp;> 0.5 ) { position.x += 0.5; position.z += 0.5; }
As you know, bricks are laid so they overlap each other on every second row. So what this code does is to calculate our position as a multiple of brick size. If, for example, a brick was 15 pixels high and we are at y=50, our y position would be 3.333.
So we calculate this position, and if we are in an odd numbered row (calculated by dividing height position by 2), we start drawing the brick half a brick length to the right, by increasing x and z. Here we learn something, which is that in C, you can say x=x+0.5 with x+=0.5. Note also that the position was calculated with the xyz values and not the fourth w value (position is a vec4). I’m guessing this is because we don’t have a w value and if we hadn’t excluded it, we might have divided by zero.
Now the next part.
position = fract(position); useBrick = step(position, brickPct.xyz);
I learn more about C here. The fract function gives us the fractional part of position, for each of the x,y and z values. And the step function in the second line is one we don’t have in Codea. Apparently, its value is 0 unless the value of brickPct.xyz is greater than position, when it becomes 1. This will be done for each of the x,y and z values. This looks as though it is telling us whether to draw a brick colour, by looking at where the x,y and z positions are on the brick, and it will be 1 if we want to draw brick, and 0 if we want to draw mortar.
So here is the final part of the code.
color = mix(mortarColor.rgb, brickColor.rgb, useBrick.x * useBrick.y * useBrick.z); color *= vColor.rgb; //Set the output color to the texture color gl_FragColor = vec4(color, 1.0);
The colour is set by using mix, which is another OpenGL function that mixes two colours together. You give it two colours, and a number N (0 to 1) that tells it how to mix them. The result will be colour1(1-N) + colour2N.
So I can see we are combining the mortar and brick colours that came from Codea, but why are we multiplying the x, y and z values of useBrick? The reason is that we only want to draw brick if all of the x, y and z values are in the brick area, ie they all equal 1. We could use if tests, but since the x,y and z values are all 0 or 1, we can just multiply them. So when we do this, we will get 0 unless each of x,y and z is 1, in which case we draw a brick.
Finally, gl_FragColor is set using the brick or mortar colour, but because the color variable is a vec3, and gl_FragColor is a vec4, we need to add the alpha value in, and this is set at 1.0 (remember OpenGL does everything in fractions, so 1.0 is the same as 255 in Codea).
If you want to see the brick shader in action, there is demo code here.