《对马岛之魂》实时武士电影:光照、大气与色调映射

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) 全局调整光照参数(如 环境光/漫反射/镜面反射平衡天空亮度 等)
  • 这些调整通常以 天气状态和时间段 的粒度进行设置

基于物理的光照模型

组件具体方案
高光 BRDFGGX 分布函数(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) 技术来达成目标视觉风格

演讲结构:三大主题

本演讲分为三大部分:

  1. 间接光照(Indirect Lighting) ——漫反射间接光照 + 镜面反射间接光照
  2. 大气体积光照(Atmospheric Volumetric Lighting) ——天空、云、体积雾霾、粒子
  3. 色调映射(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)

核心挑战

  • 游戏拥有 动态昼夜循环动态天气系统 ,因此辐照度探针必须在 运行时更新 ,不能完全依赖离线烘焙

离线 + 运行时的混合方案

  1. 离线阶段 :不直接捕获辐照度(irradiance),而是捕获每个探针位置的 天空可见性(Sky Visibility) ,以 球谐函数(SH) 形式存储
  2. 运行时阶段
    • 计算当前天空光照的 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)

步骤

  1. 分类探针 :将每个探针标记为 室内(Interior)室外(Exterior)

  2. 内部遮罩值

    • 为每个表面分配一个 内部遮罩值 ,默认为 0.5
    • 可通过以下方式设置:
      • Shader 参数
      • 顶点颜色通道(Vertex Color Channel)
      • 延迟贴花(Deferred Decal) 单独叠加
  3. 加权插值

    • 正常计算 四面体重心坐标(Tetrahedral Barycentric Coordinates)
    • 对每个重心坐标进行加权:
      • 若对应探针为 室内 探针:乘以
      • 若对应探针为 室外 探针:乘以
    • 重新归一化 重心坐标
    • 按加权后的重心坐标进行正常的辐照度混合
  4. 回退机制 :若所有权重为零,回退到 原始重心坐标

效果

  • 未使用内部遮罩 :室内后墙因光照泄漏而 过亮
  • 应用内部遮罩后 :光照泄漏问题被修复,室内光照自然合理
  • 该方案在 运行时开销额外制作负担 方面均可接受

