6.游戏中地形大气和云的渲染

  1. 6.游戏中地形大气和云的渲染
    1. 6.1 地形概述
    2. 6.2 地形几何
      1. 高度图 - Heightfield
      2. 自适应网格细分(Adaptive Mesh Tessellation)
        1. FOV与网格密度
        2. 视点相关误差边界的两大原则
      3. 网格细分算法
        1. 三角形细分(Triangle-Based Subdivision)
        2. 四叉树细分(QuadTree-Based Subdivision)
        3. 不规则三角网(Triangulated Irregular Network, TIN)
      4. 基于 GPU 的曲面细分(GPU-Based Tessellation)
        1. 硬件曲面细分(Hardware Tessellation)
        2. 网格着色器管线(Mesh Shader Pipeline)
        3. 实时可变形地形(Real-Time Deformable Terrain)
      5. 非高度场地形(Non-Heightfield Terrain)
        1. 体素化思想(Volumetric Representation)
        2. Marching Cubes算法
        3. Transvoxel算法
    3. 6.4 地形材质
      1. 纹理混合(Texture Splatting)
        1. 简单纹理混合
        2. 基于高度的纹理混合
        3. 基于偏置的高度混合
      2. 材质纹理数组采样
      3. 视差映射与位移映射
      4. 虚拟纹理(Virtual Texture)
      5. DirectStorage与DMA
      6. 浮点数精度误差
      7. 相机相对渲染(Camera-Relative Rendering)
    4. 6.5 植被道路贴花等
      1. 树木渲染(Tree Rendering)
      2. 装饰器渲染(Decorator Rendering)
      3. 道路与贴花渲染(Road and Decals Rendering)
    5. 6.6 大气
      1. 解析式大气外观建模
      2. 参与介质(Participating Media)
      3. 光的交互
      4. 散射
        1. 瑞利散射(Rayleigh Scattering)
        2. 米氏散射(Mie Scattering)
      5. 吸收
      6. 单次散射与多次散射
      7. 光线步进(Ray Marching)
      8. 预计算大气散射
        1. 透射率查找表(Transmittance LUT)
        2. 单次散射查找表(Single Scattering LUT)
        3. 多重散射查找表(Multi-Scattering LUT)
        4. 挑战与局限性
      9. 生产友好型快速天空与大气渲染
        1. 简化多重散射假设
        2. 维度降维优化
        3. 空中透视查找表
        4. 性能与效果平衡
    6. 6.7 云的渲染
      1. 云的类型
      2. 云的实现方式
        1. 基于网格的云建模
        2. 公告板云(Billboard Cloud)
        3. 体积云(Volumetric Cloud)
      3. 体积云的生成
        1. 天气纹理(Weather Texture)
        2. 噪声函数
        3. 云密度模型
      4. 体积云的渲染

6.游戏中地形大气和云的渲染


6.1 地形概述

  • 真实世界中地形是十分重要的部分,尤其是在现代大世界的场景中,可以看到各种美轮美奂的地形效果,我们通常把地形称为Terrain。

  • 地形的制作面临以下挑战:

    1. 游戏地形的尺寸可能十分广阔,如何在有限的硬件中来表现?
    2. 地形中存在大量各式各样的物件:山川、河流、植被、道路……,艺术家要如何处理?
    3. 游戏中需要与地形实时交互:比如雪地中行走留下痕迹、爆炸在地面留下坑洞。



6.2 地形几何

地形与一般的物体没有什么不同,可以用简单的Mesh显示完整的地形效果。但这样如果是高精度地形的话对目前硬件来说是难以承受的。之前处理阴影时,有一个重要的思想:对于距离Camera近的区域,需要的分辨率较高,反之则分辨率需求降低(LOD)。这个思想我们也可以应用到地形中。

高度图 - Heightfield

表示地形最简单的方法是使用高度场(heightfield)。高度场可以理解为地形的俯视图,类似于地理课上的等高线地图:从天空俯视地面,每个位置只记录一个高度值(比如这座山海拔1000米,那个山谷海拔500米)。Heightfield记录了地形的高度数据,我们可以把地形看做是平面上具有不同高度的函数,然后通过在平面进行均匀采样来近似它。原始的地形Mesh可以是精度较低的网格,在运行时根据Camera位置动态细分网格数据,这样就可以极大减少性能压力。


但HeightMap有明显的局限性:作为一张二维的俯视图,它只能处理没有重叠的地形。例如,山的内部有一个巨大的洞窟时,从上方看,山洞的入口和洞顶在同一个位置,但高度不同,高度场只能记录一个值,无法同时表示两者。虽然有一些方式可以处理,但总体来说是不方便的。HeightMap不适合开放世界,面片数会很多,表示大规模的地形或者需要更精细的地形时所需的采样点数会成倍的增长。

自适应网格细分(Adaptive Mesh Tessellation)

