Skip to content

Shader Programming — GLSL Vertex and Fragment Shaders

DodaTech Updated 2026-06-21 6 min read

In this tutorial, you'll learn about Shader Programming. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

GLSL (OpenGL Shading Language) is the programming language for GPU shaders, letting you control how vertices are transformed and how pixels are colored in the graphics pipeline for custom rendering effects.

What You'll Learn & Why It Matters

In this tutorial, you will learn GLSL Shader Programming — writing vertex shaders for coordinate transforms, fragment shaders for lighting and procedural effects, and using uniforms and varyings to pass data between stages. Shaders are essential for all real-time graphics work.

Real-world use: Every game, CAD tool, and UI framework uses shaders. The shader-based security visualization in Durga Antivirus Pro uses custom GLSL fragment shaders to render heat maps of threat activity.

Prerequisites

  • OpenGL Basics (previous)
  • C-like programming knowledge
  • Basic vector math

Learning Path

flowchart LR
  A[OpenGL Basics] --> B[Shader Programming]
  B --> C[Lighting Models]
  B --> D[Texture Mapping]
  B --> E[Compute Shaders]
  C --> F[BRDF and Materials]
  D --> G[Post-Processing]
  B:::current

  classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px

GLSL Language Basics

GLSL is a C-style language with built-in types for graphics programming:

// Basic types
int a = 5;
float b = 3.14;
bool c = true;

// Vector types
vec2 position2D = vec2(1.0, 2.0);
vec3 position3D = vec3(1.0, 2.0, 3.0);
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);  // RGBA

// Matrix types
mat4 transform = mat4(1.0);  // Identity matrix

// Swizzling
vec3 pos = vec3(1.0, 2.0, 3.0);
vec2 xy = pos.xy;  // (1.0, 2.0)

Vertex Shader in Depth

The vertex shader processes each vertex. Its primary output is gl_Position (clip-space position). It can also pass data to the fragment shader via out variables.

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 ourColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    ourColor = aColor;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

The model matrix places the object in the world. The view matrix positions the camera. The projection matrix applies perspective. Together they form the MVP matrix.

Fragment Shader in Depth

The fragment shader runs once per fragment (potential pixel). It computes the final color.

#version 330 core

in vec3 ourColor;
out vec4 FragColor;

void main()
{
    FragColor = vec4(ourColor, 1.0);
}

This simple shader passes the interpolated vertex color to the output. The GPU interpolates vertex colors across the triangle surface.

Uniforms: Passing Data from CPU to GPU

Uniforms are global variables set from the CPU that remain constant for an entire draw call.

// C++ code setting uniforms
int timeLoc = glGetUniformLocation(shaderProgram, "uTime");
glUniform1f(timeLoc, glfwGetTime());

int resolutionLoc = glGetUniformLocation(shaderProgram, "uResolution");
glUniform2f(resolutionLoc, 800.0f, 600.0f);
uniform float uTime;
uniform vec2 uResolution;

void main()
{
    vec2 uv = gl_FragCoord.xy / uResolution;
    vec3 color = vec3(
        sin(uv.x * 10.0 + uTime) * 0.5 + 0.5,
        cos(uv.y * 10.0 + uTime) * 0.5 + 0.5,
        sin((uv.x + uv.y) * 10.0 + uTime) * 0.5 + 0.5
    );
    FragColor = vec4(color, 1.0);
}

Expected output: An animated swirling pattern of colors that changes over time.

flowchart TD
  A[CPU Application] -->|glUniform| B[Uniform Values]
  C[Vertex Buffer] --> D[Vertex Shader]
  D -->|varying| E[Fragment Shader]
  B --> D
  B --> E
  E --> F[Framebuffer]

Procedural Patterns in Fragment Shaders

// Procedural checkerboard pattern
uniform vec2 uResolution;

void main()
{
    vec2 uv = gl_FragCoord.xy / uResolution;
    float checkX = floor(uv.x * 8.0);
    float checkY = floor(uv.y * 8.0);
    float pattern = mod(checkX + checkY, 2.0);
    
    vec3 color1 = vec3(0.2, 0.3, 0.8);
    vec3 color2 = vec3(1.0, 1.0, 1.0);
    vec3 finalColor = mix(color1, color2, pattern);
    
    FragColor = vec4(finalColor, 1.0);
}

