WEBGL Shader零基础教程(十一):高级光照

  • 时间:2025-10-12 05:20 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:目录前言第一章 高级光照基础认知:从 “理想化” 到 “真实化”1.1 什么是高级光照?1.2 基础光照 vs 高级光照:核心差异1.3 高级光照的核心技术体系第二章 PBR 进阶:从 “基础” 到 “写实” 的材质光照2.1 核心技术 1:GGX 微表面模型(更真实的高光)2.1.1 GGX 的核心数学逻辑2.1.2 实战:GGX 微表面的 PBR Shader 实现2.1.3 运行效果与解析2.2 核心技术 2:次表面散射(SSS)

目录

前言

第一章 高级光照基础认知:从 “理想化” 到 “真实化”

1.1 什么是高级光照?

1.2 基础光照 vs 高级光照:核心差异

1.3 高级光照的核心技术体系

第二章 PBR 进阶:从 “基础” 到 “写实” 的材质光照

2.1 核心技术 1:GGX 微表面模型(更真实的高光)

2.1.1 GGX 的核心数学逻辑

2.1.2 实战:GGX 微表面的 PBR Shader 实现

2.1.3 运行效果与解析

2.2 核心技术 2:次表面散射(SSS)—— 通透材质的秘密

2.2.1 SSS 的核心原理

2.2.2 实战:皮肤材质的 SSS 效果

2.2.3 运行效果与解析

第三章 真实光源模拟:从 “点光源” 到 “区域光”

3.1 核心技术:矩形区域光(最常用的真实光源)

3.1.1 矩形区域光的核心逻辑

3.1.2 实战:带软阴影的矩形区域光

3.1.3 运行效果与解析

第四章 体积光:模拟 “光线可见” 的真实场景

4.1 体积光的核心原理:光线步进(Ray Marching)

4.2 实战:丁达尔效应体积光

4.2.1 运行效果与解析

第五章 光照贴图与烘焙:解决高级光照的性能问题

5.1 光照烘焙的核心原理

5.2 实战:Three.js 光照烘焙与 Lightmap 应用

5.2.1 运行效果与解析

第六章 常见问题排查与性能优化

6.1 常见问题排查

6.1.1 问题 1:GGX 高光异常(过亮 / 过暗 / 形状奇怪)

6.1.2 问题 2:体积光性能差(帧率<30fps)

6.1.3 问题 3:光照烘焙后阴影缺失 / 错误

6.2 性能优化技巧

6.2.1 1. 分级渲染(按距离 / 精度动态调整)

6.2.2 2. 硬件加速与 API 优化

6.2.3 3. 效果与性能的平衡

第七章 总结与后续学习方向

7.1 本章核心知识点回顾

7.2 后续学习方向



前言

在 WEBGL Shader 渲染体系中,“高级光照” 是突破 “基础光照局限性”、实现 “照片级真实感” 的核心技术 —— 基础光照(如 Lambert 漫反射、Phong 镜面反射)仅能模拟 “理想化光线交互”,而高级光照通过还原真实世界的光照物理规律(如微表面反射、体积光散射、区域光软阴影),让物体呈现更细腻的质感(如金属的拉丝高光、布料的柔和漫反射)与更自然的环境交互(如阳光穿过烟雾的丁达尔效应、墙角的柔和阴影)。

对零基础开发者而言,高级光照的难点并非语法(已在 GLSL ES 与 PBR 基础教程中覆盖),而是 “微表面模型的数学逻辑”“体积光的采样原理”“光照与材质的耦合关系”。本文作为 WEBGL Shader 系列的第十二篇,将从 “基础认知→核心技术→实战案例” 逐步拆解:先明确高级光照的定义与核心价值,再深入讲解 PBR 进阶(GGX 微表面、Disney BRDF)、区域光、体积光、光照贴图四大核心技术,最后通过 3 个可直接运行的 Three.js 案例,帮你实现 “带软阴影的区域光”“丁达尔效应体积光”“光照烘焙场景”。

所有案例聚焦 Shader 核心逻辑,跳过复杂底层配置,确保零基础用户能边学边改,直观感受 “高级光照对真实感的提升”。掌握本文内容后,你将摆脱 “卡通化”“扁平化” 的渲染效果,迈入次世代高级渲染的核心领域。

第一章 高级光照基础认知:从 “理想化” 到 “真实化”

在深入技术细节前,需先明确 “高级光照” 的核心定位 —— 它不是单一技术,而是一套 “还原真实光照物理规律” 的技术集合,其本质是解决基础光照的 “真实感缺失” 问题。

1.1 什么是高级光照?

高级光照是指超越 “点光源 / 平行光 + Lambert/Phong 模型” 的局限,通过模拟真实世界光照物理特性(如微表面反射、光线散射、区域光照射),实现高真实感渲染的技术体系。其核心目标是 “让光线与物体的交互更贴近现实”,而非基础光照的 “简化计算”。


类比现实世界:基础光照像 “卡通画”(用简单色块表现明暗),高级光照像 “写实照片”(还原金属的高光细节、烟雾的光线穿透效果)。

1.2 基础光照 vs 高级光照:核心差异

二者在 “光照模型、光线来源、真实感、性能” 上存在本质区别,是零基础用户最易混淆的点,具体对比如下:

对比维度基础光照(如 Lambert/Phong)高级光照(如 PBR 进阶 / 体积光 / 区域光)
光照模型简化模型(忽略微表面、能量守恒)物理基于模型(还原微表面、能量守恒、菲涅尔效应)
光线来源理想化光源(点光源、平行光,无体积)真实光源(区域光、体积光,有大小 / 形状)
阴影效果硬阴影(边缘锐利,无过渡)软阴影(边缘柔和,有渐变)
材质交互单一材质响应(如金属 / 非金属无明确区分)材质差异化响应(金属拉丝高光、布料漫反射柔和)
环境交互无间接光照(仅直接光照,暗部纯黑)支持间接光照(环境光反射、全局光照)
真实感低(卡通化、扁平化)高(照片级、写实)
性能消耗低(计算简单,适合低性能设备)高(复杂采样、积分计算,需 GPU 优化)
适用场景2D 游戏、简单 3D 场景、低性能设备3A 游戏、影视渲染、元宇宙、高精度可视化

1.3 高级光照的核心技术体系

高级光照并非单一技术,而是由四大核心技术构成,覆盖 “材质交互→光源模拟→环境交互→性能优化”,具体如下:

