In Episode 4 we made the game reactive by adding falling blocks and smooth camera motion. This time we tackle one of the most noticeable visual upgrades: custom lighting shaders.
We write our own GLSL shaders to simulate 3D lighting with ambient, diffuse and specular components. This gives every block depth, contrast and a real sense of volume. No more flat cubes.
What We Covered
- Wrote a custom GLSL vertex shader to output transformed positions and normals
- Implemented a fragment shader with Blinn-Phong lighting:
- Ambient
- Diffuse (Lambert)
- Specular (Phong)
- Passed per-object
blockColorto the shader - Passed the
cameraPositionto compute view direction and reflection - Integrated custom shader into
DrawBlock()andDrawFallingBlocks()
Design Insights
Why Write Our Own Shaders?
Raylib’s default lighting system is limited. By writing our own shaders we get:
- Full control over light position, color and intensity
- Ability to animate lighting later (e.g., pulsing, colored lights)
- More realistic visuals (light fades with angle)
- Learnable, portable GLSL patterns
How We Structured It
We use the standard Raylib uniforms (mvp, matModel) so Raylib automatically sends in our matrices. We compute:
FragPosition = vec3(matModel * vec4(vertexPosition, 1.0))FragNormal = normalize(mat3(transpose(inverse(matModel))) * normal)
This gives us everything we need to do lighting math in world space.
Implementation Highlights
Vertex Shader
uniform mat4 mvp;
uniform mat4 matModel;
in vec3 vertexPosition;
in vec3 vertexNormal;
out vec3 FragPosition;
out vec3 FragNormal;
void
Fragment Shader
uniform vec3 blockColor;
uniform vec3 cameraPosition;
in vec3 FragPosition;
in vec3 FragNormal;
out vec4 FragColor;
void
Shader Integration
- Replaced
DrawCube()calls with:; ; ; - Passed:
blockColorusingSetShaderValue()cameraPositioneach frame
External Resources
- Basic Lighting - great article describing how lighting works and how it can be implemented
- Materials - the next step for the previous article
- Raylib Custom Shaders - list of available uniforms and default attributes provided by Raylib
What’s Next?
In Episode 6 we’ll revisit what happens after a game ends.
Instead of instantly restarting, we’ll introduce a brand new RESETTING_STATE, where the
entire tower gracefully collapses, shrinks or fades away. In other words the stage will
have a satisfying restart. This gives the game a natural rhythm and a much more polished
reset cycle. Think of it as a reward for losing: a bit of visual closure before you start
stacking again.
Project Code
You will find the complete source code here: tower-blocks