## 《正当防卫2》中的大世界制作经验与教训 | Making it Large, Beautiful, Fast,and Consistent: Lessons Learned 《正当防卫2(Just Cause 2)》是Avalanche Studios为PC,Xbox 360和PLAYSTATION 3开发的沙盒游戏。游戏的主要风格是大世界,主要视觉特征是具有巨大渲染范围的巨型景观,森林、城市、沙漠、丛林各种环境不同的气候,以及昼夜循环技术。 [ ![img](MakingitLarge,Beautiful,Fast.assets/11c915b42785aed12338cf882463210f.jpg)](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/%E3%80%8AGPUPro1%E3%80%8B%E5%85%A8%E4%B9%A6%E6%8F%90%E7%82%BC%E6%80%BB%E7%BB%93/media/11c915b42785aed12338cf882463210f.jpg) 图 《正当防卫2》封面 对于多动态光源的渲染,《正当防卫2》没有使用延迟渲染,而是提出了一种称作光源索引(Light indexing)的方案,该方案可以使用前向渲染渲染大量动态光源,而无需多个pass,或增加draw calls。 ### ### 3.1 光照索引 Light indexing 光照索引(Light indexing)技术的核心思路是:通过RGBA8格式128 x 128的索引纹理将光照信息提供给着色器。 将该纹理映射到摄像机位置周围的XZ平面中,并进行点采样。 每个纹素都映射在一个4m x 4m的区域,并持有四个该正方形相关的光源索引。这意味着我们覆盖了512m × 512m的区域,且动态光源处于活动状态。 活动光源存储在单独的列表中,可以是着色器常量,也可以是一维纹理,具体取决于平台。虽然使用8位通道可以索引多达256个光源,但我们将其限制为64个,以便将光源信息拟合到着色器常量中。每个光源都有两个恒定的寄存器,保存位置(position),倒数平方半径(reciprocal squared radius)和颜色(color)这三个参数。 [ ![img](MakingitLarge,Beautiful,Fast.assets/cb588c524267abdeafc0de518613af95.png)](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/%E3%80%8AGPUPro1%E3%80%8B%E5%85%A8%E4%B9%A6%E6%8F%90%E7%82%BC%E6%80%BB%E7%BB%93/media/cb588c524267abdeafc0de518613af95.png) 表 光源常量 此外,还有一个额外的“禁用(disabled)”光源槽位,其所有这些都设置为零。那么总寄存器计数会达到130。当使用一维纹理时,禁用的光源用边框颜色(border color)编码替代。 位置和倒数平方半径以RGBA16F格式存储,颜色以RGBA8格式存储。为了保持精度,位置存储在相对于纹理中心的局部空间中。 光源索引纹理在CPU上由全局光源列表生成。一开始,其位置被放置在使得纹理区域被充分利用的位置,最终以尽可能小的空间,放置在摄像机之后。 在启用并落入索引纹理区域内的光源中,根据优先级,屏幕上的近似大小以及其他因素来选择最相关的光源。每个光源都插入其所覆盖的纹素的可用通道中。如果纹理像素被四个以上的光源覆盖,则需要丢弃此光源。 如果在插入时纹理像素已满,程序将根据图块中的最大衰减系数检查入射光源是否应替换任何现有的光源,以减少掉光源的视觉误差。这些误差可能导致图块边框周围的光照不连续性。通常这些误差很小,但当四处移动时可能非常明显。而为了避免这个问题,可以将索引纹理对齐到纹素大小的坐标中。在实践中,光源的丢弃非常少见,通常很难发现。 [ ![img](MakingitLarge,Beautiful,Fast.assets/bdb0097479d27279d7402dade7dfe206.jpg)](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/%E3%80%8AGPUPro1%E3%80%8B%E5%85%A8%E4%B9%A6%E6%8F%90%E7%82%BC%E6%80%BB%E7%BB%93/media/bdb0097479d27279d7402dade7dfe206.jpg) 图 轴对齐世界空间中的光照索引。 放置纹理使得尽可能多的区域在视锥体内。 图示的4m x 4m区域由两个由R和G通道索引的光源相交。 未使用的插槽表示禁用的光源。 ### ### 3.2 阴影系统 Shadowing System 阴影方面,《正当防卫2》中采用级联阴影映射(cascaded shadow mapping)。并对高性能PC提供软阴影(Soft shadows)选项。虽然在任何情况下都不是物理上的准确,但算法确实会产生真正的软阴影,而不仅仅是在许多游戏中使用的恒定半径模糊阴影。 [ ![img](MakingitLarge,Beautiful,Fast.assets/5b80d8287950dd63abd4d51c0e28b170.jpg)](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/%E3%80%8AGPUPro1%E3%80%8B%E5%85%A8%E4%B9%A6%E6%8F%90%E7%82%BC%E6%80%BB%E7%BB%93/media/5b80d8287950dd63abd4d51c0e28b170.jpg) 图 《正当防卫2》中的软阴影。注意树底部的锐利阴影逐渐变得柔和,以及注意,树叶投下了非常柔和的阴影。 此软阴影算法的步骤如下: 1、在阴影贴图中搜索遮挡物的邻域。 2、投射阴影的样本计为遮挡物。 3、将遮挡物中的中心样本的平均深度差用作第二个pass中的采样半径,并且在该半径内取多个标准PCF样本并取平均值。 4、为了隐藏有限数量的样本失真,采样图案以从屏幕位置产生的伪随机角度进行旋转。 实现Shader代码如下: ``` // Setup rotation matrix float3 rot0 = float3(rot.xy, shadow_coord.x); float3 rot1 = float3(float2(-1, 1) * rot.yx, shadow_coord.y); float z = shadow_coord.z * BlurFactor; // Find average occluder distances . // Only shadowing samples are taken into account . [unroll] for (int i = 0; i 0.0); dd += de; } // Compute blur radius float radius = dd.x / dd.y + BlurBias; rot0.xy *= radius ; rot1.xy *= radius ; // Sample shadow with radius [unroll] for (int k = 0; k