技术类别核心技术点解决的问题应用场景
PBR 进阶GGX 微表面模型、Disney BRDF、次表面散射(SSS)基础 PBR 质感不足(如金属高光不锐利、皮肤不通透)角色皮肤、金属材质、布料渲染
真实光源模拟区域光、聚光灯软边缘、IES 光域网基础光源太理想化(无体积、阴影硬)室内照明(如台灯、吊灯)、舞台灯光
体积光与散射光线步进(Ray Marching)、米氏散射、瑞利散射无体积光效果(如阳光穿过烟雾、雾气)自然场景(雾、雨、烟雾)、科幻场景(光束)
光照贴图与烘焙Lightmap(光照贴图)、Light Probe(光照探针)实时高级光照性能不足(帧率低)静态场景(室内、建筑)、开放世界

第二章 PBR 进阶:从 “基础” 到 “写实” 的材质光照

基础 PBR(金属度 / 粗糙度模型)虽能区分金属与非金属,但无法模拟 “拉丝金属的定向高光”“皮肤的通透感” 等细腻质感 ——PBR 进阶通过 “GGX 微表面模型”“次表面散射” 等技术,解决这些真实感缺失问题。

2.1 核心技术 1:GGX 微表面模型(更真实的高光)

基础 PBR 用 “Blinn-Phong 模型” 计算高光,其高光形状过于 “圆润”,不符合真实金属的 “锐利、定向” 高光;而GGX 微表面模型通过更贴近真实的 “法线分布函数(NDF)”,模拟微表面的随机分布,生成更锐利、更真实的高光。

2.1.1 GGX 的核心数学逻辑

GGX 的法线分布函数(NDF)描述 “微表面法线与宏观表面法线一致的概率”,公式如下(无需推导,重点理解参数意义):

glsl

// GGX法线分布函数(NDF):计算微表面法线与半程向量一致的概率
float ggxNDF(vec3 N, vec3 H, float roughness) {
    float a = roughness * roughness; // 粗糙度平方(控制高光锐利度)
    float a2 = a * a;
    float NdotH = max(dot(N, H), 0.001); // 宏观法线与半程向量的点积
    float NdotH2 = NdotH * NdotH;
 
    float numerator = a2;
    float denominator = (NdotH2 * (a2 - 1.0) + 1.0);
    denominator = PI * denominator * denominator; // PI=3.14159
 
    return numerator / denominator;
}


参数影响:  roughness(粗糙度):值越小,a2越小,高光越锐利(如抛光金属);值越大,高光越分散(如磨砂金属);NdotH(法线与半程向量夹角):夹角越小,概率越高,高光越集中在半程向量方向(符合真实光照规律)。

2.1.2 实战:GGX 微表面的 PBR Shader 实现

基于 Three.js,实现带 GGX 高光的 PBR 材质,对比 Blinn-Phong 与 GGX 的高光差异:

html

预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>PBR进阶:GGX微表面模型</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/controls/OrbitControls.js"></script>
    <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
    <script>
        // 1. 基础环境搭建
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x111111);
        document.body.appendChild(renderer.domElement);
        camera.position.z = 5;
 
        // 2. 控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
 
        // 3. 平行光(模拟阳光)
        const light = new THREE.DirectionalLight(0xffffff, 1.0);
        light.position.set(3, 5, 4);
        scene.add(light);
 
        // 4. GGX PBR Shader材质
        const ggxMaterial = new THREE.ShaderMaterial({
            vertexShader: `
                attribute vec3 position;
                attribute vec3 normal;
                attribute vec2 uv;
 
                uniform mat4 modelMatrix;
                uniform mat4 viewMatrix;
                uniform mat3 normalMatrix;
                uniform mat4 projectionMatrix;
 
                varying vec2 vUv;
                varying vec3 vViewPos;   // 视图空间顶点位置
                varying vec3 vViewNormal; // 视图空间法线
 
                void main() {
                    vUv = uv;
                    vec4 viewPos = viewMatrix * modelMatrix * vec4(position, 1.0);
                    vViewPos = viewPos.xyz;
                    vViewNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * viewPos;
                }
            `,
            fragmentShader: `
                precision mediump float;
                #define PI 3.1415926535
 
                // 材质参数
                uniform vec3 uAlbedo;       // 基础色(金属用金色)
                uniform float uMetallic;    // 金属度(1.0=纯金属)
                uniform float uRoughness;   // 粗糙度(0.05=抛光,0.8=磨砂)
                // 光照参数
                uniform vec3 uLightDir;     // 视图空间光源方向
                uniform vec3 uLightColor;   // 光源颜色(白色)
                uniform vec3 uEnvColor;     // 环境色(灰色)
 
                varying vec2 vUv;
                varying vec3 vViewPos;
                varying vec3 vViewNormal;
 
                // 1. GGX法线分布函数(NDF)
                float ggxNDF(vec3 N, vec3 H, float roughness) {
                    float a = roughness * roughness;
                    float a2 = a * a;
                    float NdotH = max(dot(N, H), 0.001);
                    float NdotH2 = NdotH * NdotH;
 
                    float numerator = a2;
                    float denominator = (NdotH2 * (a2 - 1.0) + 1.0);
                    denominator = PI * denominator * denominator;
                    return numerator / denominator;
                }
 
                // 2. 几何遮蔽函数(G):模拟微表面互相遮挡导致的高光衰减
                float geometrySchlickGGX(float NdotV, float roughness) {
                    float r = (roughness + 1.0);
                    float k = (r * r) / 8.0; // 粗糙度校正因子
                    return NdotV / (NdotV * (1.0 - k) + k);
                }
                float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
                    float NdotV = max(dot(N, V), 0.001);
                    float NdotL = max(dot(N, L), 0.001);
                    float g1 = geometrySchlickGGX(NdotV, roughness);
                    float g2 = geometrySchlickGGX(NdotL, roughness);
                    return g1 * g2;
                }
 
                // 3. 菲涅尔函数(F):模拟不同角度的反射强度(如掠射时反射强)
                vec3 fresnelSchlick(float cosTheta, vec3 F0) {
                    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
                }
 
                void main() {
                    // 核心向量计算(视图空间)
                    vec3 N = normalize(vViewNormal);       // 宏观法线
                    vec3 V = normalize(-vViewPos);         // 观察方向(物体→相机)
                    vec3 L = normalize(uLightDir);         // 光源方向
                    vec3 H = normalize(V + L);             // 半程向量(V与L的中间方向)
 
                    // 基础参数计算
                    vec3 F0 = mix(vec3(0.04), uAlbedo, uMetallic); // 基础反射率(金属用自身颜色)
                    float NdotL = max(dot(N, L), 0.001);   // 法线与光源方向夹角(漫反射因子)
 
                    // 4. PBR光照计算(GGX核心)
                    // 4.1 镜面反射(GGX NDF + 几何遮蔽 + 菲涅尔)
                    float ndf = ggxNDF(N, H, uRoughness);
                    float geo = geometrySmith(N, V, L, uRoughness);
                    vec3 fresnel = fresnelSchlick(max(dot(H, V), 0.001), F0);
                    vec3 specular = (ndf * geo * fresnel) / (4.0 * NdotL * max(dot(N, V), 0.001) + 0.001);
 
                    // 4.2 漫反射(金属无漫反射)
                    vec3 diffuse = (1.0 - fresnel) * (uAlbedo / PI) * (1.0 - uMetallic);
 
                    // 4.3 最终光照(直接光照 + 环境光)
                    vec3 directLight = (diffuse + specular) * uLightColor * NdotL;
                    vec3 ambientLight = uEnvColor * uAlbedo * 0.2; // 环境光提亮暗部
                    vec3 finalColor = directLight + ambientLight;
 
                    // Gamma校正
                    finalColor = pow(finalColor, vec3(1.0 / 2.2));
                    gl_FragColor = vec4(finalColor, 1.0);
                }
            `,
            uniforms: {
                uAlbedo: { value: new THREE.Color(1.0, 0.7, 0.2) }, // 金色金属
                uMetallic: { value: 1.0 },                          // 纯金属
                uRoughness: { value: 0.1 },                         // 低粗糙度(抛光)
                uLightDir: { value: light.position.clone().applyMatrix3(new THREE.Matrix3().getInverse(camera.matrixWorldInverse)) },
                uLightColor: { value: new THREE.Color(1.0, 1.0, 1.0) },
                uEnvColor: { value: new THREE.Color(0.2, 0.2, 0.2) }
            }
        });
 
        // 5. 创建金属球(展示GGX高光)
        const sphere = new THREE.Mesh(new THREE.SphereGeometry(1.5, 64, 64), ggxMaterial);
        scene.add(sphere);
 
        // 6. 渲染循环(更新光源方向)
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            // 更新视图空间光源方向(相机移动时同步更新)
            ggxMaterial.uniforms.uLightDir.value.copy(light.position).applyMatrix3(new THREE.Matrix3().getInverse(camera.matrixWorldInverse)).normalize();
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>
2.1.3 运行效果与解析

