OpenGL Cheatsheet

Lighting

Use this OpenGL reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Lighting Model Overview

All lighting in OpenGL is implemented in shaders — there is no built-in lighting in core profile. The standard model decomposes light into three terms:

Final = Ambient + Diffuse + Specular

All calculations should be done in a consistent space (usually world space or view/eye space).

Phong Lighting Model

// Fragment shader — Phong model in world space
in vec3 vFragPos;
in vec3 vNormal;
in vec2 vUV;

uniform vec3 uLightPos;
uniform vec3 uLightColor;
uniform vec3 uViewPos;
uniform sampler2D uDiffuseMap;

out vec4 FragColor;

void main() {
    vec3 albedo = texture(uDiffuseMap, vUV).rgb;

    // Ambient
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * uLightColor;

    // Diffuse
    vec3 norm    = normalize(vNormal);
    vec3 lightDir = normalize(uLightPos - vFragPos);
    float diff   = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * uLightColor;

    // Specular (Phong)
    float specPower = 64.0;
    vec3 viewDir  = normalize(uViewPos - vFragPos);
    vec3 reflDir  = reflect(-lightDir, norm);
    float spec    = pow(max(dot(viewDir, reflDir), 0.0), specPower);
    vec3 specular = spec * uLightColor;

    FragColor = vec4((ambient + diffuse + specular) * albedo, 1.0);
}

Blinn-Phong (Preferred)

Uses the halfway vector instead of the reflection vector — cheaper and avoids cutoff artifacts.

vec3 halfDir = normalize(lightDir + viewDir);
float spec   = pow(max(dot(norm, halfDir), 0.0), specPower);
// specPower for Blinn-Phong ≈ 4× the Phong exponent for similar appearance

Light Types

Directional Light

No position, no attenuation — simulates distant light (sun).

struct DirLight {
    vec3 direction;  // towards the light (normalized)
    vec3 color;
};

vec3 calcDirLight(DirLight light, vec3 norm, vec3 viewDir) {
    vec3 lightDir = normalize(-light.direction);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 half  = normalize(lightDir + viewDir);
    float spec = pow(max(dot(norm, half), 0.0), 32.0);
    return (diff + spec * 0.5) * light.color;
}

Point Light with Attenuation

struct PointLight {
    vec3  position;
    vec3  color;
    float constant;   // 1.0
    float linear;     // 0.09
    float quadratic;  // 0.032
};

float calcAttenuation(PointLight light, float dist) {
    return 1.0 / (light.constant
                + light.linear    * dist
                + light.quadratic * dist * dist);
}

Common attenuation constants (range approximations)

RangeConstantLinearQuadratic
71.00.71.8
131.00.350.44
201.00.220.20
321.00.140.07
501.00.090.032
1001.00.0450.0075
2001.00.0220.0019

Spotlight

struct SpotLight {
    vec3  position;
    vec3  direction;  // normalized, pointing away from light
    vec3  color;
    float cutoff;      // cos(inner angle) e.g. cos(radians(12.5))
    float outerCutoff; // cos(outer angle) e.g. cos(radians(17.5))
};

vec3 calcSpotLight(SpotLight light, vec3 fragPos, vec3 norm, vec3 viewDir) {
    vec3  lightDir = normalize(light.position - fragPos);
    float theta    = dot(lightDir, normalize(-light.direction));
    float epsilon  = light.cutoff - light.outerCutoff;
    float intensity = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0);

    float diff = max(dot(norm, lightDir), 0.0);
    vec3  half = normalize(lightDir + viewDir);
    float spec = pow(max(dot(norm, half), 0.0), 32.0);

    float dist = length(light.position - fragPos);
    float att  = 1.0 / (1.0 + 0.09 * dist + 0.032 * dist * dist);

    return intensity * att * (diff + spec * 0.5) * light.color;
}

Material Maps

struct Material {
    sampler2D diffuse;    // albedo
    sampler2D specular;   // per-texel specular intensity
    sampler2D normal;     // tangent-space normals
    sampler2D emission;   // self-illumination
    float     shininess;
};

uniform Material uMat;

vec3 albedo   = texture(uMat.diffuse,   vUV).rgb;
vec3 specMap  = texture(uMat.specular,  vUV).rgb;
vec3 emission = texture(uMat.emission,  vUV).rgb;

Normal Mapping

Normal maps store tangent-space normals (blue-ish). Transform them to world/view space using the TBN matrix.

// Vertex shader — compute TBN
in vec3 aTangent;
in vec3 aBitangent;
out mat3 vTBN;

void main() {
    vec3 T = normalize(mat3(uModel) * aTangent);
    vec3 B = normalize(mat3(uModel) * aBitangent);
    vec3 N = normalize(mat3(uModel) * aNormal);
    vTBN = mat3(T, B, N);  // columns
}
// Fragment shader — decode and transform normal
vec3 n = texture(uNormalMap, vUV).rgb;
n = n * 2.0 - 1.0;         // [0,1] → [-1,1]
n = normalize(vTBN * n);   // tangent → world space

Computing tangents (CPU, for upload)

glm::vec3 edge1 = v1.pos - v0.pos;
glm::vec3 edge2 = v2.pos - v0.pos;
glm::vec2 duv1  = v1.uv  - v0.uv;
glm::vec2 duv2  = v2.uv  - v0.uv;

float f = 1.0f / (duv1.x * duv2.y - duv2.x * duv1.y);
glm::vec3 tangent = f * (duv2.y * edge1 - duv1.y * edge2);