核心思想:只对视野内的地形进行精细渲染,视野外的地形使用低精度网格,从而在保证视觉质量的同时降低渲染负担。这类似于人眼的视觉机制——当你专注看眼前的细节时,余光中的东西会变得模糊。游戏引擎采用同样的策略:玩家正在看的地方用高精度网格渲染,视野边缘和远处用低精度网格渲染,既保证了画面质量,又节省了计算资源。

FOV与网格密度

FOV(视场角)越大,视野越开阔,但单位像素覆盖的场景范围也越大,细节会变模糊。这与相机镜头的原理类似:广角镜头(FOV大)能看到更多内容,但每个物体在画面中占的面积小,细节不够清晰;长焦镜头(FOV小)视野窄,但能把远处的物体”拉近”,细节更清晰。因此,网格细分需要根据FOV动态调整:FOV较小时,网格密度应高度集中在视野中心;FOV较大时,网格密度在视野范围内均匀分布。.

视点相关误差边界的两大原则

实现视点相关地形(View-dependent Terrain)需遵循两条原则:

  1. 距离与视野原则:视野外或距离较远的地形使用低密度网格,只有视野内且距离较近的地形才精细细分。
  2. 误差控制原则:网格细分后的高度误差不超过预设阈值(通常1个像素),近处误差小,远处可适当放宽。

上图展示了实际效果:前景三角形更小、更密集,背景三角形更大、更稀疏,形成由近及远的LOD渐变

网格细分算法

细分网格算主要关注什么情况下进行细分如何细分两个元问题。

三角形细分(Triangle-Based Subdivision)

三角形细分基于二叉树递归分割三角形:选择等腰直角三角形的斜边中点,连接对顶点,将原三角形剖分成两个相同的小三角形,等价于为二叉树添加叶节点。


基于二叉树的连续分割过程(l=0到l=5)展示了网格密度的指数级增长。当某个三角形被细分后,必须同时细分与其共享邻边的相邻三角形,通过强制分割(forced split)形成一致网格(conforming mesh),避免T-junction问题导致的渲染裂缝。

T-junction问题的本质是边界不匹配:当一个三角形被细分后,如果相邻的三角形不跟着细分,它们的边界就无法对齐,渲染时就会出现裂缝或”漏光”。这就像拼拼图时,如果一块拼图的边缘被切了一刀,但相邻的拼图没有切,两块拼图就无法完美拼接。

四叉树细分(QuadTree-Based Subdivision)

四叉树细分是游戏行业更常用的方式,更符合直觉,且可直接使用纹理存储四叉树结构。四叉树将地形递归划分为四个子区域,根据视点距离和误差控制原则决定是否继续细分,形成自适应的层次结构。

四叉树处理T-junction问题更简单:将没有对应连接点的顶点自动吸附到已连接的顶点上,形成退化三角形(面积为0),算法实现简单且无视觉伪影。


对比不同网格表示方式可以看出:地形四叉树在中心区域使用高密度网格,外围使用低密度网格,既保证视觉质量又优化性能,是实际应用中的比较好的选择。

不规则三角网(Triangulated Irregular Network, TIN)

对于存在大量平面的地形,均匀采样会造成存储空间浪费。TIN(不规则三角网)根据地形复杂度自适应分配三角形:高度变化不大的区域使用少量大三角形,高程变化剧烈的区域使用更多小三角形,将细分直接做到Mesh中,不再动态计算。这类似于用积木搭模型——平坦的地方用大块积木,复杂的地方(如拐角、细节)用小块积木,既节省了积木数量,又能准确还原模型的形状。

从高度图到TIN的转换过程:左侧是灰度高度图,右侧是生成的3D线框TIN模型。复杂区域(如中央凹陷和山谷)三角形密集,平坦区域三角形稀疏,实现了自适应的三角剖分。

TIN中的密度变化:红色和橙色区域表示高密度(小三角形),蓝色区域表示低密度(大三角形),网格根据几何复杂度自适应调整。

TIN vs. 自适应细分

对比两种方法:TIN(Mesh LOD)的优点是运行时渲染简便,在某些地形类型中三角形数量较少(如示例中仅需50,000个三角形,而Clip-Map LOD需要170,000个);缺点是需要特定的预处理步骤,复用性差。目前TIN在游戏工业界的应用还比较少,主流的地形处理方法仍然是使用均匀采样的网格。

基于 GPU 的曲面细分(GPU-Based Tessellation)

现代游戏引擎将曲面细分工作交给GPU完成,利用GPU的并行计算能力实现地形的实时细化。

硬件曲面细分(Hardware Tessellation)

DirectX 11提供了基于硬件的曲面细分管线,包含三个核心阶段:

  1. Hull Shader(外壳着色器):将基础网格的控制点转换为细分面片(Patch),计算细分因子。
  2. Tessellator(曲面细分器):固定功能阶段,根据细分因子为每个面片生成半规则细分图案,输出重心坐标。
  3. Domain Shader(域着色器):可编程阶段,根据重心坐标计算每个细分顶点的最终位置,可结合位移贴图(Displacement Map)实现细节增强。


曲面细分管线从简单的三角形面片网格开始,经过Hull Shader处理、Tessellator细分、Domain Shader位移映射,最终生成高细节的变形网格,实现了从低精度基础网格到高精度地形的实时转换。