运行效果:金色金属球表面呈现 “锐利的定向高光”,随相机角度变化,高光位置和强度动态调整(掠射时反射增强,垂直观察时反射减弱),完全符合真实抛光金属的质感;若将uRoughness改为 0.8(磨砂),高光会变得分散柔和,模拟磨砂金属效果;核心优势:GGX 通过 “NDF(高光形状)+ 几何遮蔽(高光衰减)+ 菲涅尔(角度反射)” 三函数耦合,还原真实微表面的光照交互,这是基础 Phong 模型无法实现的。

2.2 核心技术 2:次表面散射(SSS)—— 通透材质的秘密

基础 PBR 无法模拟 “皮肤、蜡、玉石” 等 “半透明、通透” 材质的质感(如阳光照射下皮肤的红润感),而次表面散射(Subsurface Scattering,SSS) 通过模拟 “光线穿透物体表面后在内部散射,再从其他位置射出” 的过程,实现通透效果。

2.2.1 SSS 的核心原理

真实世界中,光线与通透材质的交互分为三步:

光线穿透物体表面(部分被反射,部分折射进入内部);光线在物体内部与粒子碰撞,发生多次散射(方向改变);散射后的光线从物体其他位置射出,形成 “通透感”。


WEBGL 中实现 SSS 的简化方案是 “屏幕空间次表面散射(Screen Space SSS,SSSS) ”:在片元着色器中,采样当前像素周围的像素颜色,按 “散射距离” 加权混合,模拟内部散射效果。

2.2.2 实战:皮肤材质的 SSS 效果

基于 Three.js,实现带 SSS 效果的皮肤材质,对比 “无 SSS” 与 “有 SSS” 的差异:

html

预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>高级光照:次表面散射(SSS)</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/controls/OrbitControls.js"></script>
    <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
    <script>
        // 1. 基础环境搭建(同前)
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x111111);
        document.body.appendChild(renderer.domElement);
        camera.position.z = 5;
 
        // 2. 控制器与光源
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        const light = new THREE.DirectionalLight(0xffffff, 1.5);
        light.position.set(5, 3, 4);
        scene.add(light);
 
        // 3. 创建离屏渲染目标(存储无SSS的原始颜色,用于SSS采样)
        const rtWidth = window.innerWidth;
        const rtHeight = window.innerHeight;
        const renderTarget = new THREE.WebGLRenderTarget(rtWidth, rtHeight);
        renderTarget.texture.minFilter = THREE.LinearFilter;
        renderTarget.texture.magFilter = THREE.LinearFilter;
 
        // 4. 基础皮肤材质(无SSS,渲染到离屏目标)
        const baseSkinMaterial = new THREE.MeshStandardMaterial({
            color: new THREE.Color(0.9, 0.7, 0.6), // 皮肤色
            roughness: 0.4,
            metalness: 0.0,
            transparent: false
        });
 
        // 5. SSS后期处理材质(采样离屏纹理,添加散射效果)
        const sssMaterial = new THREE.ShaderMaterial({
            vertexShader: `
                varying vec2 vUv;
                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                precision mediump float;
                uniform sampler2D uBaseTexture; // 无SSS的原始纹理
                uniform vec2 uResolution;       // 屏幕分辨率
                uniform float uSSSStrength;     // SSS强度(0=无,1=强)
                uniform vec3 uLightDir;         // 光源方向(用于散射方向)
 
                varying vec2 vUv;
 
                // SSS采样:周围像素加权混合
                vec3 sampleSSS() {
                    vec2 texelSize = 1.0 / uResolution; // 1像素的UV偏移
                    vec3 finalColor = vec3(0.0);
                    float totalWeight = 0.0;
 
                    // 采样方向:沿光源方向偏移(模拟光线从光源侧散射)
                    vec2 offsetDir = normalize(vec2(uLightDir.x, -uLightDir.y)); // 屏幕空间光源方向
                    float maxOffset = 4.0 * uSSSStrength; // 最大采样偏移(像素数)
 
                    // 采样周围8个方向(简化版,实际可增加采样点)
                    for (float i = -maxOffset; i <= maxOffset; i += 1.0) {
                        if (i == 0.0) continue; // 跳过自身像素
                        float weight = exp(-abs(i) * 0.3) * 0.5; // 距离越远,权重越小
                        vec2 uvOffset = offsetDir * i * texelSize;
                        vec3 sampleColor = texture2D(uBaseTexture, vUv + uvOffset).rgb;
                        finalColor += sampleColor * weight;
                        totalWeight += weight;
                    }
 
                    // 混合原始颜色与SSS颜色
                    vec3 baseColor = texture2D(uBaseTexture, vUv).rgb;
                    return mix(baseColor, finalColor / totalWeight, uSSSStrength);
                }
 
                void main() {
                    vec3 sssColor = sampleSSS();
                    gl_FragColor = vec4(sssColor, 1.0);
                }
            `,
            uniforms: {
                uBaseTexture: { value: renderTarget.texture },
                uResolution: { value: new THREE.Vector2(rtWidth, rtHeight) },
                uSSSStrength: { value: 0.6 }, // SSS强度
                uLightDir: { value: light.position.clone().normalize() }
            }
        });
 
        // 6. 创建人脸模型(用球体简化,实际可用GLB模型)
        const head = new THREE.Mesh(new THREE.SphereGeometry(1.5, 64, 64), baseSkinMaterial);
        scene.add(head);
 
        // 7. 创建全屏四边形(用于SSS后期处理)
        const fullscreenQuad = new THREE.Mesh(
            new THREE.PlaneGeometry(2, 2), // 全屏四边形(NDC坐标)
            sssMaterial
        );
        fullscreenQuad.renderOrder = 1000; // 最后渲染,覆盖整个屏幕
 
        // 8. 渲染循环(先渲染基础材质到离屏目标,再渲染SSS后期)
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
 
            // 步骤1:渲染基础皮肤材质到离屏目标
            scene.overrideMaterial = baseSkinMaterial; // 强制使用基础材质
            renderer.setRenderTarget(renderTarget);
            renderer.render(scene, camera);
            renderer.setRenderTarget(null);
            scene.overrideMaterial = null;
 
            // 步骤2:渲染SSS后期处理(全屏四边形)
            renderer.render(new THREE.Scene().add(fullscreenQuad), camera);
        }
        animate();
    </script>
