《对马岛之魂》实时武士电影:光照、大气与色调映射
Real-Time Samurai Cinema: Lighting, Atmosphere, and Tonemapping in Ghost of Tsushima
项目背景与开发概况
- 开发商 :Sucker Punch Productions,自 2011 年起隶属于 Sony Interactive Entertainment ,工作室位于华盛顿州贝尔维尤(西雅图附近)
- 开发高峰期约 160 人 参与制作
- 前作包括 《怪盗史库柏》(Sly Cooper) 系列和 《恶名昭彰》(inFAMOUS) 系列
- 《对马岛之魂》于 2020 年夏季在 PS4 上发售,随后推出了免费合作多人扩展,并在 PS5 向下兼容模式 中支持 60 fps
- 游戏类型: 开放世界动作冒险 ,背景设定在 13 世纪封建日本 ,玩家扮演武士境井仁(Jin Sakai),抵抗蒙古入侵对马岛
相关技术演讲资源
- SIGGRAPH 上关于 基于物理的着色(Physically Based Shading) 的研究
- GDC 上关于 程序化草地(Procedural Grass) 、 世界构建与渲染 、 风与布料模拟 以及 快速加载 等多个专题
项目目标:时光机器与武士电影感
"时光机器" 体验
- 目标是将玩家沉浸在 1274 年的对马岛 中
- 需要创建一个比以往作品 大得多的动态开放世界 ,要求 美轮美奂且充满生机
- 首次引入 动态天气系统 和 连续的昼夜循环(Time of Day) 系统——这在之前的项目中从未做过
经典武士电影风格
- 致敬传奇电影人 黑泽明(Akira Kurosawa) ,游戏内置 "黑泽模式"
- 需要实现 戏剧性光照(Dramatic Lighting) 、 凛冽的风(Biting Wind) 以及 富有表现力的天空与大气效果
光照系统总览
艺术方向:风格化写实(Stylized Realism)
- 并非追求照片级真实(Photorealism) ,而是 风格化写实 ——物理上合理但允许艺术性偏离
- 艺术家可在光照阶段使用一组 可调旋钮(Knobs) 全局调整光照参数(如 环境光/漫反射/镜面反射平衡 、 天空亮度 等)
- 这些调整通常以 天气状态和时间段 的粒度进行设置
基于物理的光照模型
| 组件 | 具体方案 |
|---|---|
| 高光 BRDF | GGX 分布函数(GGX NDF) ,支持可选的 各向异性(Anisotropy) |
| 可见性项 | Smith 可见性函数(Smith Visibility Function) |
| 菲涅尔 | Schlick 菲涅尔近似(Schlick Fresnel Approximation) |
| 漫反射 | Lambertian 漫反射,支持可选的 能量守恒透射(Energy Conserving Translucency) 和 绒毛模型(Fuzziness) |
| 面光源 | 支持 能量守恒的面光源(Energy Conserving Area Lights) |
| 能量守恒 | 整体光照模型满足 能量守恒(Energy Conserving) |
材质系统
- 材质使用 物理上合理的参数(Physically Plausible Parameters) 进行创作
- 部分材质采用 摄影测量(Photogrammetry) 获取
- 物理合理的材质保证了 对光照的一致响应
光源单位
- 光源使用 物理单位(Physical Units) 进行设置
间接光照
- 基于当前天空和平行光(Directional Light)动态更新的 球谐函数(Spherical Harmonics, SH) 和 反射探针(Reflection Probes)
- 加上 局部光照传递数据(Local Light Transfer Data)
天空与大气
- 使用 基于物理的多重散射大气散射模型(Physically Based Multi-Scattering Atmospheric Scattering Model)
- 统一用于 云 、 雾霾(Haze) 和 粒子 的大气光照
渲染管线
- 场景在 HDR 下渲染,使用 自定义色调映射(Custom Tonemapping) 技术来达成目标视觉风格
演讲结构:三大主题
本演讲分为三大部分:
- 间接光照(Indirect Lighting) ——漫反射间接光照 + 镜面反射间接光照
- 大气体积光照(Atmospheric Volumetric Lighting) ——天空、云、体积雾霾、粒子
- 色调映射(Tonemapping) ——局部色调映射算子、白平衡色彩分级、自定义色调映射色彩空间、人眼低光视觉模拟(夜景)
间接漫反射光照(Indirect Diffuse Lighting)
前作方案:《恶名昭彰:次子》的做法
- 使用 二次辐照度探针(Quadratic Irradiance Probes) 排列在 四面体网格(Tetrahedral Meshes / Tet Meshes) 中,为整个世界提供间接漫反射光照
- 详见 Adrian Bentley 在 GDC 2014 的演讲
- 该方案在 静态昼夜和天气 条件下表现良好——天气/时间切换仅在 加载屏幕后面 发生
《对马岛之魂》的挑战与改进
由于需要照亮 更大的世界 ,且首次面对 动态昼夜与天气 ,必须对方案进行重大调整。
规则栅格探针(Regular Grid Probes)
- 在地面上以 12.5 米间隔 排列探针
- 每个位置在地面以上 1.5 米、10 米、30 米 三个高度层放置探针
- 演讲中展示了 24 小时昼夜循环 中全岛 9 个 SH 波段绝对值 的变化——在 日出和日落 时变化最剧烈
- 这里的 "9 个 SH 波段" 指的是 二阶球谐函数(L=0,1,2) 的 9 个系数,用于编码每个探针处的 辐照度(Irradiance) 信息
栅格探针的局限:室内照明
- 12.5 米的探针间距 太粗 ,无法精确照亮室内场景
解决方案:四面体网格叠加(Tet Mesh Override)
- 在 城镇、村落、农庄 等需要精细照明的地方,使用 四面体网格(Tet Meshes) 作为补充
- 这些四面体网格作为独立单元 随场景流式加载(Streamed)
- 加载后, 四面体网格会覆盖(Override)规则栅格 ,并在边界处进行 平滑混合(Blend at Boundary)
实际效果
- 演讲中展示了漫反射间接光照缓冲区的可视化,可以看到 规则栅格光照 与 四面体网格光照 之间存在 平滑过渡 ,无明显接缝
- 以游戏中的 "金阁寺"(Golden Temple) 场景为例进行了演示
关键技术总结
| 特性 | 规则栅格探针 | 四面体网格探针 |
|---|---|---|
| 覆盖范围 | 全岛 | 局部(城镇、村落等) |
| 间距 | 12.5 米 | 更密,适配室内 |
| 高度分布 | 1.5m / 10m / 30m 三层 | 自定义布局 |
| 加载方式 | 常驻 | 随区域流式加载 |
| 优先级 | 被 Tet Mesh 覆盖 | 覆盖栅格,边界平滑混合 |
| 适用场景 | 室外大范围 | 室内与半封闭空间 |
间接漫反射光照(Indirect Diffuse Lighting)
动态辐照度探针(Dynamic Irradiance Probes)
核心挑战
- 游戏拥有 动态昼夜循环 和 动态天气系统 ,因此辐照度探针必须在 运行时更新 ,不能完全依赖离线烘焙
离线 + 运行时的混合方案
- 离线阶段 :不直接捕获辐照度(irradiance),而是捕获每个探针位置的 天空可见性(Sky Visibility) ,以 球谐函数(SH) 形式存储
- 运行时阶段 :
- 计算当前天空光照的 SH 投影
- 将 天空亮度 SH × 天空可见性 SH ,得到该探针接收到的天空光
- 对结果进行 余弦瓣卷积(Cosine Lobe Convolution) ,与传统辐照度探针处理方式一致
- 最终像普通辐照度探针一样用于光照计算
优缺点
- 优点 :支持完全动态的天空光照更新
- 缺点 :只包含 直接天空光照(Direct Sky Lighting) ,没有来自环境的 反弹光(Bounce Light)
天空反弹光(Bounce Sky Lighting)
之前方案的局限
- 在 《恶名昭彰》(inFAMOUS) 中曾使用 辐射传递矩阵(Radiance Transfer Matrices) ,适用于小世界
- 但该方案需要 至少 9 倍的存储 ,在《对马岛》的开放世界中 内存消耗过大 ,且 捕获时间过长
近似方法:均匀天空假设
- 核心假设:天空光在 半球上近似均匀(Approximately Constant)
- 离线阶段 :
- 用 均匀白色天空 加上天空可见性 SH 对世界进行光照
- 将得到的反弹辐射投影到 SH,得到 反弹天空可见性(Bounce Sky Visibility)SH
- 运行时 :
- 将反弹天空可见性 SH 乘以 平均天空颜色(Average Sky Color)
- 加到之前的直接天空光照结果上
视觉效果示例
| 阶段 | 效果 |
|---|---|
| 仅使用天空 SH(无可见性) | 光照非常 平坦 ,没有遮蔽信息 |
| 加入 直接天空可见性 | 有了基本的遮蔽和方向感 |
| 再加入 反弹天空可见性 | 出现来自环境的 彩色反弹光 (如周围黄色树叶产生的黄色间接光) |
太阳反弹光(Sun Bounce Light)
动机
- 在 晴天 条件下,大部分反弹光来自 太阳 而非天空
- 需要将太阳反弹光加入模型,但 不希望存储额外数据
核心思路:复用反弹天空可见性
- 考虑一组 固定的少量反弹方向(Bounce Directions) ,效果优于假设反弹光在球面上均匀分布
反弹方向的选择
- 观察发现:实际中大部分反弹光来自 水平面(地面) 或 垂直面(墙面)
- 使用 虚拟地面平面 和 虚拟墙面平面 对光线方向进行反射
- 地面反射方向 和墙面反射方向 恰好指向 相反方向
- 将 取反后的反射光方向 投影到 SH,并进行 去振铃(De-ringing) 确保处处非负
计算流程
- 云层遮蔽(Cloud Shadowing) 是动态的,不包含在离线捕获的天空可见性中,因此在运行时单独应用
- 将结果加到之前的辐照度上
视觉效果
- 无太阳反弹 → 室内/阴影区域偏暗偏冷
- 加入太阳反弹 → 显著增加暖色调和亮度 ,差异非常大
- 特别是在 室内场景 中,太阳反弹光让空间变暖变亮,效果自然
假设与局限
- 关键假设:反弹天空可见性 SH 中包含的 余弦加权平均天空可见性 ,可以作为表面对太阳光遮蔽的合理估计
- 这个假设 并不总是成立 ,但在实践中被证明 足够好
SH 方向性增强(Directionality Boost)
问题
- 使用 二阶 SH(Quadratic SH / L2) 无法很好地表示 高频数据
- 两个二阶 SH 相乘会产生 四阶结果 ,但为了节省存储,只保留到 二阶 ,高阶项被截断
- 结果:光照看起来 偏平坦 ,法线贴图的细节不够突出
启发式解决方案
- 向 线性 SH 最大值方向 上的 Delta 函数的 SH 投影 做 Lerp 插值
- 对 直接光照部分 和 反弹光照部分 分别执行此操作
- 计算开销很小
- 最终全游戏统一使用 25% 的增强因子
效果
- 未增强:光照平坦,法线贴图不明显
- 增强后:法线贴图获得了 更清晰的明暗定义 ,表面细节更丰富
SH 去振铃(De-ringing)
问题:负瓣(Negative Lobes)
- SH 表示中的 负值 是已知问题,尤其在高对比度区域(如窗户附近)
- 会导致 不自然的暗区或颜色错误
解决方案
- 初始尝试:对天空可见性施加 固定的去振铃因子 → 但会 降低方向性和保真度 ,不理想
- 最终方案:在 运行时 GPU 上 对 天空亮度 SH 和 最终辐射度探针 SH 进行去振铃
- 使用了 Peter-Pike Sloan 的论文 中提出的方法
实现细节
- 寻找 SH 最小值:使用 深度为 3 的二分搜索树(Binary Search Tree)
- 在实践中,单步牛顿迭代(Single Newton Step) 就足以找到 SH 最小值
- 寻找保证 SH 处处非负所需的 最小去振铃因子
新旧窗口函数对比
- 新的窗口函数(Sloan 论文)比 inFAMOUS 中使用的旧窗口函数 产生 更尖锐的结果
- 对 Delta 函数的 SH 表示应用窗口化:新函数(蓝色曲线)比旧函数(红色曲线)保留了更多方向性
效果
- 窗户附近的负瓣被修复,同时 场景其余部分的光照不受影响 (因为每个探针独立去振铃)
光照泄漏问题(Light Leaking)
问题描述
- 当一个 明亮的辐照度探针 的光照 穿透遮挡物 (如墙壁、屋顶),导致另一侧出现 不自然的过亮光照
- 在 inFAMOUS 中可以通过 在厚墙内放置额外探针 来回避,因为室内场景不多
- 《对马岛》有 大量室内场景 ,且由于时代建筑特征, 墙壁非常薄 ,问题严重
尝试过的方案(均不满意)
| 方案 | 问题 |
|---|---|
| 为每个探针添加 锥体(Cones) 标记被遮挡的邻居探针 | 性能开销高 |
| 添加 遮挡平面(Occlusion Planes) 表示墙壁等遮挡物 | 性能开销高 |
| 使用 低分辨率阴影图(Low-res Shadow Maps) | 性能开销高 |
最终方案:内外部探针分类 + 内部遮罩(Interior Mask)
步骤
-
分类探针 :将每个探针标记为 室内(Interior) 或 室外(Exterior)
-
内部遮罩值 :
- 为每个表面分配一个 内部遮罩值 ,默认为 0.5
- 可通过以下方式设置:
- Shader 参数
- 顶点颜色通道(Vertex Color Channel)
- 延迟贴花(Deferred Decal) 单独叠加
-
加权插值 :
- 正常计算 四面体重心坐标(Tetrahedral Barycentric Coordinates)
- 对每个重心坐标进行加权:
- 若对应探针为 室内 探针:乘以
- 若对应探针为 室外 探针:乘以
- 重新归一化 重心坐标
- 按加权后的重心坐标进行正常的辐照度混合
-
回退机制 :若所有权重为零,回退到 原始重心坐标
效果
- 未使用内部遮罩 :室内后墙因光照泄漏而 过亮
- 应用内部遮罩后 :光照泄漏问题被修复,室内光照自然合理
- 该方案在 运行时开销 和 额外制作负担 方面均可接受
间接镜面反射光照(Indirect Specular Lighting)
反射探针系统(Reflection Probes)
规模与内存管理
- 前作 《恶名昭彰:次子》(inFAMOUS Second Son) 使用了 230 个静态反射探针 ,已接近内存上限
- 《对马岛》最终使用了 235 个 反射探针
- 由于重新光照(Relighting)所需的额外数据量约为 压缩后已光照探针的 1.5 倍 ,因此改为 按需流式加载(Streaming on Demand)
减少探针数量的策略
- 为每个 生态群落(Biome) 添加 默认探针 ,作为该区域的基础反射
- 对许多小型房屋等场景,使用 实例化室内探针(Instanced Interior Probes) ——多个相似室内共享同一组探针数据
- 支持 一级嵌套(One Level of Nesting) ,处理游戏中大量的室内场景
- 运行时最多同时支持 128 个重新光照的探针
重新光照所需的离线捕获数据
| 数据类型 | 压缩格式 | 说明 |
|---|---|---|
| 反照率立方体贴图(Albedo Cube Map) | BC1 | 存储表面颜色 |
| 法线 + 深度立方体贴图 | BC6H | R/G 通道存储 八面体法线(Octahedral Normals) ,B 通道存储 双曲深度(Hyperbolic Depth) |
- 所有立方体贴图分辨率均为 256×256
运行时重新光照流程
更新策略
- 每帧选择 一个探针 ,在 异步计算(Async Compute) 中更新
- 由于探针体积不一定被 级联阴影贴图(Cascaded Shadow Maps) 覆盖,改用 远距阴影贴图图集(Far Shadow Map Atlas)
- 每个 200 米 Tile 对应一张 128×128 的阴影贴图
间接光照采样
- 出于性能考虑,每个探针使用 单个 SH 采样 来计算间接光照
- 同一采样转换为 亮度(Luminance) 后,用于 反射探针亮度归一化(Luminance Normalization)
- 确保探针在其捕获位置使用时具有 正确的预期亮度
- 避免了将探针投影到 SH 的额外开销
探针预过滤与压缩
- 使用 过滤重要性采样(Filtered Importance Sampling) 配合 GGX NDF 进行预过滤
- 压缩为 BC6H 格式,使用 Krzysztof Narkowicz 的开源压缩器
- 好处:
- 显著 减少内存占用
- 防止在高光泽且法线变化剧烈的表面(如 水面 )上出现 缓存抖动(Cache Thrashing)
立方体贴图阴影追踪(Cube Map Shadow Tracing)
问题
- 室内探针的阴影不正确,原因:
- 远距阴影贴图 分辨率太低
- 许多建筑在渲染远距阴影时被 LOD 简化 ,丢失了几何细节
解决方案:利用立方体贴图深度进行遮蔽
- 立方体贴图中已存储了 深度信息 ,包含丰富的 遮蔽信息
- 重新光照某个纹素时 :
- 从该纹素位置 向光源方向回溯
- 找到光线与 立方体贴图体积的交点
- 在交点处 采样立方体贴图深度
- 使用 1 - Z 深度 表示,足够小的值视为 无遮挡
- 在 4×4 采样区域 上应用 PCF 过滤 来柔化阴影
局限性
- 该方法较为 粗糙 ,仅沿光线采样一次深度
- 可通过 沿光线多次采样深度 来提高精度
- 存在误判情况:
- 某些本应被遮蔽的位置可能被判为未遮蔽(漏判)
- 某些本应未被遮蔽的位置可能被错误地判为遮蔽(误判)
视觉对比
- 未启用 立方体贴图阴影追踪:室内反射中出现明显的 异常发光(Glowing)
- 启用后 :发光大幅减少,反射中的地板和墙壁与实际场景 正确匹配
水平线遮蔽项(Horizon Occlusion Term)
问题描述
- 美术反馈:法线贴图凹凸的 背面 在反射光照中 过亮
- 原因:反射光本应被 宏观几何(Macro Geometry) 部分遮挡,但标准计算未考虑法线贴图法线 相对于顶点法线 的 倾斜(Tilt) 造成的遮蔽
输入空间
四维问题:
- 表面粗糙度(Roughness)
- 法线贴图法线相对于顶点法线的倾斜角
- 视角方向的两个分量
近似求解步骤
- 用锥体近似反射光线分布 :对于 GGX 粗糙度 ,包含能量比例 的锥体半角 由以下公式给出:
- 取 (95% 能量)
- 使用 曲线拟合 降低锥角计算开销
-
投影简化 :将法线贴图法线 投影到由 反射向量 和 顶点法线 组成的平面上,得到投影法线
-
定义关键角度 :
- :投影法线与顶点法线的夹角
- :反射向量的倾斜角
- :锥体低于表面的角度
-
遮蔽角计算 :
- 由于 BRDF 在 时已包含遮蔽,且 变化 1° 导致 变化 2°,因此钳制为 ,只计入 额外遮蔽
- 最终遮蔽量 :使用近似公式计算,结果永远不超过 (即锥体内的能量比例 95%)
视觉效果
- 无水平线遮蔽 :河床岩石的背面出现 不真实的发光
- 启用后 :岩石背面不再异常发光,效果自然
视差校正(Parallax Correction)
基本原理
- 反射探针使用 美术创作的包围盒(Authored Bounding Box) 建模反射环境
- 将 反射向量与包围盒求交 ,交点位置即为立方体贴图的采样位置
- 问题:当前视点(蓝色眼睛)与探针捕获视点(红色相机)距离不同
粗糙度补偿
- 设当前视点到采样位置距离为 ,捕获相机到采样位置距离为
- 利用之前的锥角公式:
- 观察到该比值 近似等于距离比 :
- 因此采样立方体贴图时,按 距离比调整粗糙度 :
视觉效果
- 未启用 :轻微粗糙的镀铬球体中障子纸屏风的反射 过于模糊
- 启用后 :反射清晰度与实际距离匹配
天空、大气散射与云渲染
大气散射查找表(Atmospheric Scattering LUTs)
基本结构
- 使用 预计算的 3D 查找表 来光照天空,同时也用于 云、体积雾霾(Volumetric Haze)和雾粒子(Fog Particles)
天空 LUT
- 存储 瑞利散射(Rayleigh) 和 米氏散射(Mie) 的散射值,除以各自的相函数(Phase Function)
- 索引维度:
- 海拔高度(Altitude)
- 平行光极角(Directional Light Polar Angle)
- 视线极角(View Polar Angle)
- 包含 多次散射反弹(Multiple Scattering Bounces)
- 基于 Bruneton、Elek 和 Yusov 的论文
- 3D LUT 不考虑 方位角(Azimuthal Angle) ,运行时应用 解析地球阴影项(Analytic Earth Shadow Term) 补偿
辐照度 LUT(Irradiance LUTs)
- 问题 :直接用天空 LUT 光照云/雾/霾时,在 地平线附近 出现精度问题
- 解决方案 :增加一组平行的 LUT,以相同方式索引,存储每个点的 散射后的瑞利和米氏光照值
- 这些值是 乘以局部密度之前 的值,除以相函数
- 单位为 勒克斯(Lux) ,可理解为每个点的一种 平均辐照度
- 使用时:
- 米氏辐照度 应用 阴影(Shadowing) ——因为米氏散射是 强前向散射
- 瑞利辐照度 应用 环境光遮蔽(AO) ——因为瑞利散射分布 更加均匀
- 体积雾霾中使用间接光照数据的 平均天空可见性 作为 AO,辅以 雨阴影贴图(Rain Shadow Map) 防止室内雾霾过亮
云渲染系统
基础参考
- 基于 Andrew Schneider(SIGGRAPH 2015)和 Sebastian Hillaire(PBR 课程 2016)的工作
渲染目标
- 云渲染到 抛物面贴图(Paraboloid Map) ,覆盖整个半球
- 分辨率仅 768×768 (性能限制)
- 格式:RGB11F
三通道编码
| 通道 | 存储内容 | 运行时操作 |
|---|---|---|
| R | 米氏散射量 | 乘以 LUT 中的 米氏辐照度 |
| G | 瑞利散射量 | 乘以 LUT 中的 瑞利辐照度 |
| B | 透射率(Transmittance) | 直接使用 |
多次散射的重要性
- 无多次散射 :日出/日落时天空和云的光照效果偏暗、缺乏层次
- 有多次散射 :对场景外观影响 极为显著 ,尤其在日出/日落场景中
时间切片渲染
- 使用 3 张抛物面纹理 :
- 2 张 用于在云运动方向滚动时进行 混合(Blend)
- 1 张 用于当前渲染
- 渲染以 60 帧为周期进行时间切片 ,尽量降低开销
密度抗锯齿(Density Anti-Aliasing)
- 问题 :低分辨率导致云在 UV 空间中位置变化快的区域出现 噪点和像素化边缘
- 解决方案 :
- 在极坐标下计算:
- 径向云位置对径向纹理坐标的 导数
- 角向云位置对角向纹理坐标的 导数
- 极坐标下可以得到 相对简单的解析表达式
- 当这些导数的最大值较大时,降低云密度
- 在极坐标下计算:
- 效果 :关闭时有明显的采样噪点和锯齿边缘;开启后这些瑕疵 完全消失
运行时 LUT 优化
3D → 2D 重采样
- 每帧根据当前 太阳和月亮角度 ,将 3D LUT 重采样为 2D LUT
- 使用 三次上采样(Cubic Upsampling) :
- 避免日出/日落时的 走样问题
- 使后续 LUT 查找 更廉价
光谱近似瑞利散射(Spectral Approximation for Rayleigh Scattering)
动机
- 大气散射驱动了游戏中大部分的世界光照,瑞利散射的精度至关重要
方法来源
- 基于 Kristen Schüler 的博客文章,使用 自定义颜色空间 近似光谱渲染
两步优化过程
-
步骤一 :寻找三个 光谱主色波长 (长、中、短),使得在一系列太阳角度下 瑞利透射率误差最小化
- 对浅角度(日出/日落)给予 更高权重
- 使用该波长处的 点采样瑞利散射系数
-
步骤二 :固定主色波长,允许 瑞利散射系数自由变化 ,进一步精化结果
对比结果
| 对比项 | Rec 709 + Bruneton 系数 | LMS 颜色空间 + 优化系数 |
|---|---|---|
| 透射率 | 绝对值和色调都有明显偏差 | 与光谱渲染结果 非常接近 |
| 外散射 | 色调偏差,偶尔出现 偏绿色调 | 匹配度极高 |
| 日出/日落 | 差异最显著 | 显著减少偏绿色调 |
新颜色空间细节
- 使用与 Rec 709 相同的 D65 白点
- 自定义的 LMS 光谱主色 和对应的散射系数
大气光照:云层与体积雾
云层相位函数设计(Cloud Phase Function)
核心方法:深度自适应的 Henyey-Greenstein 相位函数
- 使用 Henyey-Greenstein 相位函数 ,其中 不对称参数 随云层内部深度动态变化
- 核心思想:
- 在 低密度、稀薄的云层边缘 :以 前向散射(Forward Scattering) 为主,模拟 单次散射 主导的行为
- 随着 密度增加 及 光照方向上的密度增加 : 多重散射(Multiple Scattering) 变得更重要,逐渐过渡到 后向散射(Back Scattering)
值的插值控制
- 使用 到光源的透射率(Transmittance to Light) 与 当前光线步进段的透射率 的 乘积 作为插值因子:
- 乘积 → 0:倾向 后向散射
- 乘积 → 1:倾向 前向散射
自然产生的视觉效果
| 效果 | 说明 |
|---|---|
| 银边效果(Silver Lining) | 逆光云层顶部边缘出现明亮光晕,自然产生无需特殊处理 |
| 暗边效果(Dark Edges) | 云层边缘变暗,无需使用 Schneider 提出的 "糖粉启发式(Powdered Sugar Heuristic)" |
后向散射亮度缩放
- 后向散射源自 多重散射 ,需要对其亮度进行缩放
- 缩放系数为 2.16 ,通过模拟得出:
- 使用大气多重散射模型模拟一个 反照率(Albedo)为 0.9 的致密 Mie 散射层
- 理论上限为 4 (均匀散射层、反照率 = 1 时,等价于白色 Lambertian 表面)
视觉对比
- 仅前向散射 :云层明亮但缺乏立体感
- 仅后向散射 :可见暗边但整体偏暗
- 前向 + 后向结合 :暗边清晰可见,逆光时银边效果自然呈现
体积雾光照与渲染(Volumetric Haze)
技术基础
- 构建于 Bart Wronski(SIGGRAPH 2014) 和 Michał Drobot(Digital Dragons 2017) 描述的技术之上
Froxel 网格配置
| 参数 | 值 |
|---|---|
| 网格分辨率 | 宽度 128,高度 64,深度 64 |
| 覆盖范围 | 近平面 10 cm 到远平面 100 km (覆盖整个视锥体) |
| 深度切片分布 | 指数增长 ,每层比前一层厚 20% |
密度函数
- 限制为两种 解析密度函数 的组合:
- 指数高度衰减(Exponential Height Falloff) :经典雾气分布
- S 形高度衰减(Sigmoidal Height Falloff) : 几乎总是为零
- 在其他维度上的局部变化通过 粒子(Particles) 添加
计算流程
- 通过 单次 Compute Dispatch 更新整个 Froxel 网格
- 从前向后积分内散射(In-Scattering) ,使用 Quad Swizzling 和 4×4×4 线程组
- 使用 时间滤波(Temporal Filtering) 平滑抖动的:
- 阴影采样
- 环境光遮蔽采样
- 局部光源采样
双缓冲策略
- 使用单个 RGB11F 缓冲存储最终光照值会导致 明显色带(Banding)
- 改用 两张纹理分别存储 :
- 好处一:可以对局部光源使用 不同的重投影策略(Reprojection Strategy)
- 好处二:可以 跳过被遮挡 Froxel 的光照计算
- 性能:两张纹理以全速率过滤,性能约等于采样单张 RGBA16F 纹理
- 对于被遮挡的 Froxel,仍更新 阴影和环境光遮蔽项 ,以减少 去遮挡伪影(Disocclusion Artifacts)
性能
- 全部在 异步计算(Async Compute) 中执行
- 在 基础版 PS4 上,包含大量局部光源的重负载场景中约 0.5 毫秒
光源裁剪(Tiled Light Culling)
- 分块光源裁剪 Compute Shader 同时输出 每 Tile 的光源列表 供体积雾使用
- 线程间光源集合的 发散(Divergence) 对性能影响很大
- 采用 扁平位数组(Flat Bit Array) 技术(Drobot,2017),某些场景下性能提升近 2 倍
- 有趣发现 :对于 前向着色材质 ,使用 排序后的光源索引列表 反而比位数组稍快,因为光源列表的开销低于遍历位数组
体积雾抗锯齿(Volumetric Haze Anti-Aliasing)
问题
- Froxel 网格非常 粗糙 但覆盖整个视锥体
- 传统方法在以下场景产生 严重锯齿 :
- 地平线附近
- 薄雾层 中
- 原因:密度变化的 欠采样(Undersampling)
核心技巧:存储辐射度 / 不透明度
- 不直接存储散射辐射度 ,而是存储:
其中 是不透明度, 是透射率
- 由于密度函数是 解析的 ,可以在 逐像素 级别重新计算透射率,合成到场景时再应用
- 额外好处:不需要存储不透明度,体积纹理可以使用 RGB11F 格式
数学推导
- 假设 :散射系数 与消光系数 成正比,比值为反照率 :
- 体积渲染方程(距离 上的内散射辐射度):
-
代入 ,将 提出积分外
-
假设 (入射辐射度)在积分区间内 近似常数 (对于体积雾通常成立,除阴影区域外)
-
分离积分:
- 剩余积分有 解析解 :
- 因此:
- 根据假设 近似常数,所以 是比 本身 更平滑的函数 ,采样时锯齿大幅减少
视觉效果
- 未启用密度抗锯齿 :水面附近出现平行于地平线的明显 条带(Bands)
- 启用后 :条带伪影显著减少
三维方向的全面抗锯齿
三次 B 样条滤波(Tricubic B-Spline Filtering)
- 密度抗锯齿解决了深度方向的问题,但 所有三个维度 都需要抗锯齿
- 使用 三次 B 样条三线性滤波(Tricubic B-Spline Filtering) 采样体积纹理
- 需要 8 次三线性采样(Trilinear Taps)
性能
- 令人惊讶地 廉价 :在 PS4 Pro 上以 3200×1800 棋盘格分辨率 运行时,仅比三线性采样多 50 微秒
- 使用了三次权重函数的 近似公式 优化 ALU,对视觉效果无任何影响(详细公式见 Desmos 图表)
逐级效果对比
| 滤波方式 | 效果 |
|---|---|
| 三线性滤波(Trilinear) | X/Y 方向锯齿明显 |
| 双三次滤波(Bicubic) XY 方向 | X/Y 改善但仍有可见条带 |
| 三次滤波(Tricubic) 全方向 | 几乎 完全消除 剩余锯齿,光轴与光源开口正确连接 |
大气粒子光照(Atmospheric Particle Lighting)
粒子雾气光照模型(Haze Lighting Model for Particles)
动机与方法
- 使用 粒子(Particles) 为场景添加 局部雾气(Local Fog) 效果
- 在粒子系统中增加了一个可选的 雾气光照模型(Haze Lighting Model)
光照数据来源
- 在体积雾(Volumetric Haze)的 Compute Shader 中,将之前公式中的 存储到一个 并行光照体(Parallel Light Volume) 中
- 该光照值 = LUT 采样的光照 × 相位函数 ,并应用了 阴影(Shadowing) 和 环境光遮蔽(AO) ,再加上 局部光源贡献
粒子着色器中的采样
- 在粒子像素着色器中,采样 Froxel 光照体 并乘以 粒子不透明度(Opacity)
- 优化策略 :
- 不透明度低于阈值时,使用 三线性过滤(Trilinear Filtering) (性能优先)
- 不透明度超过阈值后,切换到 双三次过滤(Bicubic Filtering) (质量优先)
- 大多数粒子是 面向摄像机(Camera Facing) 的,因此 Z 方向上不需要额外过滤
视觉对比
- 标准粒子光照 :雾气粒子与周围环境不够融合
- 雾气光照模型 :雾气粒子更好地 融入场景
粒子多重散射近似(Multi-Scattering Approximation)
问题
- 切换到雾气光照后,特效美术反馈:粒子在 正面照明时几乎看不见
- 原因:雾气模型假设 单次散射 且以 前向散射为主 ,导致从正面(背光方向)观察时粒子极暗
解决方案:低成本多重散射近似
-
CPU 端预计算 :
- 计算 后向散射相位函数 与 Mie 相位函数 在前向和后向方向上的 比值
- 使用 Henyey-Greenstein 相位函数 ,不对称参数 ,模拟多重散射(与云层方案类似)
-
粒子像素着色器中 :
- 根据 视线方向与光照方向的点积 ,在前向和后向两个比值之间 线性插值(Lerp) ,得到 多重散射缩放因子
- 再根据 粒子不透明度 ,从 1 到 之间插值
- 用最终值 缩放粒子亮度
-
实际调整 :
- 针对 后向散射 方向适当 降低效果强度 ,因为缺少完整的 Mie 颜色贡献时,粒子会看起来不自然地明亮
视觉效果
- 无多重散射近似 :粒子平坦、缺乏形状感
- 启用多重散射近似 :粒子获得更多 形状感和细节定义(Shape and Definition)
色调映射(Tone Mapping)
曝光系统(Exposure System)
前作的问题
- 在 《恶名昭彰:次子》 和 《First Light》 中,难以维持资产的 物理合理漫反射反照率(Physically Plausible Diffuse Albedo)
- 原因分析:基于 亮度(Luminance) 的曝光与反照率之间存在 反馈循环 ,导致:
- 漫反射反照率值 偏暗
- 镜面反射分量 相对过亮
改进措施
-
漫反射颜色参考图表(Diffuse Color Reference Chart) :
- 基于多种物体的 反射光谱 转换为 sRGB 颜色
- 帮助美术选择 物理合理的反照率值
-
基于照度的曝光系统(Illuminance-Based Exposure) :
- 主要使用 照度(Illuminance) 而非亮度(Luminance),将 反照率从曝光方程中去除
- 仅在 高光亮度超过阈值 时(如火焰粒子、明亮镜面高光)才回退到基于亮度的曝光
动态范围挑战
室内外亮度差异
- 由于游戏的 历史设定 ,室内光照通常 非常昏暗
- 室内外亮度差可达 10 EV(曝光值)
- 目标:
- 尽可能 保持可见性 ——提亮过暗区域
- 同时 避免高亮区域过曝(Blown Out)
- 天空驱动间接光照 ,常比环境更亮,但 艺术上不希望天空因色调映射而过度去饱和
摄影与电影行业的解决手段
| 技术 | 说明 |
|---|---|
| 渐变滤镜(Graduated Filters) | 在镜头前使用渐变中灰密度滤镜压暗天空 |
| 特殊灯光装置与反射板(Bounce Cards) | 为暗部补光 |
| 冲洗阶段的减淡和加深(Dodging & Burning) | 选择性地提亮/压暗区域 |
| 阴影/高光调整(Shadow & Highlight Adjustment) | 数字时代的等效操作 |
| 多重曝光 / HDR 摄影 | 合成不同曝光的照片 |
模拟人眼动态范围
- 人眼能感知的 动态范围远高于相机
- 选择使用 双边滤波器(Bilateral Filter) 来 保持图像细节的同时降低整体对比度
对比度降低的效果对比
- 无调整 :高亮区过曝,暗部细节丢失
- 直接降低 50% 对比度 :恢复大量颜色,但 局部细节严重扁平化
- 使用双边滤波器 :保留了大量 局部对比度(Local Contrast) ,同时降低全局对比度
双边网格算法(Bilateral Grid)
为何选择双边网格
- 朴素的双边滤波器 计算昂贵
- 双边网格算法(Bilateral Grid Algorithm) 可以高效计算,且 内存占用低
- 允许图像数据被 大幅降采样
网格配置
| 参数 | 值 |
|---|---|
| 网格分辨率 | (XYZ) |
| 性能开销 | PS4 @ 1080p 约 250 微秒 |
| 附带功能 | 自然地与用于动态曝光的 亮度直方图生成 并行执行 |
算法流程
-
填充网格 :
- 将 齐次对数亮度值 写入网格
- 为权重, 为对数亮度
- 初始权重基于 线性 Z 轴权重 ——每个样本贡献到最近的两个 Z 切片
- X、Y 方向 无需加权 ,因为后续会施加宽模糊
-
高斯模糊 :
- X、Y 方向使用 宽模糊(Wide Blur)
- Z 方向使用 窄模糊(Small Blur) ,有时甚至完全省略
-
采样与归一化 :
- 对每个像素,通过 三线性采样(Trilinear Sampling) 从网格中取值,并归一化得到双边滤波后的亮度
对比度调整公式
其中:
- :输入图像的 对数亮度
- :输出的 对数亮度
- :双边滤波后的对数亮度
- : 中点对数亮度 ——降低对比度时亮度值趋向的目标点
- : 对比度缩放(Contrast Scale)
- : 细节强度(Detail Strength)
振铃伪影与混合方案(Ringing Artifacts & Hybrid Approach)
问题
- 在具有 平滑渐变 的场景中出现 振铃伪影(Ringing Artifacts)
- 最常见于 云层、软阴影、镜面高光 区域
- 原理:
- 绿色输入信号经双边模糊得到紫色曲线
- 降低模糊曲线的对比度,加回 细节(输入与模糊的差值)
- 当模糊曲线与原始信号偏差较大时,回加的细节产生 过冲(Overshoot) ,导致振铃
权衡
- 增大亮度桶(Luminance Buckets)或加宽 Z 模糊可减少振铃,但会 增加光晕(Haloing)
混合解决方案
- 采用 混合方法(Hybrid Approach) :将双边滤波器输出与 很宽的 2D 高斯模糊 进行混合
- 最终混合权重:
- 40% 双边模糊
- 60% 高斯模糊
- 高斯模糊参数:
- 13 tap 半径 的滤波器,应用于 64×32 的低分辨率图像
- 上采样到 256×128 ,使用 双三次 B-Spline 过滤 (避免双线性过滤伪影,同时进一步增加模糊)
- 效果:振铃伪影大幅减少,外观更自然,同时 保留了天空中的颜色
细节强度参数 的应用
- 可以 增强局部对比度 ,使场景更具 冲击力(Punchier)
- 例如 时画面明显更锐利有力
- 项目中 节制使用 ,但作为艺术工具非常有价值
色调映射的颜色问题
逐通道色调映射的缺陷
- 在渲染颜色空间中 逐通道(Per-Channel) 应用色调映射(如 Reinhard 算子):
- 将饱和的非主色裁剪到 青色(Cyan)、品红(Magenta)、黄色(Yellow) 方向
- 实际效果非常不自然——如日落和镜面高光处出现 过饱和的黄色
- 即使预缩放输入颜色使其最大分量不超过 1,问题依然严重
色调映射色彩空间(Tone Mapping Color Space)
色彩空间选择与定制
候选色彩空间评估
- 2017 年 Brian Karis 提到喜欢 ACEScg 作为色调映射空间时颜色向白色渐变的行为
- 团队测试了多个色彩空间:
- ACES AP0
- ACEScg
- Rec 2020
- DCI-P3
- 最终判定 ACEScg 整体效果最佳
ACEScg 的问题:红色偏黄
- 在色调映射过程中,ACEScg 会将 红色和橙色色调过度弯向黄色
- 尤其在日落场景中,艺术总监非常不满意天空 偏黄 的效果
定制解决方案
- 将 ACEScg 红色基色(Red Primary)的 x 坐标从 0.713 调整为 0.75
- 效果:
- 减少了红色向黄色的偏移
- 在不影响黄色场景(如 金色森林 )色调映射的前提下,恰到好处地减少了色偏和饱和度
- 仍存在改进空间 :靠近蓝色基色的颜色仍然会较强地 偏向洋红色(Magenta)
色彩空间变换可视化
- 通过动画展示色调映射如何在定制色彩空间中变换色域
- 高亮度颜色最终会向白色收敛(Roll Off to White)
色调映射白平衡(White Balance)
调色工具现状
- 仍使用较传统的调色流程:在 Photoshop 中操作截图,内嵌 32×32×32 sRGB 颜色查找表(Color LUT)
- 艺术总监喜欢这个工作流,团队不急于更换
- 局限 :LUT 分辨率有限,使用过于激进会引入 色带伪影(Banding Artifacts)
引入白平衡控制
灵感来源
- Bart Wronski 的博客文章讨论了游戏中白平衡的应用思路
实现方式
- 零 GPU 性能开销 :白平衡变换可以合并到 Rec 709 → 色调映射色彩空间的变换矩阵 中
- 效果非常好,最终 不需要更换整套调色工具
交互设计:反向白平衡选择器
| 工具 | 操作方式 |
|---|---|
| Lightroom(传统) | 点击场景中 应该是中性灰的像素 → 系统将其校正为白色 |
| 《对马岛》的工具 | 艺术家 选择希望白色变成的颜色 → 如想让场景偏蓝,就选蓝色 |
- 艺术家认为这种 反向方式更直观
技术实现:色适应矩阵(Chromatic Adaptation Matrix)
- 模拟 人类视觉系统 如何适应不同光照环境
- 使用 Von Kries 变换 在 Bradford LMS 色彩空间 中进行:
- 将颜色转换到 LMS 空间 (近似视网膜中 长/中/短波锥体细胞 的响应)
- 按 白点比值 缩放各通道
- 转换回原始色彩空间
与色调映射的交互
- 最终色彩空间仍为 Rec 709 ,因此高亮度/高饱和颜色仍会 向显示器白点收敛(Roll Off to Display White)
- 仅低饱和颜色区域受白平衡影响最明显
浦肯野位移效果(Purkinje Shift)
夜间渲染的两大挑战
- 让夜晚 感觉像真正的夜晚 ,而非 "更暗的白天"
- 确保玩家在夜间 仍能看清楚 ,保证可玩性
人类视觉系统背景
视网膜感受器
| 类型 | 数量 | 功能 |
|---|---|---|
| 锥体细胞(Cones) | 3 种(长波 L / 中波 M / 短波 S) | 负责 常规彩色视觉 |
| 杆体细胞(Rods) | 1 种 | 对 低光照更敏感 ,但为 单色(Monochromatic) |
关键事实
- 人类视觉是 三维的(3D) 而非四维,因为杆体细胞与锥体细胞共享 相同的神经通路
- 在低光照下,杆体逐渐接管锥体的工作,引入 色偏 —— 即 浦肯野位移(Purkinje Shift)
- 低光视觉 并非黑白 ,而是 偏蓝 的
色偏建模
- 色偏可建模为 对立色彩空间(Opponent Color Space) 中的增量
- 对立色彩空间被认为是 视觉系统内部使用的色彩空间
数学实现
第一步:RGB → LMSR 转换矩阵
从 Rec 709 RGB 转换到 LMSR 空间 (L/M/S 锥体 + R 杆体):
其中:
- :LMSR 响应的行索引
- :RGB 基色的列索引
- :波长
- :可见光波长范围
- :感受器 的响应曲线
- :D65 光源光谱
- :使用 Smits 方法 求得的 RGB 反射率光谱
Smits 方法求解反射率光谱
- 从 RGB 到光谱是一个 欠约束问题(Under-specified Problem)
- Smits 方法寻找一组 最大化平滑度 的 RGB 基光谱
- 约束条件:各光谱值在 [0, 1] 范围内且 总和为 1
第二步:计算增益控制向量
将场景颜色 转换为 LMSR 响应 :
计算 乘性增益控制向量(Multiplicative Gain Control Vector) (仅针对锥体):
- :描述锥体灵敏度的常数向量
- :描述杆体对 LMS 通路 输入强度 的向量()
- 根据 Cow 等人 的论文, 应随照度增加而减小,但团队选择参照 Kirk 的方案使用 常数值
- 这些常数有一定自由度 ,调整会产生不同效果,建议实现时进行实验
第三步:对立色彩空间中的杆体影响
丢弃 的 R 分量得到 (LMS 空间颜色),然后:
其中 为给定的变换矩阵。
计算杆体在对立色彩空间中的增量效应 :
第四步:转回 RGB
其中 是 的 左上 3×3 子矩阵
GPU 着色器实现
- 整个计算仅需 三行着色器代码 ,非常高效
视觉效果
效果特征
- 随光照亮度从明亮降至约 0.05 lux :
- 暗色区域被提亮
- 出现明显的 蓝色偏移(Blue Shift)
游戏中的实际效果
| 状态 | 表现 |
|---|---|
| 未启用 Purkinje Shift | 夜间场景偏暗,视觉上像"暗版白天" |
| 启用后(平均照度 ~0.05 lux) | 暗区(尤其 深蓝色水面和天空 )明显变亮,夜间氛围感更强且可玩性提升 |
总结要点
| 技术 | 核心贡献 |
|---|---|
| 定制色调映射色彩空间 | 基于 ACEScg 调整红色基色,减少红→黄偏移,保留日落和金色森林的正确色调 |
| 艺术白平衡 | 零性能开销的色适应变换(Bradford LMS + Von Kries),反向选色器更直观 |
| 浦肯野位移 | 基于人眼视觉模型的低光照色偏模拟,三行着色器代码实现,显著提升夜间场景的真实感和可玩性 |