网格着色器管线(Mesh Shader Pipeline)

DirectX 12引入了网格着色器管线,将曲面细分的概念整合到更灵活的架构中:

  • Amplification Shader(放大着色器):决定运行多少个网格着色器组,并将数据传递给这些组。
  • Mesh Shader(网格着色器):为每个面片生成半规则细分图案,输出包含顶点和图元。

网格着色器管线极大简化了Shader处理流程,所有处理流程都可以由程序自己控制,提供了比传统管线更大的灵活性。

:NVIDIA在2019年公布的Turing(图灵)架构对Mesh Shader提供了硬件支持,对应RTX 20系列显卡。DirectX 12同年也宣布对这一特性的支持,因此Windows 10之前版本不支持这一特性。

实时可变形地形(Real-Time Deformable Terrain)

基于GPU的曲面细分进一步支持实时可变形地形,实现与地形的动态交互。以雪地交互为例:当物体在雪地中移动时,使用一张纹理(Render Target)记录物体运动轨迹,然后将该纹理传入地形Shader进行曲面细分,实现雪地的交互变形效果。


这种技术利用GPU的计算性能实现动态地形绘制,通过类似弹簧的物理思想,用Render Target动态改变地形高度,大幅提升了玩家的游戏体验和沉浸感。

非高度场地形(Non-Heightfield Terrain)

有些游戏场景(如洞穴、拱门、悬垂结构)无法使用高度场表示,因为高度场只能表示单一高度值,无法处理重叠和悬垂结构。

曲面细分虽然可以处理地面变形交互,但高度场本身并未真正改变。例如地面出现深坑时,只能视觉上看到坑,角色并不会掉入。体素化地形可以较好地解决这一问题,实现真正的几何变形。

在地形中挖洞可以通过顶点着色器输出NaN来剔除地形顶点,实现真正的几何变化。

体素化思想(Volumetric Representation)

体素(Voxel)是3D空间中的立方体单元,类似于2D图像中的像素。如果把像素比作照片上的小方格,那体素就是3D空间中的小立方体。每个小立方体(体素)可以填充或不填充,最终组成一个3D物体。体素化将几何模型转换为体素表示,产生包含表面信息和内部属性的体数据。

体素的优势:不仅能表示物体的表面,还能表示内部结构。体素可以记录”这个位置是实心的”还是”空心的”,因此可以表示洞穴、悬垂等复杂结构,这类似于CT扫描能够显示人体内部结构。

在三维计算机图形学中,体素代表三维空间规则网格上的一个数值。如同二维位图中的像素,体素本身通常不会将其位置(即坐标)与数值进行显式编码。

Marching Cubes算法

Marching Cubes使用立方体网格填充物体,通过判断立方体8个顶点与物体的内外关系(2^8=256种情况),利用对称性和旋转不变性简化为14种基本配置,预存到查找表中。

算法遍历体素网格,对每个立方体查找对应的三角剖分模式,生成等值面。该算法在医学可视化(如CT扫描重建)、流体模拟等领域有广泛应用。

Transvoxel算法

Transvoxel算法是Marching Cubes的扩展,专门解决不同LOD级别体素网格之间的平滑过渡问题。通过构建过渡单元的三角剖分查找表(512种独立情况,73个等价类别),实现LOD体素立方体的无缝连接。

当使用不同精度的体素网格时(近处用高精度小块,远处用低精度大块),如何实现无缝连接是一个关键问题。Transvoxel算法通过提供”过渡单元”来解决这个问题:在两种精度之间,使用特殊形状的过渡单元来平滑过渡,避免出现明显的接缝或断层。

优缺点

  • 优点:可以处理任意结构的地形,支持真正的几何变形和交互。
  • 缺点:相比常规处理,地形内部多出大量数据,存储和计算开销较大,算法仍在完善中。

对于复杂场景,可以考虑使用体素表达几何,然后利用Marching Cubes或Transvoxel算法生成表面。不过这种方法目前仍处于试验阶段,几乎没有游戏使用相关技术来表示地形。


6.4 地形材质

地形渲染不仅需要处理几何表示,还需要为不同区域添加合适的材质细节。地形通常包含多种材质(如沙、石、草、泥土等),这些材质在不同区域混合,形成自然的地表效果。

地形材质系统使用多种纹理贴图来表示每种材质的属性:底色(Base Color)定义材质颜色,法线贴图(Normal Map)提供表面细节,粗糙度贴图(Roughness)控制光照反射,高度贴图(Height Map)记录表面起伏。通过混合遮罩(Blending Mask)控制不同材质在特定区域的混合比例,最终生成复合的地形材质。

纹理混合(Texture Splatting)

简单纹理混合

最简单的纹理混合方法是基于alpha值的线性插值:

// 简单纹理混合:基于alpha值的线性插值
// texture1, texture2: 待混合的两个纹理(RGB颜色)
// a1, a2: 两个纹理的混合权重(alpha值)
float3 blend(float4 texture1, float a1, float4 texture2, float a2)
{
    // 加权平均混合两个纹理的RGB颜色
    return texture1.rgb * a1 + texture2.rgb * a2;
}

