每日编程实践: Screen Space Ambient Occlusion (SSAO)
背景与动机为什么需要环境光遮蔽?在传统 Phong/Blinn-Phong 光照模型中,环境光(Ambient)是一个常量——整个场景里所有点的环境光照贡献完全相同,不管这个点是在空旷的平台中央,还是被夹在两堵墙的角落里。这在物理上显然是错误的:角落里的点被周围几何体遮挡,从半球方向射来的间接光理应更少。 没有环境光遮蔽的场景会显得”扁平”、”塑料感强”——所有表面的暗部都亮得不自然。加上 AO 之后,角落变暗、物体底部变暗、缝隙变暗,整体立体感和质感都会有质的提升。 工业界的实际使用场景Ambient Occlusion 的概念最早由 Zhukov 等人在 1998 年提出,但直到 Crytek 在 2007 年 GDC 上介绍 Screen Space Ambient Occlusion (SSAO) 之后才真正走入实时渲染的主流——当时是为了给《孤岛危机》服务的。 今天,AO 技术在各大引擎里几乎无处不在: Unity:有 SSAO(URP/HDRP 均支持),HDRP 还有基于射线的 RTAO Unreal Engine:内置 SSAO,后来又加了 ...
每日编程实践: Subsurface Scattering Renderer(次表面散射渲染器)
每日编程实践 Day 24/03: Subsurface Scattering Renderer今天实现了 次表面散射(Subsurface Scattering, SSS) 渲染器。SSS 是让皮肤、玉石、蜡烛等半透明材质看起来真实的关键技术。在此之前,我们用 Lambertian 漫反射模型渲染皮肤,结果往往显得”塑料感十足”——因为真实皮肤的光不是在表面反射的,而是钻进去、在内部散射一番再从另一个地方射出来。这正是 SSS 的精髓。 一、背景与动机——为什么普通漫反射不够用1.1 漫反射模型的假设标准的 Lambert/Phong/PBR 光照模型有一个隐含假设:光在表面某一点入射,就在那一点反射。用数学语言说,BRDF(双向反射分布函数)描述的是一个纯粹的表面现象: 1Lo(xo, ωo) = ∫ f_r(xo, ωi, ωo) · Li(xo, ωi) · cosθi dωi 注意,入射点和出射点是同一个点 xo。这个假设对金属、塑料、光滑漆面非常准确,但对皮肤、大理石、玉石、蜡烛、牛奶却完全错了。 1.2 光在皮肤里发生了什么皮肤是...
每日编程实践: Voxel Cone Tracing — 用体素锥形追踪实现近似全局光照
一、背景与动机全局光照的工程困境光线追踪可以精确计算全局光照(Global Illumination,GI),但在实时渲染场景下代价极高。渲染一帧 1080P 图像,路径追踪往往需要每像素几十甚至上百条路径才能收敛——这对于 60FPS 的游戏来说显然不现实。 游戏引擎里用了哪些方案来”作弊”? 光照贴图(Lightmap):离线烘焙,无法响应动态物体 Irradiance Volume(光照探针):动态性好,但空间分辨率有限 SSAO(屏幕空间环境光遮蔽):只用屏幕信息,遮蔽范围受限 Lumen(UE5):混合 SDF 追踪 + Radiance Cache,非常复杂 2011 年,Cyril Crassin 等人提出了 Voxel Cone Tracing(VCT),发表在 SIGGRAPH 论文”Interactive Indirect Illumination Using Voxel Cone Tracing”中。VCT 的核心思想是: 把场景体素化成一个带 Mipmap 的 3D 纹理,然后在着色时沿锥形方向”扫描”这个 3D 纹理,近似计算来自各方向的间接辐射度...
每日编程实践: 双向路径追踪 (BDPT) 与多重重要性采样
背景与动机为什么单向路径追踪不够用?如果你用过标准路径追踪(Unidirectional Path Tracing, PT),一定注意到一个令人头疼的现象:在某些场景里,即使采样数很高,画面还是充满噪点。具体来说,当光源面积小、场景几何复杂、或者存在”焦散”效果时,单向路径追踪的收敛速度极其缓慢。 为什么? 让我们想象一个典型的焦散场景:光线从天花板射下,经过一个玻璃球折射,在地面上形成一个亮斑。单向路径追踪从相机出发,光线碰到地面,想估计这个点的直接光照——它需要对天花板上的面光源采样,但光路是”光源 → 玻璃球折射 → 地面”,直接连接相机路径顶点和光源的概率极低(玻璃球的折射形成了很窄的采样空间),绝大多数样本的贡献是零。这就是所谓的**难以捕获的光路(difficult light transport path)**问题。 再看几个工业界的真实痛点: 室内渲染:小窗户进来的阳光,单向 PT 需要极多样本才能收敛,而建筑可视化行业的标准渲染时间往往只有几分钟 珠宝渲染:宝石刻面形成的复杂折射焦散,游戏里的宝石看起来总是”假”,根本原因就是这里 汽车内饰:阳光穿过车窗的散射...
每日编程实践: SPPM 随机渐进光子映射
① 背景与动机为什么需要全局光照?在实时渲染的世界里,我们长期依赖”直接光照 + 环境光假设”来模拟光照效果。这类方法速度快,但有一个根本性缺陷:它忽略了光线在场景中多次弹射的间接照明。 想象一个 Cornell Box——一个封闭的盒子,左墙红色,右墙绿色,天花板有一盏灯。在纯直接光照渲染中,你会看到墙面上均匀涂抹的红色和绿色;但在现实世界(或使用全局光照的渲染器)里,红墙的颜色会”流血”到白色地板和天花板上——这就是颜色渗色(Color Bleeding),是全局光照的典型特征。 此外,当场景中有镜面反射物体或玻璃时,光线可能经过多次折射/反射后才照亮某个漫反射表面。直接光照模型根本无法处理这类焦散(Caustics)效果——例如玻璃球下方那一圈明亮的光斑。 全局光照真正解决的问题: 漫反射间接照明(颜色渗色、角落变暗) 焦散(通过玻璃/镜面聚焦的光) 柔和阴影的物理正确计算 色调一致性(整个场景的光能守恒) 工业界实际应用全局光照在以下场景中有大量实际应用: 离线渲染(影视/动画): Pixar 的 RenderMan 使用路径追踪 + ...
每日编程实践: Spherical Harmonics 球谐环境光照
一、背景与动机:为什么需要球谐函数?环境光照的困境实时渲染有一道绕不过去的坎:如何高效地表示整个球面上的环境光照,并在每个着色点快速计算它对漫反射的贡献? 最直接的办法是蒙特卡洛积分——对每个着色点,在半球上采样数百个方向,取环境光加权平均: $$E(n) = \int_{\Omega} L(\omega) \max(0, n \cdot \omega) , d\omega \approx \frac{1}{N} \sum_{i=1}^{N} L(\omega_i) \max(0, n \cdot \omega_i)$$ 问题在于:1080P 下有两百万像素,每像素哪怕只采样 64 次,每帧就要做 1.28 亿次环境贴图采样。这在实时场景根本行不通。 工业界的标准答案:球谐函数UE4/UE5 的 Indirect Lighting Cache、Sky Light 全都基于 L2 球谐函数(9个系数)。Unity 的 Light Probes 同样如此。寒霜引擎、Frostbite 也把 SH 作为环境漫反射光照的核心表示。 原因很简单:Lambert...
每日编程实践: Forward+ Rendering (前向+渲染)
Forward+ Rendering(前向+渲染)为什么需要 Forward+?在实时渲染中,光源数量是一个经典的性能瓶颈。假设场景里有 N 个光源、M 个像素: 朴素前向渲染:每个像素都要遍历所有 N 个光源,复杂度 O(M × N) 现代游戏:N 可以轻松达到几十甚至数百个动态光源(手电筒、枪口火焰、爆炸特效……) 当 N 很大时,朴素方案直接崩溃。延迟渲染(Deferred Rendering) 是一种解法,但它无法处理透明物体,且 G-Buffer 内存压力大。 Forward+(也叫 Tiled Forward Rendering) 是另一条路: 核心思想:把屏幕切成小格子(Tile),对每个格子预先算出”哪些光源影响这个格子”,渲染时每个像素只遍历自己格子里的那几个光源。 这样一来,只要每个 Tile 的光源数量 k 远小于 N,就能大幅减少计算量。Frostbite(战地系列引擎)、Unity HDRP、Unreal Engine 都用了这个思路。 三种方案横向对比 方案 每像素光源遍历 透明物体 内存开销 适合场景 朴素前向渲染 全部 N 个 ...
每日编程实践: Deferred Shading Renderer 延迟渲染管线
Deferred Shading Renderer 延迟渲染管线背景:前向渲染的瓶颈在图形渲染的发展历史上,前向渲染(Forward Rendering) 是最直观的做法——每个几何体对每个光源分别着色,最终叠加。这在光源数量少的情况下工作得很好,但当你想要一个现代游戏引擎级别的场景——数百个动态点光源、面积光、体积光——前向渲染的复杂度就变成了不可接受的瓶颈。 复杂度分析(前向 vs 延迟): 方案 每帧着色次数 典型场景 (1000球 × 100光) 前向渲染 O(geometry × lights) 100,000 次着色 延迟渲染 O(pixels × lights) 800×600 × 有效光 ≈ 按覆盖范围 Tiled Deferred O(pixels × 局部光源) 现代 AAA 标准方案 前向渲染还有另一个更隐蔽的问题:Overdraw。当多个物体重叠时,被遮挡的物体也会被着色,然后深度测试丢弃——计算量全部浪费。延迟渲染通过”先确定可见性,再着色”彻底解决了这个问题。 延迟渲染的代价是显存带宽(G-Buffer 需要存储多张全分辨率纹理...
每日编程实践: LTC Area Light Rendering 面光源渲染
LTC Area Light Rendering - 面光源渲染今天实现了基于 线性变换余弦(Linearly Transformed Cosines, LTC) 的矩形面光源实时渲染算法——源自 Heitz et al. SIGGRAPH 2016 的工作。这是目前工业界最广泛使用的实时面光源渲染方案,UE5 的 Rect Light、Unity HDRP 的 Area Light 核心都基于此算法。 为什么面光源难以实时渲染?点光源的局限传统实时渲染使用点光源、方向光、聚光灯等”无面积”光源。这些光源的 BRDF 积分有解析解: 1L_o(x, ω_o) = f_r(x, ω_i, ω_o) · L_i · cos(θ_i) 因为光源退化为单一方向 ω_i,积分消失了,只剩一次 BRDF 求值。 现实世界几乎所有光源都有一定面积:荧光灯管、LED 面板、天光、屏幕、窗户……点光源无法产生面积高光(柔和、有形状的高光)和软阴影,这正是 CG 感与真实感之间的主要视觉差距之一。 面光源的积分问题对于面积光源,BRDF 积分变成: 1L_o(x, ω_o) = ∫_A f_r...
每日编程实践: SSR - Screen Space Reflections 屏幕空间反射
SSR - Screen Space Reflections 屏幕空间反射今天实现了 SSR(Screen Space Reflections)——现代实时渲染中用于平面/光滑表面动态反射的核心技术,在 UE、Unity HDRP、Frostbite、寒霜引擎等 AAA 管线中普遍使用。 与 SSAO(昨天实现)同属”屏幕空间”系列——都复用 G-Buffer,在 2D 屏幕空间完成计算,避免了构建加速结构(BVH)的开销,是实时渲染的核心工程权衡。 为什么需要 SSR?传统反射方案的局限在 SSR 出现之前,实时反射主要有三种方案: 1. Cubemap 反射将场景预渲染到六个面,以立方体贴图形式存储: ✅ 极快(一次纹理采样) ❌ 只能反射静态场景,动态物体无法正确反射 ❌ 反射是全向模糊近似,看不出精确细节 2. Planar Reflection(平面反射)为每个镜面创建一个额外的渲染 Pass,将场景从反射相机角度重新渲染: ✅ 质量完美,精确到像素 ❌ DrawCall 翻倍,每面镜子 = 多一次完整场景渲染 ❌ 只适合平面,球面...