Expected output: An 8x8 blue-and-white checkerboard pattern filling the screen.

Lighting in the Fragment Shader

in vec3 FragPos;
in vec3 Normal;

uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 viewPos;

void main()
{
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor * vec3(0.8, 0.2, 0.2);
    
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = 0.5 * spec * lightColor;
    
    vec3 ambient = 0.1 * vec3(0.8, 0.2, 0.2);
    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
}

This implements the Phong lighting model: ambient (base light), diffuse (directional light scattering), and specular (shiny highlights).

Debugging Shaders

// Debug: output normal as color to visualize normals
void main()
{
    vec3 debugNormal = normalize(Normal) * 0.5 + 0.5;
    FragColor = vec4(debugNormal, 1.0);
}

Expected output: A colorful representation of your model's surface normals. Red = X axis, Green = Y axis, Blue = Z axis.

Common Errors & Mistakes

1. Version Mismatch Between Shaders

Mistake: Using #version 330 core in one shader and #version 300 es in another when linking.

Fix: All shaders in a program must use the same GLSL version. Stick to #version 330 core for desktop OpenGL.

2. Missing Output Variable

Mistake: Fragment shader without out vec4 FragColor or mismatch with the location.

Fix: Every fragment shader must declare an output variable. Use layout(location = 0) out vec4 FragColor;.

3. Type Mismatch in Varyings

Mistake: Vertex shader declares out vec3 color but fragment shader declares in vec4 color.

Fix: Varying types must match exactly between stages. Check for precision qualifier mismatches.

4. Uniform Not Found

Mistake: Setting a uniform that the shader compiler optimized away because it was unused.

Fix: Use the uniform somewhere in the shader body. The compiler removes unused uniforms and glGetUniformLocation returns -1.

5. Division by Zero in Shaders

Mistake: Dividing by a uniform that could be zero, causing undefined behavior on the GPU.

Fix: Add a small epsilon: float result = value / max(uniform, 0.001);.

Practice Questions

Question 1

What is the difference between in, out, and uniform qualifiers in GLSL?

Show answer `in` receives data from the previous stage, `out` sends data to the next stage, and `uniform` is a constant set from the CPU that stays the same for an entire draw call.

Question 2

Why is gl_Position a vec4 instead of vec3?

Show answer `gl_Position` uses homogeneous coordinates (x, y, z, w) to handle perspective projection. The w component enables perspective division and clipping.

Question 3

What is swizzling in GLSL?

Show answer Swizzling lets you access vector components in any order using syntax like `vec.xy`, `vec.zyx`, or `vec.rgb`. For example, `vec3(1,2,3).yx` produces `vec2(2,1)`.

Question 4

How does the fragment shader receive per-vertex data?

Show answer The rasterizer interpolates per-vertex `out` variables across the triangle surface using barycentric coordinates, producing smooth gradients between vertices.

Challenge

Write a GLSL fragment shader that renders a procedural Mandelbrot set in real-time. Use uniforms for pan and zoom controls. Color the output based on iteration count with a smooth gradient.

FAQ

What is the difference between GLSL and HLSL?

GLSL is used with OpenGL and Vulkan. HLSL is used with DirectX. Both are similar in concept but differ in syntax. GLSL uses C-style functions like dot() and normalize(), while HLSL uses methods on vector objects.

Can I debug shaders with breakpoints?

Most GPU debuggers (RenderDoc, NVIDIA Nsight, AMD GPU PerfStudio) let you inspect shader inputs and outputs per vertex or per fragment. You cannot step through shader code line by line.

What is precision in GLSL?

Precision qualifiers (highp, mediump, lowp) control the floating-point precision used in computations. OpenGL ES requires explicit precision; desktop OpenGL defaults to highp.

How do I pass an array of data to a shader?

Use uniform arrays (uniform vec3 lights[10]) or shader storage buffer objects (SSBO) for large data sets. SSBOs support arbitrary read/write access from shaders.


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Author: DodaTech | Last updated: June 21, 2026

DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro