
HDR/LDR的IBL实现
对应Unity项目场景名:SimpleIBL
在 简易能量守恒光照模型 的基础上做的。
(美丽的磨砂金属感,是几天的努力换来的口牙)
一些参考
IBL相关文章: https://zhuanlan.zhihu.com/p/66518450
Youtube 教程 Freya Holmér 的 Normal Maps, Tangent Space & IBL • Shaders for Game Devs [Part 3]
@苏格拉没有底。的笔记
跑到 @苏格拉没有底。 那里看笔记,他还学了 Yt 的那个视频。结论是这个实践真超纲了。。。硬着头皮上呗
IBL
IBL就是用一张 环境贴图(通常为立方体贴图,Cubemap) 来模拟整个场景对物体的光照,替代之前用的那个常数 UNITY_LIGHTMODEL_AMBIENT。
- 漫反射IBL:计算来自环境各个方向的间接漫反射光。通常预计算为一张较模糊的辐照度图。
- 镜面反射IBL:计算来自环境的镜面反射(即高光)。由于它随粗糙度变化剧烈,通常需要一套预滤波的Mipmap链和一张BRDF积分贴图。
预计算贴图
为了得到:
- Irradiance Map: 漫反射辐照度图
- Prefiltered Env Map: 预滤波环境图
- BRDF LUT: BRDF查找表
- Unity 的 URP 和 HDRP 中有内置的IBL贴图生成功能
- 使用C#写个工具脚本(本次实践选用)
- 使用Compute Shader(高级)
- 第三方工具
.dds是在图形程序中被广泛使用的格式。它相比 .hdr 的优势主要体现在以下几个方面:
- 原生支持Mip链:
.dds文件可以预生成并存储完整的mip链。这意味着你的_PrefilteredEnvMap的模糊层级是直接写在文件里的,无需引擎在运行时额外计算,加载后就能直接使用,从而节省了运行时的开销和纹理内存。 - GPU硬件压缩:
.dds支持GPU硬件直接解码的压缩格式(例如 DXT1/BC1 等)。这使得它无论在磁盘上还是内存中,所占用的空间都远小于.hdr文件。同时,在渲染时GPU可以快速解压,极大提升了加载和渲染效率。 - 原生立方体贴图支持:
.dds格式原生支持存储立方体贴图,因此用它保存的贴图能直接被 Unity 识别和使用,无需额外配置。 - 保留高动态范围数据:这一点你可能会有些意外,
.dds本身也完全支持高动态范围数据。对于需要高精度 HDR 信息的 PBR 流程,可以将 cmftStudio 输出的格式设置为R16G16B16A16_FLOAT或R32G32B32A32_FLOAT。这样既保留了.dds的所有性能优势,又能获得足够精度。
而如果直接使用.hdr格式,就难以兼顾上面的优势了。因为PrefilteredEnvMap的核心在于其 mip 链,而.hdr文件本身无法原生包含 mip 信息。虽然引擎可以在加载时动态生成 mip,但这会增加加载耗时,并且无法保证生成的 mip 质量与 cmftStudio 这类离线工具的精确滤波效果完全一致。此外,如果操作不当,还可能出现HDR 范围信息丢失或数值被钳位(clamp)的问题,导致高光部分过曝或细节缺失
8位整数通道。这是低动态范围(LDR)格式,颜色会发灰,亮部也缺乏层次,会让 PBR 材质看起来“很平”。
所以我选择 RGBA16F.(再往上还有RGBA32F)
使用BRDF LUT小贴士
- 记得把伽马矫正关了,因为要直接用数值。Unity 就是取消 sRGB 空间的勾选。
- Repeat Mode 设置为 Clamp 以免边缘异常。
- 发现贴图采样异常记得看看Property里的和着色器里变量名一不一样。改完记得重新拖放。