</body>
</html>
2.2.3 运行效果与解析

运行效果:球体(模拟人脸)在光源照射侧呈现 “红润的通透感”,暗部过渡柔和,完全区别于 “无 SSS 时的哑光质感”,类似真实皮肤在阳光下的效果;调整uSSSStrength可控制通透程度(0 = 无 SSS,1 = 过度通透);核心逻辑:通过 “离屏渲染 + 屏幕空间采样”,模拟光线在皮肤内部的散射 —— 沿光源方向采样周围像素,加权混合后与原始颜色叠加,实现 “光线从光源侧穿透到另一侧” 的通透效果。

第三章 真实光源模拟:从 “点光源” 到 “区域光”

基础光照的 “点光源 / 平行光” 是 “无体积、无形状” 的理想化光源,而真实世界的光源(如台灯、窗户)都有 “体积和形状”,会产生 “软阴影”“光照渐变” 等效果 —— 高级光照通过 “区域光” 模拟这些真实光源特性。

3.1 核心技术:矩形区域光(最常用的真实光源)

矩形区域光是模拟 “窗户、LED 面板灯” 等矩形光源的核心技术,其核心是 “在光源矩形范围内随机采样多个点光源,将多个点光源的光照结果平均,生成软阴影和渐变光照”。

3.1.1 矩形区域光的核心逻辑

光源定义:用 “位置(position)、大小(width/height)、方向(normal)” 定义矩形光源(如窗户的位置、尺寸、朝向);随机采样:在矩形范围内随机生成 N 个采样点(N 越多,软阴影越平滑,但性能消耗越高);光照计算:对每个采样点,按点光源计算光照(方向、距离、阴影),最后将 N 个结果平均,得到区域光的最终光照。

3.1.2 实战:带软阴影的矩形区域光

基于 Three.js,实现矩形区域光照射立方体,展示软阴影效果:


html

