Skip to content

第9章:光照与着色模型

9.1 光照物理

9.1.1 光的本质

在计算机图形学中,我们需要模拟光与物体的交互来创建真实感图像。理解光的物理特性是基础。

光的基本概念

光是电磁波,可见光波长范围:380nm - 780nm

波长与颜色的对应:
┌─────────────────────────────────────────────────────┐
│  380nm   450nm   490nm   560nm   590nm   620nm  780nm │
│   紫      蓝      青      绿      黄      橙     红    │
│   ■       ■       ■       ■       ■       ■      ■     │
└─────────────────────────────────────────────────────┘


辐射度量学单位:

| 物理量 | 单位 | 说明 |
|--------|------|------|
| 辐射通量 Φ | W(瓦特)| 单位时间的能量 |
| 辐射强度 I | W/sr | 每立体角的通量 |
| 辐照度 E | W/m² | 入射到表面的通量密度 |
| 辐射出射度 M | W/m² | 从表面发出的通量密度 |
| 辐射率 L | W/(m²·sr) | 每单位面积、单位立体角的通量 |


立体角(Solid Angle):

        ╱╲
       ╱  ╲
      ╱    ╲
     ╱  Ω   ╲    ← 立体角
    ╱________╲
          r

Ω = A / r²(单位:sr,球面度)
整个球面的立体角 = 4π sr

9.1.2 光与表面的交互

光与表面的交互方式

入射光到达表面后:

        入射光



    ──────────●──────────  表面
             ╱│╲
            ╱ │ ╲
           ╱  │  ╲
        反射  │  折射

            吸收


三种主要现象:

1. 反射(Reflection)
   - 镜面反射:入射角 = 反射角
   - 漫反射:向各方向均匀散射

2. 折射(Refraction)
   - 光进入不同介质时改变方向
   - 斯涅尔定律:n₁sin(θ₁) = n₂sin(θ₂)

3. 吸收(Absorption)
   - 部分光能被材质吸收转化为热
   - 决定物体的颜色


能量守恒:
反射光 + 折射光 + 吸收能量 = 入射光

9.1.3 反射类型

反射的分类

1. 理想镜面反射(Specular)
   
   入射    法线    反射
     ╲      │     ╱
      ╲     │    ╱
       ╲   │   ╱
        ╲  │  ╱
         ╲ │ ╱
   ────────┴────────
   
   反射方向:R = 2(N·L)N - L
   

2. 理想漫反射(Diffuse / Lambertian)

              出射光
           ╱  │  ╲
          ╱   │   ╲
         ╱    │    ╲
   ──────────────────────
         入射光
   
   向所有方向均匀反射
   

3. 光泽反射(Glossy)

         ╱╲
        ╱  ╲
       ╱    ╲
   ─────────────────
   
   介于镜面和漫反射之间
   有一定的模糊


实际材质通常是多种反射的组合:
- 塑料:漫反射 + 镜面高光
- 金属:强镜面反射 + 颜色化高光
- 皮肤:次表面散射 + 表面反射

9.2 局部光照模型

9.2.1 局部与全局光照

局部光照 vs 全局光照

局部光照(Local Illumination):
- 只考虑光源直接照射
- 不考虑物体之间的光反射
- 计算简单、速度快
- 例如:Phong、Blinn-Phong

全局光照(Global Illumination):
- 考虑所有光的传播(直接 + 间接)
- 物体之间会互相照亮
- 计算复杂、更真实
- 例如:光线追踪、光子映射、路径追踪


场景对比:

局部光照:                    全局光照:
┌────────────────┐            ┌────────────────┐
│     ●          │            │     ●          │
│    光源        │            │    光源        │
│      ╲         │            │      ╲ ╱       │
│       ╲        │            │       ●────────┤← 间接光
│    ────────    │            │    ────────    │
│    阴影?      │            │    软阴影      │
│    不处理      │            │    颜色溢出    │
└────────────────┘            └────────────────┘

9.2.2 光源类型

常见光源类型

1. 平行光(Directional Light)
   - 无限远,光线平行
   - 模拟太阳
   - 参数:方向、颜色
   
       │ │ │ │ │
       ▼ ▼ ▼ ▼ ▼


2. 点光源(Point Light)
   - 从一点向四周发射
   - 有衰减
   - 参数:位置、颜色、衰减
   
           ╱│╲
          ╱ │ ╲
         ╱  ●  ╲
        ╱   │   ╲
       ╱    │    ╲