(图中为 BRDF LUT在不同粗糙度上的结果)
别问,问就是教训。
两个菲涅尔项 F
- 直接光照
- 拥有信息:明确的光线方向
L和视线方向V。 - 计算方式:可以精确计算半角向量
H = normalize(L + V),并使用VdotH来计算当前这一束光在当前这个微平面上的菲涅尔反射率F。这个F同时用于计算镜面反射BRDF和漫反射比例kD,确保了能量在该束光的镜面与漫反射分量间正确分配。
- 拥有信息:明确的光线方向
- 基于图像的光照 (IBL)
信息缺失:IBL没有单一的、明确的光线方向
L。它代表来自整个半球的所有可能入射光线的积分。核心问题:我们无法为环境光计算出一个通用的
H和VdotH。解决方案:采用被称为 “分割和近似” (Split Sum Approximation) 的方法。它将镜面反射IBL的积分拆解为两部分,并分别进行预计算或近似。
fresnelSchlickRoughness函数正是这个近似的一部分。
下面这些看起来很难,确实不简单。知道这三个贴图就够了。这几张纹理的生成卡了我好久,最终放弃自己鼓捣这个代码了。使用 cmftStudio得到辐照图和预过滤图。(风扇转冒烟了)BRDF LUT 是从 LearnOpenGL 网站上下载的 512*512 的 PNG。
*IBL的计算
对于 Cook-Torrance 模型,我们要解的方程为:(已省略中间过程)
方程拆成了两部分,漫反射项和镜面反射项。
离线渲染中我们可以用蒙特卡洛积分法去求解这个积分值,但计算量很大。所以实时渲染中面临的问题就是如何快速计算。
主要思路就是预计算,把复杂的积分都先算好。我们会分别预计算漫反射项和镜面项,最终在实时渲染中只需通过简单的纹理采样即可得到结果。
Irradiance Map: 漫反射辐照度图
原作者是个数学硕士,所以我就不深究他的公式推导了。这里有他写的 代码。
积分方法就是蒙特卡洛积分,我们可以简单地在半球面上均匀采样。
将放入irradiance map中,使用时就不用再除以 了
specular 镜面项
值取决于很多参数。最简单粗暴的方法是在实施渲染中直接使用蒙特卡洛积分求解,但是性能很差。虚幻给出了近似解决的方法 spilt sum approximation。
Pre-filtered Environment Map: 预滤波环境图
有两个变量,为粗糙度和 。对于一个特定的粗糙度,变量就只剩 ,因此我们可以像 irrandiance map 那样,预计算得到一个 cubemap。我们均匀的取粗糙度为 0, 0.25, 0.5, 0.75, 1.0,这样得到 5 个 cubemap。实时渲染时,就用 和粗糙度进行三线性插值。
这个预计算有时需要实时更新,那么快速计算就十分重要。这个预计算关键的就是计算积分。为了加速收敛,我们可以用重要性采样,但还是需要不少的样本。Krivanek 的 Pre-filtered importance sampling 可以减少样本数量,收敛提升明显,只引入了一小些偏差。
这个预计算可以用CPU或者GPU计算。原作者的 GLSL实现
BRDF LUT: BRDF查找表
我们将积分过程拆成了两部分:scale 和 bias(这俩是多项式),它们都消去了 F,因此变量只剩下 和粗糙度,为了方便,用做变量。scale 和 bias 可以根据法线分布函数进行重要性采样的蒙特卡洛积分来解决。
这两部分可以提前算好然后存在一个2D纹理中,也就是 BRDF LUT。
这是一张2D贴图,与具体的环境无关,是所有PBR材质共享的。也就是说,可以直接去下载一张png来用。
原作者 GLSL实现
ToneMapping
从 HDR 映射到 LDR。adapted_lum 是曝光参数,基于场景平均亮度可以做自适应曝光之类的。
- Reinhard:柔和,高光压缩平滑。
- CryEngine2:指数曲线,对比度较高。
- Filmic:S 形曲线,中间对比增强,高光柔和。
- ACES:电影级,色彩饱和度好,最常用。
懒了。就相信已有结论吧。此外,adapted_lum这个变量在不同的ToneMapping函数里的位置都不同,也不知道设为相同值时算不算“控制变量”。
做阶段性成果总结的时候会做一下。
代码(部分省略)
1 | Shader "Test/SimplePBR With IBL" { |
- 标题: HDR/LDR的IBL实现
- 作者: 铁名_IronName
- 创建于 : 2026-04-09 11:45:58
- 更新于 : 2026-04-18 11:02:52
- 链接: https://blog.ironname.top/2026/HDR-LDR的IBL实现/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