预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>高级光照:矩形区域光(软阴影)</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/controls/OrbitControls.js"></script>
    <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
    <script>
        // 1. 基础环境搭建
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x222222);
        document.body.appendChild(renderer.domElement);
        camera.position.set(5, 5, 8);
 
        // 2. 控制器与地面
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        // 地面(接收阴影)
        const ground = new THREE.Mesh(
            new THREE.PlaneGeometry(20, 20),
            new THREE.MeshStandardMaterial({ color: 0x333333 })
        );
        ground.rotation.x = -Math.PI / 2;
        ground.position.y = -2;
        scene.add(ground);
 
        // 3. 立方体(接收/投射阴影)
        const cube = new THREE.Mesh(
            new THREE.BoxGeometry(2, 2, 2),
            new THREE.MeshStandardMaterial({ color: 0x44aaff })
        );
        cube.position.set(0, 0, 0);
        scene.add(cube);
 
        // 4. 矩形区域光实现(自定义ShaderMaterial模拟)
        const areaLight = {
            position: new THREE.Vector3(3, 4, 0), // 光源中心位置
            width: 2.0,                           // 光源宽度
            height: 1.5,                          // 光源高度
            color: new THREE.Color(1.0, 0.9, 0.7),// 光源颜色(暖黄色)
            intensity: 3.0,                       // 光强
            sampleCount: 16                       // 采样点数量(越多阴影越软,性能越低)
        };
 
        // 5. 自定义区域光材质(替换MeshStandardMaterial)
        const areaLightMaterial = new THREE.ShaderMaterial({
            vertexShader: `
                attribute vec3 position;
                attribute vec3 normal;
                attribute vec2 uv;
 
                uniform mat4 modelMatrix;
                uniform mat4 viewMatrix;
                uniform mat3 normalMatrix;
                uniform mat4 projectionMatrix;
                uniform vec3 uAreaLightPos; // 区域光位置
 
                varying vec2 vUv;
                varying vec3 vViewPos;
                varying vec3 vViewNormal;
                varying vec3 vWorldPos;    // 世界空间顶点位置(用于阴影计算)
                varying vec3 vToLight;      // 顶点到光源中心的向量
 
                void main() {
                    vUv = uv;
                    vec4 worldPos = modelMatrix * vec4(position, 1.0);
                    vWorldPos = worldPos.xyz;
                    vToLight = uAreaLightPos - vWorldPos;
 
                    vec4 viewPos = viewMatrix * worldPos;
                    vViewPos = viewPos.xyz;
                    vViewNormal = normalize(normalMatrix * normal);
 
                    gl_Position = projectionMatrix * viewPos;
                }
            `,
            fragmentShader: `
                precision mediump float;
                #define PI 3.1415926535
 
                // 材质参数
                uniform vec3 uAlbedo;
                // 区域光参数
                uniform vec3 uAreaLightPos;
                uniform float uAreaLightWidth;
                uniform float uAreaLightHeight;
                uniform vec3 uAreaLightColor;
                uniform float uAreaLightIntensity;
                uniform int uAreaLightSampleCount;
                // 场景参数
                uniform vec3 uCameraPos;    // 相机位置(世界空间)
 
                varying vec3 vWorldPos;
                varying vec3 vViewNormal;
                varying vec3 vToLight;
 
                // 随机函数(用于采样点随机偏移)
                float random(vec2 uv) {
                    return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);
                }
 
                // 矩形区域光采样:生成随机采样点
                vec3 sampleAreaLight(vec2 rand) {
                    // 光源局部坐标系(X=宽度,Y=高度,Z=朝向)
                    vec3 lightNormal = normalize(vToLight); // 光源朝向顶点的方向
                    vec3 lightX = normalize(cross(lightNormal, vec3(0, 1, 0))); // 光源宽度方向
                    vec3 lightY = normalize(cross(lightX, lightNormal));        // 光源高度方向
 
                    // 随机偏移(-0.5~0.5)
                    float offsetX = (rand.x - 0.5) * uAreaLightWidth;
                    float offsetY = (rand.y - 0.5) * uAreaLightHeight;
 
                    // 采样点位置(世界空间)
                    return uAreaLightPos + lightX * offsetX + lightY * offsetY;
                }
 
                // 软阴影计算(简化版:判断采样点是否被遮挡)
                float calculateSoftShadow(vec3 samplePos) {
                    vec3 rayDir = normalize(samplePos - vWorldPos);
                    float rayLength = distance(samplePos, vWorldPos);
                    // 简化:假设无遮挡(实际需用光线追踪或阴影贴图,此处为演示)
                    // 真实场景需替换为ShadowMap或光线步进遮挡检测
                    return 1.0; // 1.0=无遮挡,0.0=完全遮挡
                }
 
                void main() {
                    vec3 N = normalize(vViewNormal);
                    vec3 V = normalize(uCameraPos - vWorldPos); // 观察方向(世界空间)
                    vec3 finalColor = vec3(0.0);
 
                    // 采样多个点光源,平均结果
                    for (int i = 0; i < uAreaLightSampleCount; i++) {
                        // 生成随机数(基于顶点UV和采样索引)
                        vec2 rand = vec2(random(vWorldPos.xy + float(i)), random(vWorldPos.yz + float(i)));
                        // 采样区域光的一个点
                        vec3 samplePos = sampleAreaLight(rand);
                        // 计算该采样点的光照
                        vec3 L = normalize(samplePos - vWorldPos);
                        float NdotL = max(dot(N, L), 0.001);
                        float distance = distance(samplePos, vWorldPos);
                        float attenuation = 1.0 / (distance * distance); // 距离衰减
 
                        // 软阴影(简化版)
                        float shadow = calculateSoftShadow(samplePos);
 
                        // 漫反射光照(简化,可结合PBR)
                        vec3 diffuse = uAlbedo * uAreaLightColor * uAreaLightIntensity * NdotL * attenuation * shadow;
                        finalColor += diffuse;
                    }
 
                    // 平均多个采样点的结果
                    finalColor /= float(uAreaLightSampleCount);
 
                    // 环境光提亮
                    finalColor += uAlbedo * 0.1;
 
                    // Gamma校正
                    finalColor = pow(finalColor, vec3(1.0 / 2.2));
                    gl_FragColor = vec4(finalColor, 1.0);
                }
            `,
            uniforms: {
                uAlbedo: { value: new THREE.Color(0x44aaff) },
                // 区域光参数
                uAreaLightPos: { value: areaLight.position },
                uAreaLightWidth: { value: areaLight.width },
                uAreaLightHeight: { value: areaLight.height },
                uAreaLightColor: { value: areaLight.color },
                uAreaLightIntensity: { value: areaLight.intensity },
                uAreaLightSampleCount: { value: areaLight.sampleCount },
                // 相机位置
                uCameraPos: { value: camera.position }
            }
        });
 
        // 6. 应用区域光材质
        cube.material = areaLightMaterial;
        ground.material = areaLightMaterial.clone();
        ground.material.uniforms.uAlbedo.value = new THREE.Color(0x333333);
 
        // 7. 渲染循环(更新相机位置)
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            // 更新相机位置uniform
            areaLightMaterial.uniforms.uCameraPos.value.copy(camera.position);
            ground.material.uniforms.uCameraPos.value.copy(camera.position);
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>
3.1.3 运行效果与解析

运行效果:暖黄色的矩形区域光照射立方体和地面,立方体的阴影边缘呈现 “柔和的渐变”(而非基础光的硬边缘),光照从光源中心向边缘逐渐衰减,完全模拟真实窗户光照的效果;调整sampleCount(采样点数量),采样点越多,阴影越平滑,但帧率会下降;核心优势:通过 “多采样点平均”,解决基础光源 “硬阴影” 的真实感缺失问题,是室内场景、舞台灯光等真实光源模拟的核心技术;优化方向:案例中阴影为简化版(无遮挡检测),真实场景需结合 “阴影贴图(Shadow Map)” 或 “光线追踪” 实现遮挡检测,Three.js 的RectAreaLight类已封装完整逻辑,可直接调用。

第四章 体积光:模拟 “光线可见” 的真实场景

基础光照仅能模拟 “光线照射物体产生明暗”,但无法模拟 “光线在空气中的可见效果”(如阳光穿过烟雾、手电筒光束)——体积光(Volumetric Light) 通过模拟 “光线与空气粒子的散射”,让光线本身成为 “可见的渲染对象”,是实现 “丁达尔效应”“雾中光束” 的核心技术。

4.1 体积光的核心原理:光线步进(Ray Marching)

体积光的本质是 “光线在体积介质(如雾、烟雾)中传播时,与粒子碰撞产生的散射光”,WEBGL 中通过 “光线步进(Ray Marching)” 技术实现:

光线生成:从相机出发,向屏幕每个像素发射一条光线;步进采样:将光线分为多个 “步进段”,在每个步进点采样体积介质的密度(如烟雾浓度);散射计算:若步进点有体积介质,计算光线在该点的散射光(向相机方向发射),叠加到最终颜色;衰减计算:光线穿过体积介质时,强度会因吸收而衰减,距离越远,衰减越严重。

4.2 实战:丁达尔效应体积光

基于 Three.js,实现 “阳光穿过烟雾” 的丁达尔效应,核心是 “光线步进采样体积纹理”:

html