3. 聚光灯(Spot Light)
   - 锥形光照
   - 有衰减和角度
   - 参数:位置、方向、角度、颜色
   

          ╱ ╲
         ╱   ╲
        ╱_____╲


4. 面光源(Area Light)
   - 有面积的光源
   - 产生软阴影
   - 计算复杂
   
       ┌──────┐
       │      │
       └──────┘

9.3 Phong 模型

9.3.1 Phong 光照模型

Phong 模型

Phong 模型将光照分为三个分量:

总光照 = 环境光 + 漫反射 + 镜面反射

I = Iₐ + Id + Is


1. 环境光(Ambient)
   Iₐ = kₐ × Lₐ
   
   - kₐ:材质的环境光系数
   - Lₐ:环境光颜色
   - 模拟间接光照的简化


2. 漫反射(Diffuse)
   Id = kd × Ld × max(0, N·L)
   
        N

        │ θ
   ─────┼─────

      L
   
   - kd:材质的漫反射系数
   - Ld:光源颜色
   - N:表面法向量
   - L:光源方向
   - cos(θ) = N·L


3. 镜面反射(Specular)
   Is = ks × Ls × max(0, R·V)ⁿ
   
        N     R
        │    ╱
        │   ╱
   ─────┼──╱──────
       ╱ │
      L  │
         V(视线)
   
   - ks:材质的镜面反射系数
   - Ls:光源颜色
   - R:反射方向
   - V:视线方向
   - n:光泽度(越大高光越锐利)


反射向量计算:
R = 2(N·L)N - L

9.3.2 Phong 模型实现

javascript
/**
 * Phong 光照计算
 */
class PhongLighting {
    /**
     * 计算 Phong 光照
     * @param normal 法向量(归一化)
     * @param lightDir 指向光源的方向(归一化)
     * @param viewDir 指向相机的方向(归一化)
     * @param material 材质参数
     * @param lightColor 光源颜色
     */
    static calculate(normal, lightDir, viewDir, material, lightColor) {
        // 环境光
        const ambient = {
            r: material.ambient.r * lightColor.r,
            g: material.ambient.g * lightColor.g,
            b: material.ambient.b * lightColor.b
        };
        
        // 漫反射
        const NdotL = Math.max(0, this.dot(normal, lightDir));
        const diffuse = {
            r: material.diffuse.r * lightColor.r * NdotL,
            g: material.diffuse.g * lightColor.g * NdotL,
            b: material.diffuse.b * lightColor.b * NdotL
        };
        
        // 计算反射向量
        const reflect = this.reflect(lightDir, normal);
        
        // 镜面反射
        const RdotV = Math.max(0, this.dot(reflect, viewDir));
        const specFactor = Math.pow(RdotV, material.shininess);
        const specular = {
            r: material.specular.r * lightColor.r * specFactor,
            g: material.specular.g * lightColor.g * specFactor,
            b: material.specular.b * lightColor.b * specFactor
        };
        
        // 合并
        return {
            r: Math.min(1, ambient.r + diffuse.r + specular.r),
            g: Math.min(1, ambient.g + diffuse.g + specular.g),
            b: Math.min(1, ambient.b + diffuse.b + specular.b)
        };
    }
    
    /**
     * 计算反射向量
     * R = 2(N·L)N - L
     */
    static reflect(lightDir, normal) {
        const NdotL = this.dot(normal, lightDir);
        return {
            x: 2 * NdotL * normal.x - lightDir.x,
            y: 2 * NdotL * normal.y - lightDir.y,
            z: 2 * NdotL * normal.z - lightDir.z
        };
    }
    
    static dot(a, b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }
}

// 材质示例
const plasticMaterial = {
    ambient: { r: 0.1, g: 0.1, b: 0.1 },
    diffuse: { r: 0.6, g: 0.0, b: 0.0 },
    specular: { r: 1.0, g: 1.0, b: 1.0 },
    shininess: 32
};

// GLSL 版本
const phongVertexShader = `
attribute vec3 aPosition;
attribute vec3 aNormal;

uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat3 uNormalMatrix;

varying vec3 vNormal;
varying vec3 vPosition;

void main() {
    vec4 worldPosition = uModelMatrix * vec4(aPosition, 1.0);
    vPosition = worldPosition.xyz;
    vNormal = normalize(uNormalMatrix * aNormal);
    
    gl_Position = uProjectionMatrix * uViewMatrix * worldPosition;
}
`;