PBR (Physically Based Rendering)

Based on the Cook-Torrance BRDF. Requires: albedo, metallic, roughness, AO.

// GGX / Trowbridge-Reitz NDF
float D_GGX(vec3 N, vec3 H, float roughness) {
    float a  = roughness * roughness;
    float a2 = a * a;
    float NdotH = max(dot(N, H), 0.0);
    float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
    return a2 / (3.14159265 * d * d);
}

// Schlick-GGX geometry function
float G_SchlickGGX(float NdotV, float roughness) {
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;
    return NdotV / (NdotV * (1.0 - k) + k);
}

float G_Smith(vec3 N, vec3 V, vec3 L, float roughness) {
    return G_SchlickGGX(max(dot(N,V),0.0), roughness)
         * G_SchlickGGX(max(dot(N,L),0.0), roughness);
}

// Fresnel-Schlick
vec3 F_Schlick(float cosTheta, vec3 F0) {
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

// Cook-Torrance specular BRDF
vec3 cookTorrance(vec3 N, vec3 V, vec3 L, vec3 albedo, float metallic, float roughness) {
    vec3 H   = normalize(V + L);
    vec3 F0  = mix(vec3(0.04), albedo, metallic); // 0.04 = non-metals
    vec3 F   = F_Schlick(max(dot(H, V), 0.0), F0);
    float D  = D_GGX(N, H, roughness);
    float G  = G_Smith(N, V, L, roughness);

    vec3  num   = D * G * F;
    float denom = 4.0 * max(dot(N,V),0.0) * max(dot(N,L),0.0) + 0.0001;
    vec3  spec  = num / denom;

    vec3 kD = (1.0 - F) * (1.0 - metallic); // metals have no diffuse
    return (kD * albedo / 3.14159265 + spec) * max(dot(N,L), 0.0);
}

Image Based Lighting (IBL)

// Diffuse IBL — irradiance cube map
uniform samplerCube uIrradianceMap;
vec3 irradiance = texture(uIrradianceMap, N).rgb;
vec3 diffuse    = irradiance * albedo;

// Specular IBL — prefiltered env map + BRDF LUT
uniform samplerCube uPrefilterMap;
uniform sampler2D   uBRDF_LUT;

vec3  prefilteredColor = textureLod(uPrefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;
vec2  brdf             = texture(uBRDF_LUT, vec2(max(dot(N,V),0.0), roughness)).rg;
vec3  specularIBL      = prefilteredColor * (F * brdf.x + brdf.y);

vec3 ambient = (diffuse + specularIBL) * ao;

Shadow Mapping

// 1. Render scene from light's POV into depth FBO
glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO);
glViewport(0, 0, SHADOW_W, SHADOW_H);
glClear(GL_DEPTH_BUFFER_BIT);
// Draw with depth-only shader (no fragment output)

// 2. Render scene normally
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, screenW, screenH);
glBindTextureUnit(1, shadowMap);
// Fragment shader — PCF soft shadows
uniform sampler2DShadow uShadowMap;
uniform mat4 uLightSpaceMatrix;

float calcShadow(vec3 fragPos, vec3 norm, vec3 lightDir) {
    vec4 lsPos   = uLightSpaceMatrix * vec4(fragPos, 1.0);
    vec3 projPos = lsPos.xyz / lsPos.w;
    projPos      = projPos * 0.5 + 0.5;  // [-1,1] → [0,1]

    // Bias to prevent shadow acne
    float bias = max(0.005 * (1.0 - dot(norm, lightDir)), 0.0005);
    projPos.z -= bias;

    // PCF — 3×3 kernel
    float shadow = 0.0;
    vec2 texelSize = 1.0 / textureSize(uShadowMap, 0);
    for (int x = -1; x <= 1; x++) {
        for (int y = -1; y <= 1; y++) {
            shadow += texture(uShadowMap, projPos + vec3(vec2(x,y)*texelSize, 0.0));
        }
    }
    return shadow / 9.0;
}

Shadow acne mitigation

TechniqueDescription
Depth biasSubtract small value from shadow depth
Normal offsetOffset sample along surface normal
Front-face cullingRender shadow map with front faces culled
glPolygonOffsetglPolygonOffset(1.0, 4.0) during shadow pass

Deferred Shading

// G-buffer outputs (MRT)
layout(location = 0) out vec3 gPosition;
layout(location = 1) out vec3 gNormal;
layout(location = 2) out vec4 gAlbedoSpec;  // rgb=albedo, a=spec intensity
// Lighting pass — iterate over all lights as full-screen quads or volumes
// Bind G-buffer textures, read and compute lighting per pixel

G-buffer formats (typical)

AttachmentFormatContent
Color 0GL_RGBA16FWorld position (xyz), can store depth in w
Color 1GL_RGBA16FNormal (xyz), roughness in w
Color 2GL_RGBA8Albedo (rgb), metallic in a
Color 3GL_RGBA8Emissive (rgb), AO in a
DepthGL_DEPTH24_STENCIL8Depth + stencil

Fog

// Exponential squared fog
float fogDensity = 0.05;
float dist       = length(vFragPos - uViewPos);
float fogFactor  = exp(-pow(fogDensity * dist, 2.0));
fogFactor        = clamp(fogFactor, 0.0, 1.0);
vec3 fogColor    = vec3(0.5, 0.6, 0.7);
FragColor = vec4(mix(fogColor, color, fogFactor), 1.0);