预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>高级光照:体积光(丁达尔效应)</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/controls/OrbitControls.js"></script>
    <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
    <script>
        // 1. 基础环境搭建
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x0a0a0a); // 深色背景,突出光束
        document.body.appendChild(renderer.domElement);
        camera.position.set(0, 2, 10);
 
        // 2. 控制器与场景元素
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        // 远处墙壁(衬托光束)
        const wall = new THREE.Mesh(
            new THREE.PlaneGeometry(20, 10),
            new THREE.MeshStandardMaterial({ color: 0x1a1a1a })
        );
        wall.position.z = -10;
        scene.add(wall);
 
        // 3. 体积光参数
        const volumetricLight = {
            position: new THREE.Vector3(5, 5, 5),  // 光源位置(如太阳)
            direction: new THREE.Vector3(-1, -1, -1).normalize(), // 光源方向
            color: new THREE.Color(1.0, 0.8, 0.4), // 光束颜色(暖黄色)
            intensity: 2.0,                        // 光强
            stepCount: 32,                         // 光线步进次数(越多越细腻,性能越低)
            maxDistance: 20.0,                     // 光束最大距离
            density: 0.15                          // 体积介质密度(越大光束越浓)
        };
 
        // 4. 体积光纹理(模拟烟雾密度,用噪声纹理简化)
        const noiseTexture = new THREE.TextureLoader().load('https://threejs.org/examples/textures/noise.png', (tex) => {
            tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
            tex.repeat.set(8, 8);
        });
 
        // 5. 体积光后期处理材质(光线步进核心)
        const volumetricMaterial = new THREE.ShaderMaterial({
            vertexShader: `
                varying vec2 vUv;
                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                precision mediump float;
                uniform sampler2D uSceneTexture;    // 场景原始纹理(无体积光)
                uniform sampler2D uNoiseTexture;    // 噪声纹理(模拟烟雾密度)
                uniform vec2 uResolution;           // 屏幕分辨率
                uniform vec3 uCameraPos;            // 相机位置(世界空间)
                uniform mat4 uProjectionInverse;    // 投影矩阵逆(用于光线生成)
                uniform mat4 uViewInverse;          // 视图矩阵逆(用于光线生成)
                // 体积光参数
                uniform vec3 uLightPos;
                uniform vec3 uLightDir;
                uniform vec3 uLightColor;
                uniform float uLightIntensity;
                uniform int uStepCount;
                uniform float uMaxDistance;
                uniform float uDensity;
 
                varying vec2 vUv;
 
                // 生成相机到像素的光线(世界空间)
                vec3 getRayDirection() {
                    // NDC坐标(-1~1)
                    vec2 ndc = vUv * 2.0 - 1.0;
                    vec4 clipPos = vec4(ndc.x, ndc.y, -1.0, 1.0); // 近平面点
                    vec4 viewPos = uProjectionInverse * clipPos;   // 视图空间
                    viewPos /= viewPos.w;
                    vec4 worldPos = uViewInverse * viewPos;        // 世界空间
                    return normalize(worldPos.xyz - uCameraPos);
                }
 
                // 体积光采样(光线步进)
                vec3 sampleVolumetricLight(vec3 rayOrigin, vec3 rayDir) {
                    vec3 volumetricColor = vec3(0.0);
                    float totalTransmittance = 1.0; // 光线透过率(初始为1,无衰减)
                    float stepSize = uMaxDistance / float(uStepCount); // 每步距离
 
                    // 光线步进
                    for (int i = 0; i < uStepCount; i++) {
                        // 当前步进点位置(世界空间)
                        vec3 currentPos = rayOrigin + rayDir * float(i) * stepSize;
                        // 计算到光源的距离(超出范围则跳过)
                        float distanceToLight = distance(currentPos, uLightPos);
                        if (distanceToLight > uMaxDistance) continue;
 
                        // 1. 采样烟雾密度(用噪声纹理模拟随机密度)
                        vec3 noisePos = currentPos * 0.2; // 缩放噪声坐标,控制烟雾大小
                        float density = texture2D(uNoiseTexture, noisePos.xy).r * uDensity;
                        // 只在光源方向附近产生光束(模拟定向光)
                        float lightDirFactor = max(dot(normalize(currentPos - uLightPos), -uLightDir), 0.0);
                        density *= pow(lightDirFactor, 8.0); // 增强光源方向的密度
 
                        // 2. 计算散射光(向相机方向发射的光)
                        float scatter = density * stepSize * uLightIntensity;
                        volumetricColor += totalTransmittance * scatter * uLightColor;
 
                        // 3. 计算光线衰减(透过率降低)
                        totalTransmittance *= exp(-scatter * 2.0);
                        // 透过率过低时提前退出(性能优化)
                        if (totalTransmittance < 0.01) break;
                    }
 
                    return volumetricColor;
                }
 
                void main() {
                    // 步骤1:获取场景原始颜色
                    vec3 sceneColor = texture2D(uSceneTexture, vUv).rgb;
 
                    // 步骤2:生成光线并采样体积光
                    vec3 rayDir = getRayDirection();
                    vec3 volumetricColor = sampleVolumetricLight(uCameraPos, rayDir);
 
                    // 步骤3:混合场景颜色与体积光颜色
                    vec3 finalColor = sceneColor + volumetricColor;
                    gl_FragColor = vec4(finalColor, 1.0);
                }
            `,
            uniforms: {
                uSceneTexture: { value: new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight).texture },
                uNoiseTexture: { value: noiseTexture },
                uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
                uCameraPos: { value: camera.position },
                uProjectionInverse: { value: new THREE.Matrix4() },
                uViewInverse: { value: new THREE.Matrix4() },
                // 体积光参数
                uLightPos: { value: volumetricLight.position },
                uLightDir: { value: volumetricLight.direction },
                uLightColor: { value: volumetricLight.color },
                uLightIntensity: { value: volumetricLight.intensity },
                uStepCount: { value: volumetricLight.stepCount },
                uMaxDistance: { value: volumetricLight.maxDistance },
                uDensity: { value: volumetricLight.density }
            }
        });
 
        // 6. 离屏渲染目标(存储场景原始纹理)
        const sceneRenderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
        volumetricMaterial.uniforms.uSceneTexture.value = sceneRenderTarget.texture;
 
        // 7. 全屏四边形(用于体积光后期处理)
        const fullscreenQuad = new THREE.Mesh(
            new THREE.PlaneGeometry(2, 2),
            volumetricMaterial
        );
        fullscreenQuad.renderOrder = 1000;
 
        // 8. 渲染循环(先渲染场景,再渲染体积光)
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
 
            // 步骤1:渲染场景到离屏目标(无体积光)
            renderer.setRenderTarget(sceneRenderTarget);
            renderer.render(scene, camera);
            renderer.setRenderTarget(null);
 
            // 步骤2:更新体积光uniform(投影/视图矩阵逆)
            volumetricMaterial.uniforms.uProjectionInverse.value.copy(camera.projectionMatrix).invert();
            volumetricMaterial.uniforms.uViewInverse.value.copy(camera.matrixWorld);
            volumetricMaterial.uniforms.uCameraPos.value.copy(camera.position);
 
            // 步骤3:渲染体积光后期处理
            renderer.render(new THREE.Scene().add(fullscreenQuad), camera);
        }
        animate();
    </script>
</body>
</html>
4.2.1 运行效果与解析

运行效果:深色背景中,暖黄色的光束从光源位置向远处延伸,光束内部有 “烟雾状的纹理”(噪声纹理模拟),随相机角度变化,光束的形状和亮度动态调整,完全模拟真实阳光穿过烟雾的丁达尔效应;调整uDensity(密度),密度越大,光束越浓;核心逻辑:通过 “光线步进 + 噪声纹理采样”,模拟光线与烟雾粒子的散射 —— 在光线的每个步进点,采样烟雾密度,计算散射光并叠加,同时考虑光线衰减,实现 “光束可见” 的效果;性能优化:通过 “提前退出”(透过率<0.01 时终止步进)和 “减少步进次数”(如 32 次),平衡效果与性能,避免帧率过低。