const phongFragmentShader = `
precision mediump float;

uniform vec3 uLightPosition;
uniform vec3 uViewPosition;
uniform vec3 uLightColor;

uniform vec3 uAmbient;
uniform vec3 uDiffuse;
uniform vec3 uSpecular;
uniform float uShininess;

varying vec3 vNormal;
varying vec3 vPosition;

void main() {
    vec3 normal = normalize(vNormal);
    vec3 lightDir = normalize(uLightPosition - vPosition);
    vec3 viewDir = normalize(uViewPosition - vPosition);
    
    // 环境光
    vec3 ambient = uAmbient * uLightColor;
    
    // 漫反射
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = uDiffuse * uLightColor * diff;
    
    // 镜面反射
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), uShininess);
    vec3 specular = uSpecular * uLightColor * spec;
    
    vec3 result = ambient + diffuse + specular;
    gl_FragColor = vec4(result, 1.0);
}
`;

9.4 Blinn-Phong 模型

9.4.1 半角向量

Blinn-Phong 改进

Phong 模型的问题:
- 当视角与反射方向夹角 > 90° 时,高光突然消失
- 计算反射向量开销较大

Blinn-Phong 解决方案:使用半角向量(Half Vector)

          N

    H ────┼──── 
         ╱│╲
        ╱ │ ╲
       L  │  V


半角向量:
H = normalize(L + V)

镜面反射改为:
Is = ks × Ls × max(0, N·H)ⁿ


优点:
1. 计算更简单(不需要反射向量)
2. 高光更平滑
3. 在大角度时表现更好


对比:

Phong 高光:              Blinn-Phong 高光:
    ╭───╮                     ╭───────╮
   ╱     ╲                   ╱         ╲
  ╱       ╲                 ╱           ╲
 │         │               │             │

Phong 高光更锐利          Blinn-Phong 更柔和

9.4.2 Blinn-Phong 实现

javascript
/**
 * Blinn-Phong 光照计算
 */
class BlinnPhongLighting {
    static calculate(normal, lightDir, viewDir, material, lightColor) {
        // 环境光
        const ambient = {
            r: material.ambient.r * lightColor.r,
            g: material.ambient.g * lightColor.g,
            b: material.ambient.b * lightColor.b
        };
        
        // 漫反射
        const NdotL = Math.max(0, this.dot(normal, lightDir));
        const diffuse = {
            r: material.diffuse.r * lightColor.r * NdotL,
            g: material.diffuse.g * lightColor.g * NdotL,
            b: material.diffuse.b * lightColor.b * NdotL
        };
        
        // 计算半角向量
        const halfVector = this.normalize({
            x: lightDir.x + viewDir.x,
            y: lightDir.y + viewDir.y,
            z: lightDir.z + viewDir.z
        });
        
        // 镜面反射(使用半角向量)
        const NdotH = Math.max(0, this.dot(normal, halfVector));
        const specFactor = Math.pow(NdotH, material.shininess);
        const specular = {
            r: material.specular.r * lightColor.r * specFactor,
            g: material.specular.g * lightColor.g * specFactor,
            b: material.specular.b * lightColor.b * specFactor
        };
        
        return {
            r: Math.min(1, ambient.r + diffuse.r + specular.r),
            g: Math.min(1, ambient.g + diffuse.g + specular.g),
            b: Math.min(1, ambient.b + diffuse.b + specular.b)
        };
    }
    
    static dot(a, b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    
    static normalize(v) {
        const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        return { x: v.x / len, y: v.y / len, z: v.z / len };
    }
}

// GLSL 片段着色器
const blinnPhongFragmentShader = `
precision mediump float;

uniform vec3 uLightPosition;
uniform vec3 uViewPosition;
uniform vec3 uLightColor;
uniform vec3 uAmbient;
uniform vec3 uDiffuse;
uniform vec3 uSpecular;
uniform float uShininess;

varying vec3 vNormal;
varying vec3 vPosition;

void main() {
    vec3 normal = normalize(vNormal);
    vec3 lightDir = normalize(uLightPosition - vPosition);
    vec3 viewDir = normalize(uViewPosition - vPosition);
    
    // 半角向量
    vec3 halfDir = normalize(lightDir + viewDir);
    
    // 环境光
    vec3 ambient = uAmbient * uLightColor;
    
    // 漫反射
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = uDiffuse * uLightColor * diff;
    
    // 镜面反射(Blinn-Phong)
    float spec = pow(max(dot(normal, halfDir), 0.0), uShininess);
    vec3 specular = uSpecular * uLightColor * spec;
    
    vec3 result = ambient + diffuse + specular;
    gl_FragColor = vec4(result, 1.0);
}
`;

9.5 BRDF

9.5.1 BRDF 的概念

BRDF(双向反射分布函数)

BRDF 描述了光在物体表面的反射特性。

定义:
fr(ωi, ωo) = dLo(ωo) / (Li(ωi) × cos(θi) × dωi)

其中:
- ωi:入射方向
- ωo:出射方向
- Lo:出射辐射率
- Li:入射辐射率
- θi:入射角


图示:

