Lighting Models — Phong, Blinn-Phong and PBR
In this tutorial, you'll learn about Lighting Models. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Lighting models compute how light interacts with surfaces by simulating the physics of light Reflection, from simple empirical models like Phong to physically-based rendering that obeys energy conservation.
What You'll Learn & Why It Matters
In this tutorial, you will learn the evolution of lighting models — from the Phong Reflection model to Blinn-Phong and finally physically-based rendering (PBR). You will implement each model in GLSL and understand the trade-offs.
Real-world use: PBR is the standard for modern games and film. Understanding lighting models helps you debug rendering issues, optimize shaders, and create realistic materials.
Prerequisites
- Shader Programming (previous)
- Texture Mapping (previous)
- Vector math (dot, reflect, normalize)
Learning Path
flowchart LR A[Shader Programming] --> B[Lighting Models] B --> C[BRDF and Materials] B --> D[Texture Mapping] B --> E[Real-Time GI] C --> F[Path Tracing] B:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
The Phong Reflection Model
Phong breaks lighting into three components: ambient (constant base light), diffuse (scattered light from rough surfaces), and specular (mirror-like highlights).
#version 330 core
in vec3 FragPos;
in vec3 Normal;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
uniform vec3 viewPos;
void main()
{
// Ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// Diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// Specular (Phong)
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
Expected output: A colored sphere with a soft ambient glow, bright diffuse lighting on the sun-facing side, and a small bright specular highlight where the camera aligns with the reflected light.
Blinn-Phong Model
Blinn-Phong replaces the Reflection vector calculation with the half-vector, which is cheaper and produces similar results:
// Blinn-Phong specular
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfDir), 0.0), 64);
The half-vector is the vector halfway between the light direction and view direction. When the half-vector aligns with the surface normal, specular Reflection is strongest.
Shiny vs Rough Surfaces
// Low shininess (rough surface) - large, dim highlight
float roughSpec = pow(NdotH, 8.0);
// High shininess (smooth surface) - small, bright highlight
float smoothSpec = pow(NdotH, 256.0);
import matplotlib.pyplot as plt
import numpy as np
def phong_specular(ndoth, shininess):
return np.power(np.clip(ndoth, 0, 1), shininess)
angles = np.linspace(0, 1, 100)
for s in [2, 8, 32, 128, 512]:
values = phong_specular(angles, s)
print(f"Shininess {s}: peak at {values[50]:.3f}, half-width at half-max")
| Shininess | Appearance | Example Material |
|---|---|---|
| 2-8 | Dull, broad highlight | Cloth, paper |
| 16-64 | Moderate highlight | Plastic, skin |
| 128-512 | Sharp highlight | Metal, glass |
| 1024+ | Mirror-like | Polished chrome |
flowchart TD A[Light Source] -->|Light Direction L| B[Surface Point] C[Camera] -->|View Direction V| B B -->|Normal N| B B -->|Reflection R = reflect(-L, N)| D[Phong: dot(V, R)] B -->|Half H = normalize(L + V)| E[Blinn-Phong: dot(N, H)] D -->|Power| F[Specular Intensity] E -->|Power| F
Multiple Lights
struct Light {
vec3 position;
vec3 color;
float intensity;
};
uniform Light lights[4];
void main()
{
vec3 result = vec3(0.0);
for (int i = 0; i < 4; i++)
{
vec3 lightDir = normalize(lights[i].position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
float distance = length(lights[i].position - FragPos);
float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
result += diff * lights[i].color * lights[i].intensity * attenuation;
}
FragColor = vec4(result * objectColor, 1.0);
}
Physically-Based Rendering (PBR)
PBR respects energy conservation: a surface cannot reflect more light than it receives. Key properties:
- Albedo: Base color (no lighting information)
- Metalness: How metallic the surface is (metals reflect, dielectrics scatter)
- Roughness: Micro-surface variation (rough = blurry reflections)
- Ambient Occlusion: Self-shadowing in crevices
// Simplified PBR fragment
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
void main()
{
vec3 F0 = mix(vec3(0.04), albedo, metallic);
float NdotV = max(dot(N, V), 0.001);
float NdotL = max(dot(N, L), 0.001);
vec3 H = normalize(V + L);
float NdotH = max(dot(N, H), 0.0);
// Fresnel (Schlick approximation)
vec3 F = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0);
// Normal Distribution Function (GGX)
float a = roughness * roughness;
float a2 = a * a;
float NdotH2 = NdotH * NdotH;
float D = a2 / (3.14159 * (NdotH2 * (a2 - 1.0) + 1.0) * (NdotH2 * (a2 - 1.0) + 1.0));
// Geometry function (Smith)
float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
float G1 = NdotV / (NdotV * (1.0 - k) + k);
float G2 = NdotL / (NdotL * (1.0 - k) + k);
float G = G1 * G2;
vec3 specular = (F * D * G) / (4.0 * NdotV * NdotL);
vec3 diffuse = (1.0 - F) * (1.0 - metallic) * albedo / 3.14159;
vec3 color = (diffuse + specular) * NdotL * lightColor;
FragColor = vec4(color, 1.0);
}
Common Errors & Mistakes
1. Negative Dot Products
Mistake: Not clamping dot products to 0, causing lights on the back of surfaces to contribute.
Fix: Always use max(dot(N, L), 0.0) for diffuse and specular terms.
2. Specular Highlight on Dark Side
Mistake: Specular highlights appearing on the dark side of a sphere because dot(N, reflectDir) is positive even when dot(N, L) is negative.
Fix: Multiply specular by step(0.0, dot(N, L)) or use the Blinn-Phong half-vector method.
3. Energy Gain in PBR
Mistake: Specular + diffuse contributions exceed 1.0, violating energy conservation.
Fix: The Fresnel term F splits energy between diffuse and specular. Ensure diffuse + specular <= 1.0 at each wavelength.
4. Gamma Correction
Mistake: Rendering in linear space without gamma correction, producing dark images with saturated highlights.
Fix: Apply gamma correction at the end: FragColor = vec4(pow(color, 1.0 / 2.2), 1.0).
Practice Questions
Question 1
What is the difference between Phong and Blinn-Phong specular?
Show answer
Phong uses `dot(viewDir, reflect(-lightDir, normal))`. Blinn-Phong uses `dot(normal, normalize(lightDir + viewDir))`. Blinn-Phong is cheaper (no reflect function) and produces slightly wider highlights.Question 2
Why does PBR use roughness and metalness?
Show answer
Roughness controls micro-surface detail (how blurry reflections are). Metalness distinguishes metals (conductors that reflect with colored tint) from dielectrics (insulators that reflect white) for energy-conserving rendering.Question 3
What is the Fresnel effect?
Show answer
Fresnel describes how reflectivity increases at grazing angles. A surface seen edge-on reflects more light than seen head-on. This is why water looks mirror-like at shallow angles but transparent when viewed straight down.Question 4
What is ambient occlusion?
Show answer
Ambient occlusion approximates shadowing in crevices where ambient light cannot reach. It darkens corners and gaps, adding depth perception without expensive global illumination calculations.Challenge
Implement a PBR material viewer in OpenGL with an environment map for image-based lighting (IBL). Support loading HDR environment maps and computing diffuse irradiance and specular pre-filtered maps.
FAQ
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