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)
| Range | Constant | Linear | Quadratic |
|---|---|---|---|
| 7 | 1.0 | 0.7 | 1.8 |
| 13 | 1.0 | 0.35 | 0.44 |
| 20 | 1.0 | 0.22 | 0.20 |
| 32 | 1.0 | 0.14 | 0.07 |
| 50 | 1.0 | 0.09 | 0.032 |
| 100 | 1.0 | 0.045 | 0.0075 |
| 200 | 1.0 | 0.022 | 0.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
| Technique | Description |
|---|---|
| Depth bias | Subtract small value from shadow depth |
| Normal offset | Offset sample along surface normal |
| Front-face culling | Render shadow map with front faces culled |
glPolygonOffset | glPolygonOffset(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)
| Attachment | Format | Content |
|---|---|---|
| Color 0 | GL_RGBA16F | World position (xyz), can store depth in w |
| Color 1 | GL_RGBA16F | Normal (xyz), roughness in w |
| Color 2 | GL_RGBA8 | Albedo (rgb), metallic in a |
| Color 3 | GL_RGBA8 | Emissive (rgb), AO in a |
| Depth | GL_DEPTH24_STENCIL8 | Depth + 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);