         Lo(ωo)


    ─────╱──────

       ╱ Li(ωi)

BRDF 回答:从 ωi 方向入射的光,有多少从 ωo 方向反射出去?


BRDF 的性质:

1. 非负性
   fr(ωi, ωo) ≥ 0

2. 亥姆霍兹互易性
   fr(ωi, ωo) = fr(ωo, ωi)

3. 能量守恒
   ∫ fr(ωi, ωo) × cos(θo) dωo ≤ 1

9.5.2 常见 BRDF 模型

Lambertian BRDF(理想漫反射)

fr = kd / π

- 各向同性
- 最简单的 BRDF
- 能量守恒:∫(kd/π)cos(θ)dω = kd


Phong BRDF

fr = kd/π + ks × (n+2)/(2π) × cos^n(θr)

- kd:漫反射系数
- ks:镜面反射系数
- n:光泽度
- θr:反射角


Cook-Torrance BRDF

用于物理渲染(PBR):

fr = kd × fLambert + ks × DGF / (4×cosθi×cosθo)

其中:
- D:法线分布函数(NDF)
- G:几何遮蔽函数
- F:菲涅尔方程

9.5.3 PBR 材质

javascript
/**
 * PBR(Cook-Torrance)BRDF
 */
class PBRBRDF {
    /**
     * 法线分布函数(GGX/Trowbridge-Reitz)
     */
    static D_GGX(NdotH, roughness) {
        const a = roughness * roughness;
        const a2 = a * a;
        const NdotH2 = NdotH * NdotH;
        
        const nom = a2;
        const denom = NdotH2 * (a2 - 1) + 1;
        
        return nom / (Math.PI * denom * denom);
    }
    
    /**
     * 几何遮蔽函数(Schlick-GGX)
     */
    static G_SchlickGGX(NdotV, roughness) {
        const r = roughness + 1;
        const k = (r * r) / 8;
        
        const nom = NdotV;
        const denom = NdotV * (1 - k) + k;
        
        return nom / denom;
    }
    
    /**
     * Smith 几何函数
     */
    static G_Smith(NdotV, NdotL, roughness) {
        const ggx1 = this.G_SchlickGGX(NdotV, roughness);
        const ggx2 = this.G_SchlickGGX(NdotL, roughness);
        return ggx1 * ggx2;
    }
    
    /**
     * 菲涅尔方程(Schlick 近似)
     */
    static F_Schlick(cosTheta, F0) {
        const pow5 = Math.pow(1 - cosTheta, 5);
        return {
            r: F0.r + (1 - F0.r) * pow5,
            g: F0.g + (1 - F0.g) * pow5,
            b: F0.b + (1 - F0.b) * pow5
        };
    }
    