间接镜面反射光照(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存储表面颜色
法线 + 深度立方体贴图BC6HR/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. 从该纹素位置 向光源方向回溯
    2. 找到光线与 立方体贴图体积的交点
    3. 在交点处 采样立方体贴图深度
    4. 使用 1 - Z 深度 表示,足够小的值视为 无遮挡
    5. 4×4 采样区域 上应用 PCF 过滤 来柔化阴影

局限性

  • 该方法较为 粗糙 ,仅沿光线采样一次深度
  • 可通过 沿光线多次采样深度 来提高精度
  • 存在误判情况:
    • 某些本应被遮蔽的位置可能被判为未遮蔽(漏判)
    • 某些本应未被遮蔽的位置可能被错误地判为遮蔽(误判)

视觉对比

  • 未启用 立方体贴图阴影追踪:室内反射中出现明显的 异常发光(Glowing)
  • 启用后 :发光大幅减少,反射中的地板和墙壁与实际场景 正确匹配

水平线遮蔽项(Horizon Occlusion Term)

问题描述

  • 美术反馈:法线贴图凹凸的 背面 在反射光照中 过亮
  • 原因:反射光本应被 宏观几何(Macro Geometry) 部分遮挡,但标准计算未考虑法线贴图法线 相对于顶点法线 倾斜(Tilt) 造成的遮蔽

输入空间

四维问题:

  1. 表面粗糙度(Roughness)
  2. 法线贴图法线相对于顶点法线的倾斜角
  3. 视角方向的两个分量

近似求解步骤

  1. 用锥体近似反射光线分布 :对于 GGX 粗糙度 ,包含能量比例 的锥体半角 由以下公式给出:

  • (95% 能量)
  • 使用 曲线拟合 降低锥角计算开销
  1. 投影简化 :将法线贴图法线 投影到由 反射向量顶点法线 组成的平面上,得到投影法线

  2. 定义关键角度

    • :投影法线与顶点法线的夹角
    • :反射向量的倾斜角
    • :锥体低于表面的角度
  3. 遮蔽角计算

  • 由于 BRDF 在 时已包含遮蔽,且 变化 1° 导致 变化 2°,因此钳制为 ,只计入 额外遮蔽
  1. 最终遮蔽量 :使用近似公式计算,结果永远不超过 (即锥体内的能量比例 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 的博客文章,使用 自定义颜色空间 近似光谱渲染

两步优化过程

  1. 步骤一 :寻找三个 光谱主色波长 (长、中、短),使得在一系列太阳角度下 瑞利透射率误差最小化

    • 对浅角度(日出/日落)给予 更高权重
    • 使用该波长处的 点采样瑞利散射系数
  2. 步骤二 :固定主色波长,允许 瑞利散射系数自由变化 ,进一步精化结果

对比结果

对比项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%

密度函数

  • 限制为两种 解析密度函数 的组合:
    1. 指数高度衰减(Exponential Height Falloff) :经典雾气分布
    2. S 形高度衰减(Sigmoidal Height Falloff) 几乎总是为零
  • 在其他维度上的局部变化通过 粒子(Particles) 添加

计算流程

  • 通过 单次 Compute Dispatch 更新整个 Froxel 网格
  • 从前向后积分内散射(In-Scattering) ,使用 Quad Swizzling4×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 格式

数学推导

  1. 假设 :散射系数 与消光系数 成正比,比值为反照率

  1. 体积渲染方程(距离 上的内散射辐射度):

  1. 代入 ,将 提出积分外

  2. 假设 (入射辐射度)在积分区间内 近似常数 (对于体积雾通常成立,除阴影区域外)

  3. 分离积分:

  1. 剩余积分有 解析解

  1. 因此:

  • 根据假设 近似常数,所以 是比 本身 更平滑的函数 ,采样时锯齿大幅减少

视觉效果

  • 未启用密度抗锯齿 :水面附近出现平行于地平线的明显 条带(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)

问题

  • 切换到雾气光照后,特效美术反馈:粒子在 正面照明时几乎看不见
  • 原因:雾气模型假设 单次散射 且以 前向散射为主 ,导致从正面(背光方向)观察时粒子极暗

解决方案:低成本多重散射近似

  1. CPU 端预计算

    • 计算 后向散射相位函数Mie 相位函数 在前向和后向方向上的 比值
    • 使用 Henyey-Greenstein 相位函数 ,不对称参数 ,模拟多重散射(与云层方案类似)
  2. 粒子像素着色器中

    • 根据 视线方向与光照方向的点积 ,在前向和后向两个比值之间 线性插值(Lerp) ,得到 多重散射缩放因子
    • 再根据 粒子不透明度 ,从 1 到 之间插值
    • 用最终值 缩放粒子亮度
  3. 实际调整

    • 针对 后向散射 方向适当 降低效果强度 ,因为缺少完整的 Mie 颜色贡献时,粒子会看起来不自然地明亮

视觉效果

  • 无多重散射近似 :粒子平坦、缺乏形状感
  • 启用多重散射近似 :粒子获得更多 形状感和细节定义(Shape and Definition)

色调映射(Tone Mapping)


曝光系统(Exposure System)

前作的问题

  • 《恶名昭彰:次子》《First Light》 中,难以维持资产的 物理合理漫反射反照率(Physically Plausible Diffuse Albedo)
  • 原因分析:基于 亮度(Luminance) 的曝光与反照率之间存在 反馈循环 ,导致:
    • 漫反射反照率值 偏暗
    • 镜面反射分量 相对过亮

改进措施

  1. 漫反射颜色参考图表(Diffuse Color Reference Chart)

    • 基于多种物体的 反射光谱 转换为 sRGB 颜色
    • 帮助美术选择 物理合理的反照率值
  2. 基于照度的曝光系统(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 微秒
附带功能自然地与用于动态曝光的 亮度直方图生成 并行执行

算法流程

  1. 填充网格

    • 齐次对数亮度值 写入网格
    • 为权重, 为对数亮度
    • 初始权重基于 线性 Z 轴权重 ——每个样本贡献到最近的两个 Z 切片
    • X、Y 方向 无需加权 ,因为后续会施加宽模糊
  2. 高斯模糊

    • X、Y 方向使用 宽模糊(Wide Blur)
    • Z 方向使用 窄模糊(Small Blur) ,有时甚至完全省略
  3. 采样与归一化

    • 对每个像素,通过 三线性采样(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 色彩空间 中进行:
    1. 将颜色转换到 LMS 空间 (近似视网膜中 长/中/短波锥体细胞 的响应)
    2. 白点比值 缩放各通道
    3. 转换回原始色彩空间

与色调映射的交互

  • 最终色彩空间仍为 Rec 709 ,因此高亮度/高饱和颜色仍会 向显示器白点收敛(Roll Off to Display White)
  • 仅低饱和颜色区域受白平衡影响最明显

浦肯野位移效果(Purkinje Shift)


夜间渲染的两大挑战

  1. 让夜晚 感觉像真正的夜晚 ,而非 "更暗的白天"
  2. 确保玩家在夜间 仍能看清楚 ,保证可玩性

人类视觉系统背景

视网膜感受器

类型数量功能
锥体细胞(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),反向选色器更直观
浦肯野位移基于人眼视觉模型的低光照色偏模拟,三行着色器代码实现,显著提升夜间场景的真实感和可玩性