这种方法虽然能实现平滑过渡,但效果不够自然。在沙地和卵石的混合区域,沙子应该更多地填充在石头缝隙中,而不是简单地线性混合,导致过渡区域看起来不真实。

基于高度的纹理混合

为了获得更自然的混合效果,可以引入高度图(Height Map)来指导混合过程。核心思想是:较高的区域应该更多地显示,从较高区域过渡到较低区域时,较高区域的混合权重下降较慢,较低区域快速退去。

基础的高度混合方法通过比较两个纹理的高度值来选择显示哪个:

// 基于高度的纹理混合:根据高度值选择显示哪个纹理
// texture1, texture2: 待混合的两个纹理
// height1, height2: 两个纹理对应的高度值
float3 blend(float4 texture1, float height1, float4 texture2, float height2)
{
    // 高度值较大的纹理优先显示(产生硬切换效果)
    return height1 > height2 ? texture1.rgb : texture2.rgb;
}

这种方法会产生硬切换,过渡不够平滑。高度图曲线展示了两个纹理的高度变化,需要更精细的混合策略。

基于偏置的高度混合

基于偏置的高度混合(Height-Biased Blending)通过引入高度偏置(Height Bias)参数来优化混合效果:

// 基于偏置的高度混合:结合alpha和高度值实现平滑过渡
// texture1, texture2: 待混合的两个纹理(包含alpha通道)
// height1, height2: 两个纹理对应的高度值
float3 blend(float4 texture1, float height1, float4 texture2, float height2)
{
    float depth = 0.2; // 高度偏置参数,控制混合的平滑程度
    
    // 计算最大混合值(alpha + height),并减去偏置
    float ma = max(texture1.a + height1, texture2.a + height2) - depth;
    
    // 计算两个纹理的混合权重(确保非负)
    float b1 = max(texture1.a + height1 - ma, 0);
    float b2 = max(texture2.a + height2 - ma, 0);
    
    // 归一化混合:加权平均并除以权重总和
    return (texture1.rgb * b1 + texture2.rgb * b2) / (b1 + b2);
}

这种方法结合了alpha通道和高度值,通过偏置参数控制混合的平滑程度。混合权重曲线展示了两个纹理的权重如何根据高度值平滑过渡,实现了更自然的材质混合效果。

材质纹理数组采样

当需要混合多种材质时,使用材质纹理数组(Material Texture Array)来管理不同材质的纹理数据。每种材质包含多个纹理贴图(Albedo、Normal、Height等),通过Splat Map(溅斑贴图)存储材质索引,指示每个区域应该使用哪些材质进行混合。

从Splat Map采样得到材质索引后,从材质纹理数组中获取对应的纹理数据。为了获得平滑的混合效果,需要在多个方向(基础、右侧、上方、右上方)采样材质,然后根据高度值计算混合权重,使用双线性插值进行混合。这种方法能够处理复杂的多材质混合场景,生成自然的地形效果。

视差映射与位移映射

为了让平坦的地形表面产生几何感,可以使用多种技术来模拟或实现表面细节:

  1. 色彩映射(Color Mapping):直接将纹理颜色绘制到表面,完全平面,无深度感。

  2. 凹凸贴图(Bump Mapping):通过修改法线来模拟表面细节,在光照计算时产生凹凸的视觉效果,但表面轮廓仍然是平的。

  3. 视差映射(Parallax Mapping):通过对纹理UV坐标进行偏移来模拟高度差。由于表面高度的影响,人眼会看到B点而非A点,从而营造出立体感。这种方法比凹凸贴图更真实,但仍不改变几何形状。

  4. 位移贴图(Displacement Mapping):在顶点着色器中对顶点沿法线方向进行实际位移,真正改变几何形状,产生最真实的效果,但计算开销最大。

虚拟纹理(Virtual Texture)

当对多种材质进行混合时,需要频繁地在不同纹理之间跳转采样,导致性能问题。此外,大规模地形需要覆盖数百平方公里的Splat Map,但玩家只能观察到一小片区域,将整个Splat Map载入显存会造成巨大的内存浪费。

多重纹理处理会导致采样次数过多:4个位置采样加上4种材质各3个纹理属性,总共需要16次采样,严重影响性能。

虚拟纹理(Virtual Texture)技术很好地解决了这个问题。核心思想是:预先将混合好的材质烘焙到瓦片中,根据视距相关的LOD确定需要使用的图块,只加载必要的纹理数据到显存。

虚拟纹理系统包含三个核心组件:

  1. 虚拟纹理Mipmap:构建虚拟索引纹理来表示整个场景中所有混合地形材质,包含多个LOD级别。

  2. 页表(Page Table):将虚拟纹理页映射到物理纹理位置,实现虚拟地址到物理地址的转换。

  3. 物理纹理(Physical Texture):实际存储在显存中的纹理数据,只包含当前需要渲染的图块。

