Skip to content

Lighting Models — Phong, Blinn-Phong and PBR

DodaTech Updated 2026-06-21 7 min read

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

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

What is the difference between a BRDF and a lighting model?

A BRDF is a mathematical function that describes how light reflects at a surface. A lighting model is a practical implementation of a BRDF. Phong is a lighting model; the Cook-Torrance BRDF is used in PBR.

Do I need gamma correction with PBR?

Yes, PBR calculations must be done in linear space. Textures should be converted from sRGB to linear, lighting computed in linear space, and the final output gamma-corrected for display.

What is image-based lighting (IBL)?

IBL uses an environment map (HDR panorama) as the light source instead of explicit lights. The environment is sampled to provide diffuse and specular lighting for realistic ambient illumination.

Can I mix Phong and PBR materials in one scene?

Yes, but they will not look consistent under the same lights because Phong does not conserve energy. For a uniform look, use one lighting model for all materials.


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