目录
前言
第一章 高级光照基础认知:从 “理想化” 到 “真实化”
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 核心逻辑,跳过复杂底层配置,确保零基础用户能边学边改,直观感受 “高级光照对真实感的提升”。掌握本文内容后,你将摆脱 “卡通化”“扁平化” 的渲染效果,迈入次世代高级渲染的核心领域。
在深入技术细节前,需先明确 “高级光照” 的核心定位 —— 它不是单一技术,而是一套 “还原真实光照物理规律” 的技术集合,其本质是解决基础光照的 “真实感缺失” 问题。
高级光照是指超越 “点光源 / 平行光 + Lambert/Phong 模型” 的局限,通过模拟真实世界光照物理特性(如微表面反射、光线散射、区域光照射),实现高真实感渲染的技术体系。其核心目标是 “让光线与物体的交互更贴近现实”,而非基础光照的 “简化计算”。
类比现实世界:基础光照像 “卡通画”(用简单色块表现明暗),高级光照像 “写实照片”(还原金属的高光细节、烟雾的光线穿透效果)。
二者在 “光照模型、光线来源、真实感、性能” 上存在本质区别,是零基础用户最易混淆的点,具体对比如下:
对比维度 | 基础光照(如 Lambert/Phong) | 高级光照(如 PBR 进阶 / 体积光 / 区域光) |
---|---|---|
光照模型 | 简化模型(忽略微表面、能量守恒) | 物理基于模型(还原微表面、能量守恒、菲涅尔效应) |
光线来源 | 理想化光源(点光源、平行光,无体积) | 真实光源(区域光、体积光,有大小 / 形状) |
阴影效果 | 硬阴影(边缘锐利,无过渡) | 软阴影(边缘柔和,有渐变) |
材质交互 | 单一材质响应(如金属 / 非金属无明确区分) | 材质差异化响应(金属拉丝高光、布料漫反射柔和) |
环境交互 | 无间接光照(仅直接光照,暗部纯黑) | 支持间接光照(环境光反射、全局光照) |
真实感 | 低(卡通化、扁平化) | 高(照片级、写实) |
性能消耗 | 低(计算简单,适合低性能设备) | 高(复杂采样、积分计算,需 GPU 优化) |
适用场景 | 2D 游戏、简单 3D 场景、低性能设备 | 3A 游戏、影视渲染、元宇宙、高精度可视化 |
高级光照并非单一技术,而是由四大核心技术构成,覆盖 “材质交互→光源模拟→环境交互→性能优化”,具体如下:
技术类别 | 核心技术点 | 解决的问题 | 应用场景 |
---|---|---|---|
PBR 进阶 | GGX 微表面模型、Disney BRDF、次表面散射(SSS) | 基础 PBR 质感不足(如金属高光不锐利、皮肤不通透) | 角色皮肤、金属材质、布料渲染 |
真实光源模拟 | 区域光、聚光灯软边缘、IES 光域网 | 基础光源太理想化(无体积、阴影硬) | 室内照明(如台灯、吊灯)、舞台灯光 |
体积光与散射 | 光线步进(Ray Marching)、米氏散射、瑞利散射 | 无体积光效果(如阳光穿过烟雾、雾气) | 自然场景(雾、雨、烟雾)、科幻场景(光束) |
光照贴图与烘焙 | Lightmap(光照贴图)、Light Probe(光照探针) | 实时高级光照性能不足(帧率低) | 静态场景(室内、建筑)、开放世界 |
基础 PBR(金属度 / 粗糙度模型)虽能区分金属与非金属,但无法模拟 “拉丝金属的定向高光”“皮肤的通透感” 等细腻质感 ——PBR 进阶通过 “GGX 微表面模型”“次表面散射” 等技术,解决这些真实感缺失问题。
基础 PBR 用 “Blinn-Phong 模型” 计算高光,其高光形状过于 “圆润”,不符合真实金属的 “锐利、定向” 高光;而GGX 微表面模型通过更贴近真实的 “法线分布函数(NDF)”,模拟微表面的随机分布,生成更锐利、更真实的高光。
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
(法线与半程向量夹角):夹角越小,概率越高,高光越集中在半程向量方向(符合真实光照规律)。
基于 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>
运行效果:金色金属球表面呈现 “锐利的定向高光”,随相机角度变化,高光位置和强度动态调整(掠射时反射增强,垂直观察时反射减弱),完全符合真实抛光金属的质感;若将uRoughness
改为 0.8(磨砂),高光会变得分散柔和,模拟磨砂金属效果;核心优势:GGX 通过 “NDF(高光形状)+ 几何遮蔽(高光衰减)+ 菲涅尔(角度反射)” 三函数耦合,还原真实微表面的光照交互,这是基础 Phong 模型无法实现的。
基础 PBR 无法模拟 “皮肤、蜡、玉石” 等 “半透明、通透” 材质的质感(如阳光照射下皮肤的红润感),而次表面散射(Subsurface Scattering,SSS) 通过模拟 “光线穿透物体表面后在内部散射,再从其他位置射出” 的过程,实现通透效果。
真实世界中,光线与通透材质的交互分为三步:
光线穿透物体表面(部分被反射,部分折射进入内部);光线在物体内部与粒子碰撞,发生多次散射(方向改变);散射后的光线从物体其他位置射出,形成 “通透感”。
WEBGL 中实现 SSS 的简化方案是 “屏幕空间次表面散射(Screen Space SSS,SSSS) ”:在片元着色器中,采样当前像素周围的像素颜色,按 “散射距离” 加权混合,模拟内部散射效果。
基于 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>
运行效果:球体(模拟人脸)在光源照射侧呈现 “红润的通透感”,暗部过渡柔和,完全区别于 “无 SSS 时的哑光质感”,类似真实皮肤在阳光下的效果;调整uSSSStrength
可控制通透程度(0 = 无 SSS,1 = 过度通透);核心逻辑:通过 “离屏渲染 + 屏幕空间采样”,模拟光线在皮肤内部的散射 —— 沿光源方向采样周围像素,加权混合后与原始颜色叠加,实现 “光线从光源侧穿透到另一侧” 的通透效果。
基础光照的 “点光源 / 平行光” 是 “无体积、无形状” 的理想化光源,而真实世界的光源(如台灯、窗户)都有 “体积和形状”,会产生 “软阴影”“光照渐变” 等效果 —— 高级光照通过 “区域光” 模拟这些真实光源特性。
矩形区域光是模拟 “窗户、LED 面板灯” 等矩形光源的核心技术,其核心是 “在光源矩形范围内随机采样多个点光源,将多个点光源的光照结果平均,生成软阴影和渐变光照”。
光源定义:用 “位置(position)、大小(width/height)、方向(normal)” 定义矩形光源(如窗户的位置、尺寸、朝向);随机采样:在矩形范围内随机生成 N 个采样点(N 越多,软阴影越平滑,但性能消耗越高);光照计算:对每个采样点,按点光源计算光照(方向、距离、阴影),最后将 N 个结果平均,得到区域光的最终光照。
基于 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>
运行效果:暖黄色的矩形区域光照射立方体和地面,立方体的阴影边缘呈现 “柔和的渐变”(而非基础光的硬边缘),光照从光源中心向边缘逐渐衰减,完全模拟真实窗户光照的效果;调整sampleCount
(采样点数量),采样点越多,阴影越平滑,但帧率会下降;核心优势:通过 “多采样点平均”,解决基础光源 “硬阴影” 的真实感缺失问题,是室内场景、舞台灯光等真实光源模拟的核心技术;优化方向:案例中阴影为简化版(无遮挡检测),真实场景需结合 “阴影贴图(Shadow Map)” 或 “光线追踪” 实现遮挡检测,Three.js 的RectAreaLight
类已封装完整逻辑,可直接调用。
基础光照仅能模拟 “光线照射物体产生明暗”,但无法模拟 “光线在空气中的可见效果”(如阳光穿过烟雾、手电筒光束)——体积光(Volumetric Light) 通过模拟 “光线与空气粒子的散射”,让光线本身成为 “可见的渲染对象”,是实现 “丁达尔效应”“雾中光束” 的核心技术。
体积光的本质是 “光线在体积介质(如雾、烟雾)中传播时,与粒子碰撞产生的散射光”,WEBGL 中通过 “光线步进(Ray Marching)” 技术实现:
光线生成:从相机出发,向屏幕每个像素发射一条光线;步进采样:将光线分为多个 “步进段”,在每个步进点采样体积介质的密度(如烟雾浓度);散射计算:若步进点有体积介质,计算光线在该点的散射光(向相机方向发射),叠加到最终颜色;衰减计算:光线穿过体积介质时,强度会因吸收而衰减,距离越远,衰减越严重。
基于 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>
运行效果:深色背景中,暖黄色的光束从光源位置向远处延伸,光束内部有 “烟雾状的纹理”(噪声纹理模拟),随相机角度变化,光束的形状和亮度动态调整,完全模拟真实阳光穿过烟雾的丁达尔效应;调整uDensity
(密度),密度越大,光束越浓;核心逻辑:通过 “光线步进 + 噪声纹理采样”,模拟光线与烟雾粒子的散射 —— 在光线的每个步进点,采样烟雾密度,计算散射光并叠加,同时考虑光线衰减,实现 “光束可见” 的效果;性能优化:通过 “提前退出”(透过率<0.01 时终止步进)和 “减少步进次数”(如 32 次),平衡效果与性能,避免帧率过低。
高级光照(如 PBR、体积光)的计算复杂度高,实时渲染时易导致帧率下降 ——光照贴图(Lightmap) 与光照烘焙(Light Baking) 通过 “离线计算光照结果,存储到纹理中,实时渲染时直接采样纹理” 的方式,大幅降低实时计算压力,是静态场景(如室内、建筑)的核心性能优化技术。
光照烘焙是 “离线计算场景中所有静态物体的光照结果,将结果存储到光照贴图(Lightmap)中,实时渲染时物体直接采样 Lightmap 获取光照颜色,无需实时计算光照” 的技术,流程如下:
标记静态物体:指定场景中不移动的物体(如墙壁、地面、家具)为 “静态物体”;离线烘焙:用烘焙工具(如 Blender、Three.js 的LightBaker
)计算静态物体的光照(包括直接光照、间接光照、阴影);生成 Lightmap:将烘焙后的光照颜色存储到 2D 纹理(Lightmap)中,每个物体的 UV 映射到 Lightmap 的对应区域;实时渲染:静态物体的材质采样 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>
运行效果:页面加载后,控制台显示 “开始烘焙→烘焙完成”,烘焙完成后,静态物体(地面、墙壁、桌子)呈现 “柔和的光照和阴影”(如桌子在地面的阴影、墙壁的光照渐变),动态立方体(蓝色)旋转时仍有实时光照;对比 “烘焙前”(实时计算光照,帧率可能较低)和 “烘焙后”(采样 Lightmap,帧率显著提升),性能差异明显;核心优势:将静态物体的光照计算从 “实时” 转移到 “离线”,实时渲染时仅需采样纹理,CPU/GPU 压力大幅降低,适合大规模静态场景(如建筑可视化、开放世界的静态地形);注意事项:Lightmap 分辨率需平衡 “细腻度” 与 “内存”(1024x1024 足够多数场景),采样点数量越多,烘焙时间越长,但光照越平滑。
高级光照的实现复杂度高,易出现 “质感异常”“性能卡顿”“效果不符合预期” 等问题,本节提供针对性的排查步骤与优化技巧,帮助零基础用户快速解决问题。
现象:金属材质的 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))
校正,否则高光会过亮。
现象:体积光场景帧率低,画面卡顿;原因 1:光线步进次数过多(如>64 次);
优化:减少步进次数到 32 次以下,或根据距离动态调整(近处多步进,远处少步进); 原因 2:未做 “提前退出” 优化;
排查:在光线步进循环中添加if (totalTransmittance < 0.01) break
,透过率过低时终止计算; 原因 3:体积光分辨率与屏幕一致;
优化:使用低于屏幕分辨率的离屏目标渲染体积光(如 0.5 倍分辨率),再放大到屏幕,减少采样像素数。
现象:烘焙后的 Lightmap 中,物体阴影缺失或位置错误;原因 1:静态物体未标记userData.isStatic = true
;
排查:确保所有需要烘焙的物体标记为静态,否则烘焙工具不会计算其光照; 原因 2:Lightmap UV 重叠;
排查:使用 Blender 等工具为物体展开不重叠的 UV,Three.js 的LightBaker
对 UV 重叠的物体烘焙效果差; 原因 3:采样点数量过少(<16 次);
优化:增加烘焙采样点数量(如 32 次),但会延长烘焙时间。
PBR 分级:远处物体使用基础 PBR(Blinn-Phong),近处物体使用 GGX+SSS,平衡质感与性能;体积光分级:近处使用 32 次步进,远处使用 16 次步进,或远处关闭体积光;光照烘焙分级:远处静态物体使用低分辨率 Lightmap(512x512),近处使用高分辨率(2048x2048)。
使用 WebGL 2.0:WebGL 2.0 支持OES_texture_float_linear
等扩展,优化纹理采样性能,同时支持实例化渲染,减少 Draw Call;GPU 并行计算:将部分光照计算(如 GGX 的 NDF)用 GPU 并行执行,避免 CPU 参与;减少 CPU→GPU 数据传递:将光照参数(如区域光采样点)预计算后存储到纹理,而非每次渲染传递 uniform。
简化光照模型:非关键场景用 “直接光照” 替代 “直接 + 间接光照”,关闭全局光照;降低纹理分辨率:体积光的噪声纹理用 256x256,Lightmap 根据物体大小调整分辨率,避免过度消耗显存;关闭非必要效果:低性能设备关闭 SSS、体积光等消耗高的效果,仅保留基础 PBR 和软阴影。
高级光照定义:超越基础光照的局限,通过 PBR 进阶(GGX、SSS)、真实光源(区域光)、体积光、光照烘焙,实现照片级真实感;核心技术逻辑: GGX:通过 NDF + 几何遮蔽 + 菲涅尔,模拟真实微表面高光;SSS:屏幕空间采样周围像素,模拟通透材质的散射;区域光:多采样点平均,生成软阴影和渐变光照;体积光:光线步进 + 噪声采样,模拟丁达尔效应;光照烘焙:离线计算光照到 Lightmap,优化静态场景性能; 实战关键:所有高级光照需平衡 “真实感” 与 “性能”,通过分级渲染、硬件优化、效果简化实现高效渲染。
掌握高级光照基础后,可按以下路径深入,实现更复杂的次世代渲染效果:
全局光照(GI):
学习 “实时全局光照(Real-Time GI)” 技术(如 Voxel GI、Light Propagation Volumes),模拟光线在场景中的多次反射(如红色墙壁反射红色光到周围物体);掌握 “烘焙全局光照”,实现静态场景的间接光照细节(如房间角落的柔和光照)。
实时光线追踪(RT):
学习 WebGL 的光线追踪扩展(如WEBGL_ray_tracing
),实现完全基于物理的光线交互(如镜面反射、折射、软阴影);探索 Three.js 的RaytracingRenderer
,实现影视级的真实渲染效果。
材质与光照的深度耦合:
学习 “分层材质”(如金属镀膜、透明涂层),实现更复杂的材质光照交互(如带清漆的汽车漆面);掌握 “材质实例化”,为不同物体分配差异化的光照响应参数(如金属、布料、皮肤的光照模型区分)。
跨平台与低性能优化:
学习 “自适应渲染”,根据设备性能动态调整光照效果(如手机端关闭体积光,PC 端开启);探索 “WebGPU” 的高级光照 API,WebGPU 的并行计算能力更强,可实现更复杂的光照效果(如大规模体积光)。