    /**
     * 计算 PBR BRDF
     */
    static calculate(normal, lightDir, viewDir, material) {
        const halfDir = this.normalize({
            x: lightDir.x + viewDir.x,
            y: lightDir.y + viewDir.y,
            z: lightDir.z + viewDir.z
        });
        
        const NdotL = Math.max(0.001, this.dot(normal, lightDir));
        const NdotV = Math.max(0.001, this.dot(normal, viewDir));
        const NdotH = Math.max(0.001, this.dot(normal, halfDir));
        const HdotV = Math.max(0.001, this.dot(halfDir, viewDir));
        
        // F0:非金属约 0.04,金属使用 albedo
        const F0 = material.metallic > 0.5 ? material.albedo : 
                   { r: 0.04, g: 0.04, b: 0.04 };
        
        // Cook-Torrance 镜面 BRDF
        const D = this.D_GGX(NdotH, material.roughness);
        const G = this.G_Smith(NdotV, NdotL, material.roughness);
        const F = this.F_Schlick(HdotV, F0);
        
        const denom = 4 * NdotV * NdotL;
        
        const specular = {
            r: (D * G * F.r) / denom,
            g: (D * G * F.g) / denom,
            b: (D * G * F.b) / denom
        };
        
        // 漫反射(金属无漫反射)
        const kS = F;
        const kD = {
            r: (1 - kS.r) * (1 - material.metallic),
            g: (1 - kS.g) * (1 - material.metallic),
            b: (1 - kS.b) * (1 - material.metallic)
        };
        
        const diffuse = {
            r: kD.r * material.albedo.r / Math.PI,
            g: kD.g * material.albedo.g / Math.PI,
            b: kD.b * material.albedo.b / Math.PI
        };
        
        return {
            r: (diffuse.r + specular.r) * NdotL,
            g: (diffuse.g + specular.g) * NdotL,
            b: (diffuse.b + specular.b) * NdotL
        };
    }
    
    static dot(a, b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    
    static normalize(v) {
        const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        return { x: v.x / len, y: v.y / len, z: v.z / len };
    }
}

9.6 全局光照

9.6.1 渲染方程

渲染方程(Rendering Equation)

Kajiya 1986 年提出的光照传输统一公式:

Lo(p, ωo) = Le(p, ωo) + ∫ fr(p, ωi, ωo) × Li(p, ωi) × cos(θi) dωi


各项含义:
- Lo:出射辐射率
- Le:自发光
- fr:BRDF
- Li:入射辐射率
- cos(θi):朗伯余弦项
- 积分:对半球所有入射方向


图示:

                Lo(ωo)


    ∫ fr × Li dωi      ← 对半球积分
         ╱│╲
        ╱ │ ╲
   ────●──────────────
       p


这是递归方程:Li 本身可能是其他表面的 Lo
全局光照算法的目标就是求解这个方程

9.6.2 常见全局光照技术

全局光照技术

1. 光线追踪(Ray Tracing)
   - 从相机发射光线
   - 递归跟踪反射/折射
   - 适合镜面材质

2. 路径追踪(Path Tracing)
   - 随机采样光路
   - 无偏估计渲染方程
   - 收敛慢但结果准确

3. 光子映射(Photon Mapping)
   - 两步法:发射光子 + 收集
   - 适合焦散效果

4. 辐射度(Radiosity)
   - 预计算漫反射光传输
   - 适合建筑可视化

5. 实时技术:
   - 屏幕空间反射(SSR)
   - 屏幕空间环境光遮蔽(SSAO)
   - 光照探针(Light Probes)
   - 反射探针(Reflection Probes)

9.6.3 简单光线追踪

javascript
/**
 * 简单光线追踪器
 */
class SimpleRayTracer {
    constructor(width, height) {
        this.width = width;
        this.height = height;
        this.scene = {
            spheres: [],
            lights: [],
            camera: null
        };
        this.maxDepth = 5;
    }
    
    /**
     * 追踪光线
     */
    trace(ray, depth = 0) {
        if (depth > this.maxDepth) {
            return { r: 0, g: 0, b: 0 };
        }
        
        // 找最近的交点
        let closest = null;
        let minDist = Infinity;
        
        for (const sphere of this.scene.spheres) {
            const t = this.intersectSphere(ray, sphere);
            if (t > 0 && t < minDist) {
                minDist = t;
                closest = sphere;
            }
        }
        
        if (!closest) {
            return this.backgroundColor();
        }
        
        // 计算交点
        const hitPoint = {
            x: ray.origin.x + ray.direction.x * minDist,
            y: ray.origin.y + ray.direction.y * minDist,
            z: ray.origin.z + ray.direction.z * minDist
        };
        
        // 计算法向量
        const normal = this.normalize({
            x: hitPoint.x - closest.center.x,
            y: hitPoint.y - closest.center.y,
            z: hitPoint.z - closest.center.z
        });
        
        // 局部光照
        let color = this.computeLocalLighting(hitPoint, normal, closest.material);
        
        // 反射
        if (closest.material.reflectivity > 0) {
            const reflectDir = this.reflect(ray.direction, normal);
            const reflectRay = {
                origin: this.offsetPoint(hitPoint, normal),
                direction: reflectDir
            };
            
            const reflectColor = this.trace(reflectRay, depth + 1);
            
            color = {
                r: color.r * (1 - closest.material.reflectivity) + 
                   reflectColor.r * closest.material.reflectivity,
                g: color.g * (1 - closest.material.reflectivity) + 
                   reflectColor.g * closest.material.reflectivity,
                b: color.b * (1 - closest.material.reflectivity) + 
                   reflectColor.b * closest.material.reflectivity
            };
        }
        
        return color;
    }
    