系统根据视点位置和LOD需求,从虚拟纹理Mipmap中选择需要的图块,通过页表查找对应的物理纹理位置,只加载必要的纹理数据。这种方式极大地缓解了纹理读写的内存需求,提高了渲染效率。

DirectStorage与DMA

虚拟纹理的性能瓶颈在于从硬盘加载纹理的IO过程。传统的GPU读取流程是:硬盘 → 内存 → 显存,中间的数据传递需要CPU参与,十分耗时。

DirectStorage技术优化了这一流程:传递未解压的数据,由GPU完成解压。数据流程变为:NVMe SSD → 系统内存 → GPU内存 → GPU解压。GPU支持更高的解压带宽,CPU节省显著,数据在到达目的地之前保持最小体积。

GPUDirect Storage(DMA)技术更进一步,允许GPU直接从NVMe SSD读取数据,绕过CPU和系统内存,实现真正的直接内存访问。这进一步减少了数据传输延迟,提高了大规模地形纹理的加载效率。

浮点数精度误差

在渲染大规模地形时,当物体和相机处于大数值范围(从1米到数万公里)时,32位浮点数的精度限制会导致严重的渲染问题。

IEEE 754单精度浮点数使用32位存储(1位符号、8位指数、23位尾数),随着数值增大,小数部分的精度会显著下降。精度曲线图显示,单精度浮点数在大数值时精度急剧下降,可能导致顶点位置抖动、穿模等现象。

相机相对渲染(Camera-Relative Rendering)

相机相对渲染通过将相机设置为世界坐标的中心来解决浮点数精度问题。在任何其他几何变换之前,通过取反的世界空间相机位置来平移所有对象,然后将世界空间中的相机位置设置为原点,并相应调整所有相关矩阵。

// 相机相对渲染:将所有对象转换为相对于相机的位置,解决浮点数精度问题
foreach render_object in render_objects
{
    // 将对象位置减去相机位置,使对象相对于相机定位
    render_object.m_position -= render_camera.m_position;
    // 更新对象的变换矩阵
    updateRenderObjectTransform();
}

// 将相机位置设置为原点(0, 0, 0)
render_camera.m_position = Vector3(0.0, 0.0, 0.0);
// 更新相机的视图投影矩阵
updateRenderViewProjectionMatrix();

这样处理后,相机视野范围内的对象都处于相对较小的数值范围内,可以使用浮点数精确表示,避免了精度误差导致的渲染问题。这种方法使得渲染整个星系这样的大规模场景成为可能。


6.5 植被道路贴花等

除了地形几何和材质,游戏场景还需要大量细节元素来增强真实感:植被、道路、贴花等装饰件(Decorator)。这些元素看似简单,但要实现高质量效果需要复杂的渲染技术。

树木渲染(Tree Rendering)

树木是开放世界场景中最常见的元素之一,但每棵树都包含大量几何细节,直接渲染会带来巨大的性能负担。

LOD策略:根据距离使用不同的表示方法。近处使用完整的三维网格(Mesh),包含详细的树干、树枝和密集的树叶几何;中距离使用插片(Billboard)技术,用多个平面贴图模拟树叶,插片数量随距离增加而减少;远处则简化为单个2D billboard纹理,完全平面化表示。

LOD系统从高细节到低细节的渐进过程:最高细节使用复杂多边形和密集线框;中等细节减少多边形数量,部分使用平面表示;最低细节完全简化为2D纹理,用包围盒标识。这种策略在保证视觉质量的同时,大幅降低了渲染开销。

装饰器渲染(Decorator Rendering)

装饰器包括草地、灌木、小石块等小型环境元素。这些元素数量庞大,需要特殊的渲染优化策略。

材质混合流程:装饰器渲染与地形材质系统紧密集成。首先定义多种材质(如砖块、泥土、草地)的纹理贴图(底色、法线、粗糙度、高度),然后通过混合遮罩(Blend Mask)控制不同材质在特定区域的混合比例,最终生成复合的地形材质。

装饰器LOD:与树木类似,装饰器也采用LOD策略。以草地为例:高细节使用密集的草叶几何,每个草叶都是独立的几何体;中细节减少草叶数量,简化几何复杂度;低细节使用极简的几何表示,最少的多边形数量。早期游戏的装饰器会一直面向相机(Billboard),现代引擎则根据距离和视角动态切换不同的LOD级别。

道路与贴花渲染(Road and Decals Rendering)

道路和贴花是地形系统中较为复杂的部分,不仅涉及纹理渲染,还需要处理几何变形和高度场修改。

基于样条的道路编辑:道路通常使用样条(Spline)系统进行编辑。通过控制点定义道路路径,系统自动生成平滑的曲线。道路不仅需要在地形表面绘制纹理,还需要修改高度场,使道路区域的地形高度符合道路几何,形成真实的凹陷或凸起效果。

贴花技术贴花(Decal)是将纹理附着在对象表面的技术,用于添加局部细节,如道路上的裂缝、建筑上的弹孔、角色脸上的花纹等。贴花通过投影方式将纹理映射到目标表面,可以叠加在现有材质上,实现细节增强。