第五章 光照贴图与烘焙:解决高级光照的性能问题

高级光照(如 PBR、体积光)的计算复杂度高,实时渲染时易导致帧率下降 ——光照贴图(Lightmap) 与光照烘焙(Light Baking) 通过 “离线计算光照结果,存储到纹理中,实时渲染时直接采样纹理” 的方式,大幅降低实时计算压力,是静态场景(如室内、建筑)的核心性能优化技术。

5.1 光照烘焙的核心原理

光照烘焙是 “离线计算场景中所有静态物体的光照结果,将结果存储到光照贴图(Lightmap)中,实时渲染时物体直接采样 Lightmap 获取光照颜色,无需实时计算光照” 的技术,流程如下:


标记静态物体:指定场景中不移动的物体(如墙壁、地面、家具)为 “静态物体”;离线烘焙:用烘焙工具(如 Blender、Three.js 的LightBaker)计算静态物体的光照(包括直接光照、间接光照、阴影);生成 Lightmap:将烘焙后的光照颜色存储到 2D 纹理(Lightmap)中,每个物体的 UV 映射到 Lightmap 的对应区域;实时渲染:静态物体的材质采样 Lightmap,直接获取光照颜色,动态物体(如角色)仍用实时光照。

5.2 实战:Three.js 光照烘焙与 Lightmap 应用

基于 Three.js 的LightBaker扩展(简化版),实现室内场景的光照烘焙,对比 “实时光照” 与 “烘焙光照” 的性能差异:

html

预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>高级光照:光照贴图(Lightmap)</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/controls/OrbitControls.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/utils/LightBaker.js"></script> <!-- 光照烘焙工具 -->
    <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
    <script>
        // 1. 基础环境搭建
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x111111);
        document.body.appendChild(renderer.domElement);
        camera.position.set(3, 2, 5);
 
        // 2. 控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
 
        // 3. 创建室内场景(静态物体:墙壁、地面、桌子)
        const staticObjects = []; // 存储静态物体,用于烘焙
 
        // 3.1 地面
        const ground = new THREE.Mesh(
            new THREE.PlaneGeometry(10, 10),
            new THREE.MeshStandardMaterial({ color: 0x333333 })
        );
        ground.rotation.x = -Math.PI / 2;
        ground.position.y = 0;
        ground.userData.isStatic = true; // 标记为静态物体
        scene.add(ground);
        staticObjects.push(ground);
 
        // 3.2 后墙
        const backWall = new THREE.Mesh(
            new THREE.PlaneGeometry(10, 5),
            new THREE.MeshStandardMaterial({ color: 0x444444 })
        );
        backWall.position.set(0, 2.5, -5);
        backWall.userData.isStatic = true;
        scene.add(backWall);
        staticObjects.push(backWall);
 
        // 3.3 桌子(静态)
        const table = new THREE.Mesh(
            new THREE.BoxGeometry(4, 0.2, 2),
            new THREE.MeshStandardMaterial({ color: 0x8B4513 }) // 棕色桌子
        );
        table.position.set(0, 0.1, -1);
        table.userData.isStatic = true;
        scene.add(table);
        staticObjects.push(table);
 
        // 4. 添加光源(用于烘焙)
        const pointLight = new THREE.PointLight(0xffffff, 3.0);
        pointLight.position.set(2, 3, 1);
        scene.add(pointLight);
 
        // 5. 光照烘焙(离线计算Lightmap)
        async function bakeLightmap() {
            // 5.1 初始化LightBaker
            const lightBaker = new THREE.LightBaker(renderer);
            // 设置烘焙参数
            lightBaker.setSize(1024, 1024); // Lightmap分辨率
            lightBaker.setSamples(32);       // 采样点数量(越多越细腻)
            lightBaker.setStaticObjects(staticObjects); // 静态物体
            lightBaker.addLight(pointLight); // 添加光源
 
            // 5.2 开始烘焙(异步,耗时操作)
            console.log('开始烘焙光照...');
            await lightBaker.bake();
            console.log('烘焙完成!');
 
            // 5.3 应用Lightmap到静态物体
            const lightmapTexture = lightBaker.getLightmap(); // 获取烘焙后的Lightmap
            staticObjects.forEach(obj => {
                // 替换材质为支持Lightmap的材质
                obj.material = new THREE.ShaderMaterial({
                    vertexShader: `
                        attribute vec3 position;
                        attribute vec2 uv;
                        attribute vec2 uv2; // Lightmap UV(烘焙工具生成)
 
                        uniform mat4 modelMatrix;
                        uniform mat4 viewMatrix;
                        uniform mat4 projectionMatrix;
 
                        varying vec2 vUv;
                        varying vec2 vUv2; // 传递Lightmap UV
 
                        void main() {
                            vUv = uv;
                            vUv2 = uv2;
                            gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
                        }
                    `,
                    fragmentShader: `
                        precision mediump float;
                        uniform sampler2D uAlbedoTexture; // 基础色纹理(可选)
                        uniform sampler2D uLightmapTexture; // Lightmap
                        uniform vec3 uAlbedo; // 基础色
 
                        varying vec2 vUv;
                        varying vec2 vUv2;
 
                        void main() {
                            // 采样Lightmap获取光照颜色
                            vec3 lightColor = texture2D(uLightmapTexture, vUv2).rgb;
                            // 混合基础色与光照色
                            vec3 finalColor = uAlbedo * lightColor;
                            gl_FragColor = vec4(finalColor, 1.0);
                        }
                    `,
                    uniforms: {
                        uAlbedo: { value: obj.material.color },
                        uLightmapTexture: { value: lightmapTexture }
                    }
                });
                // 应用烘焙生成的Lightmap UV
                obj.geometry.setAttribute('uv2', new THREE.BufferAttribute(lightBaker.getUVsForObject(obj), 2));
            });
 
            // 5.4 烘焙完成后移除实时光源(避免重复光照)
            scene.remove(pointLight);
        }
 
        // 6. 启动烘焙(页面加载后执行)
        window.addEventListener('load', bakeLightmap);
 
        // 7. 添加动态物体(如角色,仍用实时光照)
        const dynamicCube = new THREE.Mesh(
            new THREE.BoxGeometry(0.5, 0.5, 0.5),
            new THREE.MeshStandardMaterial({ color: 0x44aaff })
        );
        dynamicCube.position.set(1, 0.5, 0);
        scene.add(dynamicCube);
 
        // 8. 渲染循环(动态物体旋转)
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            // 动态物体旋转
            dynamicCube.rotation.x += 0.01;
            dynamicCube.rotation.y += 0.02;
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>
5.2.1 运行效果与解析