    /**
     * 计算局部光照
     */
    computeLocalLighting(point, normal, material) {
        let color = { r: 0, g: 0, b: 0 };
        
        for (const light of this.scene.lights) {
            const lightDir = this.normalize({
                x: light.position.x - point.x,
                y: light.position.y - point.y,
                z: light.position.z - point.z
            });
            
            // 阴影测试
            const shadowRay = {
                origin: this.offsetPoint(point, normal),
                direction: lightDir
            };
            
            if (this.isInShadow(shadowRay, light)) {
                continue;
            }
            
            // Phong 光照
            const NdotL = Math.max(0, this.dot(normal, lightDir));
            
            color.r += material.color.r * light.color.r * NdotL;
            color.g += material.color.g * light.color.g * NdotL;
            color.b += material.color.b * light.color.b * NdotL;
        }
        
        // 环境光
        color.r += material.color.r * 0.1;
        color.g += material.color.g * 0.1;
        color.b += material.color.b * 0.1;
        
        return {
            r: Math.min(1, color.r),
            g: Math.min(1, color.g),
            b: Math.min(1, color.b)
        };
    }
    
    /**
     * 渲染图像
     */
    render() {
        const pixels = new Uint8ClampedArray(this.width * this.height * 4);
        const camera = this.scene.camera;
        
        for (let y = 0; y < this.height; y++) {
            for (let x = 0; x < this.width; x++) {
                // 生成主光线
                const ray = this.generateRay(x, y, camera);
                
                // 追踪光线
                const color = this.trace(ray);
                
                // 写入像素
                const i = (y * this.width + x) * 4;
                pixels[i] = color.r * 255;
                pixels[i + 1] = color.g * 255;
                pixels[i + 2] = color.b * 255;
                pixels[i + 3] = 255;
            }
        }
        
        return new ImageData(pixels, this.width, this.height);
    }
    
    // 辅助方法...
    intersectSphere(ray, sphere) {
        const oc = {
            x: ray.origin.x - sphere.center.x,
            y: ray.origin.y - sphere.center.y,
            z: ray.origin.z - sphere.center.z
        };
        
        const a = this.dot(ray.direction, ray.direction);
        const b = 2 * this.dot(oc, ray.direction);
        const c = this.dot(oc, oc) - sphere.radius * sphere.radius;
        const discriminant = b * b - 4 * a * c;
        
        if (discriminant < 0) return -1;
        
        return (-b - Math.sqrt(discriminant)) / (2 * a);
    }
    
    reflect(dir, normal) {
        const d = 2 * this.dot(dir, normal);
        return {
            x: dir.x - d * normal.x,
            y: dir.y - d * normal.y,
            z: dir.z - d * normal.z
        };
    }
    
    dot(a, b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    
    normalize(v) {
        const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        return { x: v.x / len, y: v.y / len, z: v.z / len };
    }
}

9.7 本章小结

光照模型对比

模型复杂度真实感应用
Phong简单场景
Blinn-Phong实时渲染
Cook-TorrancePBR
光线追踪离线渲染
路径追踪很高最高电影级

关键公式

Phong 模型:
I = Ia + Id + Is
  = ka×La + kd×Ld×(N·L) + ks×Ls×(R·V)^n

Blinn-Phong:
Is = ks×Ls×(N·H)^n
H = normalize(L + V)

渲染方程:
Lo = Le + ∫ fr×Li×cosθ dω

关键要点

  1. 局部光照只考虑直接光照,计算简单
  2. Blinn-Phong 是 Phong 的改进,使用半角向量
  3. BRDF 描述材质的反射特性
  4. PBR 使用物理正确的 BRDF(GGX + Smith + Fresnel)
  5. 全局光照考虑所有光的传播,更真实但计算量大

下一章预告:在第10章中,我们将学习纹理映射,包括纹理过滤、Mipmap 和各种贴图技术。


文档版本:v1.0
字数统计:约 11,000 字

如有转载或 CV 的请标注本站原文地址