虚拟纹理集成:现代游戏引擎通常使用程序化方式生成地形物件,并将所有纹理(包括道路、贴花、装饰器等)直接烘焙到虚拟纹理(Virtual Texture)中。这种方式将复杂的多纹理混合计算从运行时转移到预处理阶段,大幅减少了渲染时的采样次数和计算开销,提高了渲染效率。


6.6 大气

解析式大气外观建模

对于天空和大气的渲染,最简单的方法是使用解析式拟合公式直接计算着色。这种方法类似于Blinn-Phong模型,通过视角到天顶和太阳的两个夹角来确定天空颜色。

优点

  • 计算简单高效,适合实时渲染

缺点

  • 仅限于地面视角,无法处理从空中或太空观察的情况
  • 大气参数不能自由更改,缺乏灵活性

这种方法能够快速生成逼真的天空效果,但受限于特定的观察条件和参数范围。对比照片和渲染效果图可以看出,解析式模型能够较好地模拟日出日落时的天空渐变效果。

参与介质(Participating Media)

大气由气溶胶组成,所以才能表现出特殊的光学现象。气溶胶可以看做是很多细小的颗粒,光线在这些颗粒之间不断弹射,形成复杂的光学现象。

光的交互

光线在参与介质中传播时,会发生四种基本交互:吸收(Absorption)、外散射(Out-scattering)、发射(Emission)和内散射(In-scattering)。

辐射传输方程(RTE)描述了光线在介质中传播时辐射度的变化率:

dL(x,ω)/dx = −σtL(x,ω) + σaLe(x,ω) + σs ∫S² fp(x,ω,ω')L(x,ω')dω'

其中:

  • σa:吸收系数(Absorption Coefficient)
  • σs:散射系数(Scattering Coefficient)
  • σt = σa + σs:消光系数(Extinction Coefficient)
  • **fp(x,ω,ω’)**:相位函数(Phase Function),描述从方向ω’散射到方向ω的概率分布

方程右侧三项分别表示:消光损失(吸收+外散射)、介质发射增益、内散射增益。

体积渲染方程(VRE)通过对RTE沿光路积分,计算从点M到点P的最终辐射度:

L(P,ω) = ∫₀ᵈ T(x)[σa·Le(x,ω) + σs·Li(x,ω)]dx + T(M)L(M,ω)

其中:

  • **T(x) = e^(-∫ₓᴾ σt(s)ds)**:透射率(Transmittance),表示从点x到P的衰减因子
  • **Li(x,ω) = ∫S² fp(x,ω,ω’)L(x,ω’)dω’**:内散射辐射度(In-scattered Radiance)

VRE沿光路积分计算每个点的吸收、散射和接收周围散射的能量,最终得到观察点P处的颜色。

散射

求解VRE最复杂的地方在于如何计算散射项。大气渲染主要考虑两种散射类型:瑞利散射(Rayleigh Scattering)和米氏散射(Mie Scattering)。

地球大气由空气分子(N₂、O₂、O₃)和气溶胶(尘埃、沙尘)组成。阳光包含不同波长的可见光(从短波长的蓝光到长波长的红光),这些光线与大气粒子发生散射,产生不同的光学现象。

瑞利散射:由直径远小于辐射波长的粒子(如空气分子)引起的光散射。
米氏散射:当颗粒(如气溶胶)的直径与入射光波长相近或更大时发生的光散射。

瑞利散射(Rayleigh Scattering)

当大气中介质的尺寸远小于光线波长时发生瑞利散射,散射行为只与波长有关,呈现前后对称的分布。

瑞利散射方程

S(λ,θ,h) = (π²(n² - 1)²ρ(h) / 2N) · (1/λ⁴) · (1 + cos²θ)

散射系数:σ_s^Rayleigh(λ,h) = (8π³(n² - 1)²ρ(h) / 3N) · (1/λ⁴)

相位函数:F_Rayleigh(θ) = (3 / 16π) · (1 + cos²θ)

核心特性

  • 散射强度与波长的四次方成反比(1/λ⁴),短波长(蓝光)散射更强
  • 前后方向散射强度大于垂直方向,呈现对称分布

天空为什么是蓝色的:白天太阳光中短波长的蓝光被大量散射到各个方向,观察者接收到来自四面八方的散射蓝光,因此天空呈现蓝色。日出日落时,太阳光需要穿越更长的大气路径,蓝光被大量散射掉,只剩下未散射的红光到达观察者,因此太阳和天空呈现红色或橙色。

米氏散射(Mie Scattering)

当大气中介质的尺寸接近或大于光线波长时发生米氏散射,散射程度与波长无关,只与观测方向有关,呈现强烈的正向指向性。

米氏散射方程

S(λ,θ,h) = π²(n² - 1)²ρ(h) / N · (1 - g²) / (2 + g²) · (1 + cos²θ) / (1 - g² - 2gcosθ)^(3/2)

散射系数:σ_s^Mie(λ,h) = 8π³(n² - 1)²ρ(h) / (3N)

