A equação geral para calcular a intensidade da luz em um ponto é:
I = I_ambient + I_diffuse + I_specular
Onde cada componente é calculado como:
I_ambient = k_a × I_light
// REFLEXÃO AMBIENTAL
float ambientStrength = 0.3; // k_a (coeficiente ambiental)
vec3 ambient = ambientStrength * uLightColor; // k_a × I_light- k_a (
ambientStrength): Coeficiente de reflexão ambiental [0.0, 1.0] - I_light (
uLightColor): Intensidade/cor da luz
- ✅ Constante para toda a superfície
- ✅ Independe da geometria
- ✅ Independe da posição da câmera
- ✅ Independe da posição da luz
I_diffuse = k_d × I_light × max(N · L, 0)
Onde:
- N = normal da superfície (normalizada)
- L = vetor direção da luz (normalizado)
- N · L = produto escalar = cos(θ)
- θ = ângulo entre N e L
// REFLEXÃO DIFUSA (Lambertiana)
vec3 lightDir = normalize(uLightPos - vFragPos); // L (direção da luz)
float diff = max(dot(norm, lightDir), 0.0); // max(N · L, 0)
vec3 diffuse = diff * uLightColor; // k_d × I_light × (N · L)
// Note: k_d = 1.0 (implícito, modulado pela cor base do objeto depois)- k_d: Coeficiente de reflexão difusa (implícito = 1.0, depois multiplicado pela cor do objeto)
- N (
norm): Normal da superfície normalizada - L (
lightDir): Direção da luz normalizada - I_light (
uLightColor): Intensidade/cor da luz
N · L = |N| × |L| × cos(θ) = cos(θ) (pois ambos são normalizados)
| Ângulo θ | cos(θ) | Iluminação |
|---|---|---|
| 0° (perpendicular) | 1.0 | 100% |
| 45° | 0.707 | 70.7% |
| 60° | 0.5 | 50% |
| 90° (paralelo) | 0.0 | 0% |
| >90° | <0.0 → 0.0 | 0% (max limita a 0) |
- ✅ Depende da orientação da superfície
- ✅ Depende da posição da luz
- ❌ Independe da posição da câmera
- ✅ Segue a Lei de Lambert (superfícies matte/foscas)
I_specular = k_s × I_light × max(R · V, 0)^n
Onde:
- R = vetor de reflexão da luz
- V = vetor direção da câmera (viewer)
- n = expoente de Phong (shininess)
- R · V = produto escalar = cos(α)
- α = ângulo entre R e V
R = 2(N · L)N - L
Ou usando a função built-in do GLSL: reflect(-L, N)
// REFLEXÃO ESPECULAR
float specularStrength = 0.5; // k_s
vec3 viewDir = normalize(uViewPos - vFragPos); // V (direção da câmera)
vec3 reflectDir = reflect(-lightDir, norm); // R (reflexão da luz)
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); // (R · V)^n
vec3 specular = specularStrength * spec * uLightColor; // k_s × I_light × (R·V)^n- k_s (
specularStrength): Coeficiente de reflexão especular [0.0, 1.0] - n (32.0): Expoente de Phong (shininess) [1, 256+]
- R (
reflectDir): Vetor de reflexão - V (
viewDir): Direção da câmera - I_light (
uLightColor): Intensidade/cor da luz
O expoente n controla o tamanho e intensidade do ponto brilhante:
| n | Material | Aparência |
|---|---|---|
| 1-5 | Superfície muito fosca | Brilho espalhado, quase difuso |
| 10-32 | Plástico | Brilho moderado |
| 32-64 | Plástico polido | Valor atual no código |
| 64-128 | Metal | Brilho concentrado |
| 128-256+ | Espelho | Brilho muito concentrado |
Intensidade
^
1.0 | n=256
| /\
0.8 | / \
| / \ n=32
0.6 | / \/\
| / / \
0.4 |/ n=8 / \
| / \___
0.2 | / \___
|______/________________\____> Ângulo de visão
0° 20° 40° 60° 90°
- ✅ Depende da orientação da superfície
- ✅ Depende da posição da luz
- ✅ Depende da posição da câmera (especular)
- ✅ Simula materiais brilhantes/polidos
- ✅ Concentrado em ângulos específicos
Vertex Shader passa para o Fragment Shader:
// 1. Normal transformada para espaço world
vNormal = mat3(uModelMatrix) * aNormal;
// 2. Posição do fragmento no espaço world
vFragPos = vec3(uModelMatrix * vec4(aPosition, 1.0));- Remove a componente de translação (só queremos rotação/escala para normais)
- Transforma vetores de direção corretamente
- Mais eficiente que usar mat4 para vetores
// Cor base do objeto (textura ou cor sólida)
vec4 baseColor = texture2D(uSampler, vTexCoord); // ou uSolidColor
// Componentes de Phong
vec3 ambient = k_a × I_light
vec3 diffuse = I_light × max(N · L, 0)
vec3 specular = k_s × I_light × [max(R · V, 0)]^n
// Resultado final
vec3 lighting = ambient + diffuse + specular
vec3 finalColor = lighting × baseColor.rgb
gl_FragColor = vec4(finalColor, baseColor.a); 👁️ Câmera (Eye/View)
↑
| V (viewDir)
|
Luz ☀️ | R (reflectDir)
↓ __|__ ↗
L ___/ | \___ /
___/ ↑ \_/
___/ N
___/ (normal)
████████████████████████████████ Superfície
// Normal (já vem do vertex shader, mas precisa normalizar após interpolação)
vec3 N = normalize(vNormal);
// Direção da Luz (do fragmento para a luz)
vec3 L = normalize(uLightPos - vFragPos);
// Direção da Câmera (do fragmento para a câmera)
vec3 V = normalize(uViewPos - vFragPos);
// Reflexão da Luz (refletida pela normal)
vec3 R = reflect(-L, N);
// Equivalente a: R = 2.0 * dot(N, L) * N - L| Parâmetro | Símbolo | Valor | Efeito |
|---|---|---|---|
| Coef. Ambiental | k_a | 0.3 | 30% de iluminação mínima |
| Coef. Difuso | k_d | 1.0 (implícito) | 100% de reflexão difusa |
| Coef. Especular | k_s | 0.5 | 50% de intensidade especular |
| Expoente Phong | n | 32.0 | Material tipo plástico polido |
| Cor da Luz | I_light | (1, 1, 1) | Luz branca |
Suponha:
- N · L = 0.8 (luz incidente a ~37°)
- R · V = 0.6 (reflexão a ~53° da câmera)
- Cor base = (0.8, 0.2, 0.2) - vermelho
Ambient = 0.3 × (1, 1, 1) = (0.3, 0.3, 0.3)
Diffuse = (1, 1, 1) × 0.8 = (0.8, 0.8, 0.8)
Specular = 0.5 × (1, 1, 1) × (0.6)^32 = 0.5 × 0.00007 ≈ (0.00003, 0.00003, 0.00003)
Lighting = (0.3 + 0.8 + 0.00003, ...) ≈ (1.1, 1.1, 1.1)
Final = (1.1, 1.1, 1.1) × (0.8, 0.2, 0.2) = (0.88, 0.22, 0.22) - vermelho iluminado
Se você quisesse renderizar cada componente separadamente:
// Apenas Ambiental (iluminação uniforme)
gl_FragColor = vec4(ambient * baseColor.rgb, 1.0);
// Apenas Difuso (shading suave)
gl_FragColor = vec4(diffuse * baseColor.rgb, 1.0);
// Apenas Especular (pontos brilhantes)
gl_FragColor = vec4(specular, 1.0); // geralmente branco
// Completo (Phong)
gl_FragColor = vec4((ambient + diffuse + specular) * baseColor.rgb, 1.0);-
Lei de Lambert (Reflexão Difusa)
- Intensidade ∝ cos(θ) = N · L
- Superfícies perfeitamente difusas (matte)
-
Modelo de Phong (1975)
- Bui Tuong Phong
- Aproximação empírica de reflexão especular
- Simples e eficiente
-
Alternativas Mais Avançadas
- Blinn-Phong: usa halfway vector (mais eficiente)
- Cook-Torrance: fisicamente baseado (PBR)
- Oren-Nayar: superfícies rugosas
- Normalização é cara: minimize chamadas de
normalize() - Pow é caro: considere usar Blinn-Phong para expoentes menores
- Calcule por vértice quando possível: se a geometria for detalhada suficiente
- Use half-float:
mediumpem vez dehighpse precisão for OK
- ✅ Normais definidas na geometria
- ✅ Normais transformadas para espaço world
- ✅ Posição dos fragmentos no espaço world
- ✅ Posição da luz definida
- ✅ Posição da câmera enviada ao shader
- ✅ Componente ambiental implementado
- ✅ Componente difuso implementado
- ✅ Componente especular implementado
- ✅ Combinação final correta
- ✅ Todos os vetores normalizados