运行效果:页面加载后,控制台显示 “开始烘焙→烘焙完成”,烘焙完成后,静态物体(地面、墙壁、桌子)呈现 “柔和的光照和阴影”(如桌子在地面的阴影、墙壁的光照渐变),动态立方体(蓝色)旋转时仍有实时光照;对比 “烘焙前”(实时计算光照,帧率可能较低)和 “烘焙后”(采样 Lightmap,帧率显著提升),性能差异明显;核心优势:将静态物体的光照计算从 “实时” 转移到 “离线”,实时渲染时仅需采样纹理,CPU/GPU 压力大幅降低,适合大规模静态场景(如建筑可视化、开放世界的静态地形);注意事项:Lightmap 分辨率需平衡 “细腻度” 与 “内存”(1024x1024 足够多数场景),采样点数量越多,烘焙时间越长,但光照越平滑。

第六章 常见问题排查与性能优化

高级光照的实现复杂度高,易出现 “质感异常”“性能卡顿”“效果不符合预期” 等问题,本节提供针对性的排查步骤与优化技巧,帮助零基础用户快速解决问题。

6.1 常见问题排查

6.1.1 问题 1:GGX 高光异常(过亮 / 过暗 / 形状奇怪)

现象:金属材质的 GGX 高光过亮(泛白)、过暗(无高光)或形状不规则;原因 1:菲涅尔函数(F)的基础反射率(F0)设置错误;  排查:金属的 F0 应设为自身颜色(mix(vec3(0.04), uAlbedo, uMetallic)),非金属的 F0 固定为vec3(0.04),若金属的 F0 设为vec3(0.04),会导致高光过暗; 原因 2:粗糙度未平方(GGX 的 NDF 需要粗糙度平方);  排查:确保 NDF 函数中使用a = roughness * roughness,若直接用roughness,会导致高光过分散; 原因 3:光照计算时未做 Gamma 校正;  排查:最终颜色需通过pow(finalColor, vec3(1.0/2.2))校正,否则高光会过亮。

6.1.2 问题 2:体积光性能差(帧率<30fps)

现象:体积光场景帧率低,画面卡顿;原因 1:光线步进次数过多(如>64 次);  优化:减少步进次数到 32 次以下,或根据距离动态调整(近处多步进,远处少步进); 原因 2:未做 “提前退出” 优化;  排查:在光线步进循环中添加if (totalTransmittance < 0.01) break,透过率过低时终止计算; 原因 3:体积光分辨率与屏幕一致;  优化:使用低于屏幕分辨率的离屏目标渲染体积光(如 0.5 倍分辨率),再放大到屏幕,减少采样像素数。

6.1.3 问题 3:光照烘焙后阴影缺失 / 错误

现象:烘焙后的 Lightmap 中,物体阴影缺失或位置错误;原因 1:静态物体未标记userData.isStatic = true;  排查:确保所有需要烘焙的物体标记为静态,否则烘焙工具不会计算其光照; 原因 2:Lightmap UV 重叠;  排查:使用 Blender 等工具为物体展开不重叠的 UV,Three.js 的LightBaker对 UV 重叠的物体烘焙效果差; 原因 3:采样点数量过少(<16 次);  优化:增加烘焙采样点数量(如 32 次),但会延长烘焙时间。

6.2 性能优化技巧

6.2.1 1. 分级渲染(按距离 / 精度动态调整)

PBR 分级:远处物体使用基础 PBR(Blinn-Phong),近处物体使用 GGX+SSS,平衡质感与性能;体积光分级:近处使用 32 次步进,远处使用 16 次步进,或远处关闭体积光;光照烘焙分级:远处静态物体使用低分辨率 Lightmap(512x512),近处使用高分辨率(2048x2048)。

6.2.2 2. 硬件加速与 API 优化

使用 WebGL 2.0:WebGL 2.0 支持OES_texture_float_linear等扩展,优化纹理采样性能,同时支持实例化渲染,减少 Draw Call;GPU 并行计算:将部分光照计算(如 GGX 的 NDF)用 GPU 并行执行,避免 CPU 参与;减少 CPU→GPU 数据传递:将光照参数(如区域光采样点)预计算后存储到纹理,而非每次渲染传递 uniform。

6.2.3 3. 效果与性能的平衡

简化光照模型:非关键场景用 “直接光照” 替代 “直接 + 间接光照”,关闭全局光照;降低纹理分辨率:体积光的噪声纹理用 256x256,Lightmap 根据物体大小调整分辨率,避免过度消耗显存;关闭非必要效果:低性能设备关闭 SSS、体积光等消耗高的效果,仅保留基础 PBR 和软阴影。

第七章 总结与后续学习方向

7.1 本章核心知识点回顾

高级光照定义:超越基础光照的局限,通过 PBR 进阶(GGX、SSS)、真实光源(区域光)、体积光、光照烘焙,实现照片级真实感;核心技术逻辑:  GGX:通过 NDF + 几何遮蔽 + 菲涅尔,模拟真实微表面高光;SSS:屏幕空间采样周围像素,模拟通透材质的散射;区域光:多采样点平均,生成软阴影和渐变光照;体积光:光线步进 + 噪声采样,模拟丁达尔效应;光照烘焙:离线计算光照到 Lightmap,优化静态场景性能; 实战关键:所有高级光照需平衡 “真实感” 与 “性能”,通过分级渲染、硬件优化、效果简化实现高效渲染。

7.2 后续学习方向

掌握高级光照基础后,可按以下路径深入,实现更复杂的次世代渲染效果:

全局光照(GI)

学习 “实时全局光照(Real-Time GI)” 技术(如 Voxel GI、Light Propagation Volumes),模拟光线在场景中的多次反射(如红色墙壁反射红色光到周围物体);掌握 “烘焙全局光照”,实现静态场景的间接光照细节(如房间角落的柔和光照)。

实时光线追踪(RT)

学习 WebGL 的光线追踪扩展(如WEBGL_ray_tracing),实现完全基于物理的光线交互(如镜面反射、折射、软阴影);探索 Three.js 的RaytracingRenderer,实现影视级的真实渲染效果。

材质与光照的深度耦合

学习 “分层材质”(如金属镀膜、透明涂层),实现更复杂的材质光照交互(如带清漆的汽车漆面);掌握 “材质实例化”,为不同物体分配差异化的光照响应参数(如金属、布料、皮肤的光照模型区分)。

跨平台与低性能优化

学习 “自适应渲染”,根据设备性能动态调整光照效果(如手机端关闭体积光,PC 端开启);探索 “WebGPU” 的高级光照 API,WebGPU 的并行计算能力更强,可实现更复杂的光照效果(如大规模体积光)。

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部