相位函数:F_Mie(θ) = 3 / (8π) · (1 - g²) / (2 + g²) · (1 + cos²θ) / (1 - g² - 2gcosθ)^(3/2)

几何参数g的影响

  • g > 0:更倾向于前向散射
  • g < 0:更多向后散射
  • g = 0:退化为瑞利散射

核心特性

  • 所有波长的散射光几乎均匀分布
  • 展现出强烈的正向指向性

日常应用:米氏散射导致太阳周围的光晕效应(强烈的正向指向性)和雾效(几乎均匀散射所有波长的光)。

吸收

除了散射外,不同气体分子对不同波长的光线具有选择性吸收特性,影响大气的颜色表现。

臭氧(O₃):强烈吸收较长波长的光线,滤除红色、橙色、黄色。日落时分,天顶附近的蓝天部分由臭氧吸收长波长光线所致。

甲烷(CH₄):以吸收红光而闻名。海王星呈现蓝色正是因为其大气中富含甲烷,吸收了红光,使蓝光得以反射。

单次散射与多次散射

单次散射:光线在介质中只发生一次散射事件后到达观察者。单次散射积分公式:L1 = ∫A^B Lp->A ds

多次散射:光线在介质中发生多次散射事件后才到达观察者。多次散射积分公式:Ln+1 = ∫A^B ∫4π Ln(p,v') · S(λ,θ,h) · T(p -> A)dv'ds,需要考虑整个空间中介质粒子的贡献。

视觉效果对比:单次散射时,阴影区域几乎完全黑暗,缺乏环境光照;多次散射时,大气散射的光线照亮阴影区域,呈现更真实的蓝色调,细节更丰富。现实中气体散射会影响场景光照,山的背面被空气的散射照亮,需要使用多次散射才能正确模拟。

光线步进(Ray Marching)

光线步进是一种沿路径积分函数的常用方法。核心思想:将整条光线分解成N段,在每一小段上单独进行积分计算,最后将N段的积分结果相加。

单次散射积分L_sun ∫_A^B S(λ,θ,h) · (T(sun -> P) + T(P -> A)) ds

其中:

  • **S(λ,θ,h)**:散射函数,依赖于波长、散射角和高度
  • **T(sun -> P)**:从太阳到路径点P的透射率
  • **T(P -> A)**:从路径点P到观察点A的透射率

积分辐射度通常存储在查找表(LUT)中,以提高实时渲染效率。

预计算大气散射

为了避免实时计算复杂的散射积分,游戏引擎采用预计算查找表(LUT)的方式:将大气散射参数化为关键变量的函数,离线预计算并存储在纹理中,运行时通过查表快速获取结果。

透射率查找表(Transmittance LUT)

透射率T(x) = e^(-∫x^P σt(s)ds),表示光线从点x到点P的衰减因子。

透射率可参数化为高度h和**视角天顶余弦角μ = cos(θ)**的二维函数,预计算存储在2D纹理中。任意两点间的透射率可通过公式计算:T(Xv -> Xm) = T(Xv -> B) / T(Xm -> B),其中B为大气边界。

单次散射查找表(Single Scattering LUT)

单次散射参数化为高度h、**视角天顶余弦角μ = cos(θ)太阳天顶余弦角μs = cos(η)散射角余弦v = cos(φ)**的四维函数。由于4D表存储开销大,通常将4D表压缩存储在3D纹理阵列中,通过视角-太阳余弦角组合(v, μs)和高度h进行索引。

多重散射查找表(Multi-Scattering LUT)

多重散射通过迭代计算生成:以透射率LUT和单次散射LUT为输入,进行散射光积分,然后通过N阶散射迭代过程(每次迭代将前一次结果作为输入),最终生成多重散射LUT。目前很多3A游戏的天空渲染都基于这种方式处理。

预计算大气散射能够实现高质量的天空和大气渲染效果,广泛应用于现代游戏引擎。

挑战与局限性

预计算成本:多重散射迭代的计算成本非常高,在低端设备(如移动设备)上难以生成大气查找表。

环境创作与动态调整:艺术家无法实时修改散射系数,难以渲染动态天气效果(如从晴天到雨雾的转变)和行星间的太空穿梭等场景。

运行时渲染开销:昂贵的逐像素高维纹理采样用于透射率查找表和多重散射查找表,出于效率考虑通常需要降采样,对移动设备不够友好。

生产友好型快速天空与大气渲染

为了缓解预计算大气散射的挑战,人们开发了各种近似方法,在保证视觉效果的同时大幅降低计算和存储开销。

简化多重散射假设

核心假设

  1. 各向同性相位函数:阶数大于或等于2的散射事件采用各向同性相位函数执行
  2. 局部均匀性:当前着色位置邻域内的所有点均接收到相同数量的二阶散射光
  3. 忽略可见性:不考虑遮挡和阴影

多重散射公式

  • 迭代公式:G_n+1 = G_n · f_ms
  • 总多重散射因子:F_ms = 1 + f_ms + f_ms² + f_ms³ + ... = 1 / (1 - f_ms)
  • 总多重散射辐射度:Ψ_ms = L_2nd order · F_ms

通过几何级数表示光线经过无限次散射后到达相机的能量比例,避免了复杂的迭代计算。

维度降维优化

固定视角位置与太阳方位:假设观察位置和太阳位置不变,光照只依赖于观测方向,从LUT中剔除两个维度。这样可以将高维纹理降维,大幅减少存储和采样开销。

空中透视查找表

3D空中透视LUT:通过光线步进技术生成3D查找表,用于评估空中透视效果。LUT参数包括视角天顶角、光视角水平角和到相机的距离,能够快速计算大气对远处物体的散射影响。

性能与效果平衡

可扩展性:从移动设备到高端PC都能实现良好的性能表现。在PC(NVIDIA GTX 1080)上,各LUT的渲染时间在0.01-0.07ms之间;在移动设备(iPhone 6s)上,通过降低分辨率和步数,渲染时间控制在0.11-0.53ms之间,仍能保持高质量的视觉效果。

优点

  1. 参数化控制:虽然不完全基于物理,但符合迪士尼材质原则,通过调节几个参数即可得到各种效果
  2. 艺术创作友好:方便艺术家创作,可以实现非现实场景(如两个太阳的天空、气候变化等)
  3. 硬件友好:省去大量复杂的运算和存储,适合实时渲染

6.7 云的渲染

在大气的基础上添加云可以实现更加真实的环境渲染效果。目前3A游戏一般使用体积云的方式进行渲染,尽管计算复杂度较高,但能实现逼真的渲染效果。

云的类型

根据高度和形态,云可分为多个类型:

低云族(< 2000m)

  • 层云(Stratus):云体均匀成层,呈灰色,似雾但不与地接,主要由小水滴构成,厚度400-500米
  • 层积云(Stratocumulus):结构松散的大云块、大云条组成的云层,颜色灰白或灰色

中云族(1500-4000m)

  • 高层云(Altostratus):均匀的厚层云
  • 高积云(Altocumulus):小块状云团

高云族(> 4000m)

  • 卷云(Cirrus):由高空的细小冰晶组成,云薄而透光良好,色泽洁白
  • 卷层云(Cirrostratus):薄而透明的层状云
  • 卷积云(Cirrocumulus):小波纹状云

垂直发展云

  • 积云(Cumulus):蓬松的白色云团
  • 积雨云(Cumulonimbus):巨大的垂直发展云,可延伸至8000米以上

云的实现方式

基于网格的云建模

优点:高质量

缺点:总体昂贵,不支持动态天气

早期使用网格建立云的模型,可以渲染出高质量的云,但由于非常不灵活,现在基本已经弃用。

公告板云(Billboard Cloud)

优点:高效

缺点:有限的视觉效果,有限的云类型

使用透明通道的公告板面片来近似云效果,虽然高效但很难生成逼真的渲染效果。

体积云(Volumetric Cloud)

优点

  • 逼真的云形状
  • 可能实现大规模云
  • 支持动态天气
  • 动态体积光照和阴影

缺点:必须考虑效率

体积云是目前主流的云渲染方式,能够实现逼真的渲染效果。

体积云的生成

天气纹理(Weather Texture)

体积云使用天气纹理来处理。天气纹理分为两个部分:云的分布纹理云的厚度纹理。通过结合天气纹理和云类型参数(高度和类型),可以生成不同类型的云:层云(Stratus)、层积云(Stratocumulus)、积云(Cumulus)等。当需要让云产生变化时,只需要对天气纹理做扰动处理。

噪声函数

Perlin噪声:生成平滑的随机游走模型,通过网格定义、点积计算和插值生成连续的噪声场。

Worley噪声(Voronoi噪声):模拟细胞的生成,根据空间中的点对空间进行划分,产生细胞状纹理。

云密度模型

基础分布:使用天气纹理定义云的基本分布。

基础形状:使用RGBA通道存储噪声信息:

  • R通道:Perlin-Worley噪声
  • GBA通道:分层Worley噪声

采样公式:SNsample = R(snr, (sng × 0.625 + snb × 0.25 + sna × 0.125) - 1, 1, 0, 1)

更多详情:使用3个低分辨率Worley噪声进行细节增强,公式:DNfbm = dnr × 0.625 + dng × 0.25 + dnb × 0.125

生成体积云模型时,首先使用天气纹理产生柱状的云层,然后利用Perlin噪声和Worley噪声进行腐蚀,通过多层级噪声对云模型进行细节处理,产生逼真的云模型。

体积云的渲染

体积云的渲染方式与大气类似,使用光线步进(Ray Marching)技术:

  1. 步骤一:为每个屏幕像素投射光线
  2. 步骤二:大步步进直至触及云层
  3. 步骤三:云层内部密集步进采样
  4. 步骤四:收集太阳散射辐射

由于云的通透度很低,不需要像大气那样复杂的处理。我们看到的云是GPU中的三维纹理,使用光线步进的方式解析出来,通过计算散射实现逼真的渲染效果。



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