Bootstrap

Games104——游戏中地形大气和云的渲染

原文链接

原文链接

地形的几何

Heightfield高程图

虽然方法古老,但依旧是现代游戏的主力,因为简单有效:先建立密集顶点MeshGrids(比如1km*1km,每米一个顶点),再用Heighfield对每个顶点进行位移和材质渲染。

网格自适应细分

但是在开放世界里几百公里的地形这个方法就计算量太大了。在开放世界中经常使用LOD处理远处地形,但因为地形的特殊之处在于一直是连续的,因此需要使用一些技术比如下图,根据视角FOV(八倍镜原理)和距离进行网格的自适应细分
在这里插入图片描述
优化视图两条原则:

  1. 离相机越近越密集,fov越小越密集;
  2. 要保证在简化合并的时候,采样点变少,地形高低误差不能超过一个阈值(Error Bound,这个阈值是在view空间上,理想情况下不要超过一个像素)。

三角形的剖分

二叉树

二叉树是最基础的切分方法,就是对等腰三角形的斜边进行切分,如下图,黑色是看不见的区域,高亮是可见区域

问题:对地形数据的管理和剖分算法不符合制作地形时候的直觉(将大地形切成豆腐块,即四叉树思想),因此用的不太多
在这里插入图片描述

T-Junctions

如果某一条边的两侧采样密度差很多,可能会导致出现高低差裂缝(稀疏那侧在这条边上没有采样点,密集那侧可能采样点的高度和稀疏侧不同),这时候就必须对稀疏的一侧也进行进一步采样直到缝隙消失。这个行为就是T-Junctions
在这里插入图片描述

四叉树

最常用的方法仍是四叉树
在这里插入图片描述
在这里插入图片描述
优点:容易构建;因为和其他几何材质数据形式更贴合,所以易于管理各类几何空间数据,包括Culling和tex streaming等
缺点:切分没三角形那么灵活;叶子结点的方格必须是连续的
四叉树也会遇到T-Junctions问题。解决方案让采样密度高的一侧多出来的采样点调整高度,吸附到采样密度低的那条边上(退化三角形,将多出来的切分点吸附到另一边上),如下图
在这里插入图片描述

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

用得不多,但前沿
在这里插入图片描述

优点:容易实时渲染;在特定地形下需要的三角形更少(比如大面积平原、沙漠)
缺点:需要特定预处理;重复使用性低
上边实时处理的方法比TIN渲染的三角形呢?:上边的方法是等间隔采样,不能保证每个采样点正好在高频区域,而TIN是定制的

GPU Drived Tessellator(Hardware Tessellation)

外壳着色器(Hull Shader)、镶嵌器(Tessellator)、域着色器(Domain Shader)-----(老师说)反人类设计,还好有新的Mesh Shader来代替了(Vulkan和DX12以上 win10+,win7不支持)

Mesh Shader

在传统的图形管线中,顶点着色器和几何着色器是按顶点或图元(如三角形)顺序执行的,这种方式在处理大量几何体时可能会导致 GPU 的计算能力没有被充分利用。而 Mesh Shader 则采用了一种基于任务的并行处理模型,它可以将一个大的几何体(如一个模型或场景)分解成许多小的任务(称为“meshlets”),每个任务包含一小部分的顶点和图元,然后这些任务可以在 GPU 上并行处理。这种方式可以更好地利用 GPU 的并行计算能力,从而提高渲染效率。
在这里插入图片描述

meshlet:一个 meshlet 是一个包含一组顶点和图元的小型几何体,它是 Mesh Shader 处理的基本单位。

GPU Drived Tessellator可以实时调整所有顶点的位置和数量,实现实时变形地形(Real-Time Deformable Terrain,类似于堆弹簧在地面上,当压他的时候就会变形)->(黑神话悟空预告片中雪地场景)。

Non-HeightField

像桂林象鼻山这种有洞的地形就不能使用HeightField生成,比如在山体模型上挖个洞做隧道,通过取一个隧道中心点,将其位置设为NAN,现代引擎就会自动去掉所有包含这个点的三角形,最终形成一个洞,后续再用一个美术的隧道模型插进去掩盖锯齿状的边缘

体素化Marching Cube

医学CT建模、点云等都用到了Marching Cube,对应到地形的情况可以做一个查找表,具体的看原文
在这里插入图片描述

地形的材质

真实世界中材质数量很多,有大量材质混合情况。

texture splatting纹理混合

用于2种材质混合:

传统纹理混合相当于用alpha混合的逻辑,但是太过平滑不够真实,一看就是p的

在这里插入图片描述
在这里插入图片描述
后来有了利用地形高度混合的方法,谁高显示谁的纹理,见右图,效果还不错,但是由于2种材质是01切换,在相机移动或者视角很低时,会出现抖动或者分界线sharp的问题
在这里插入图片描述
所以又引入一个高度bias来优化,并且结合高度和alpha一起过渡,代码和效果如下图
在这里插入图片描述

Texture Array

多材质混合时使用(一般1个点4种材质),把所有要混合的材质纹理放入Texture Array中,根据index及其权重来进行混合计算:获取材质index,分别获取各材质纹理,计算混合权重,混合各材质。

「注意:Texture Array与3D texture不同,3D texture是用cube的八个顶点采样并上下两个面进行Tri-linear三线性插值计算最终采样结果,而Texture Array每层之间没关系」
在这里插入图片描述

视差贴图,位移贴图

法线贴图只能实现平面上的光照差异,但模型表面和纹理感都很塑料。(大部分应用)
视差贴图在视觉上做了一个offset,从而让视角效果更好。但是视差贴图成本高些,因为需要走几步更新一下,并且几何上实际还是没有改变。
位移贴图是利用高度贴图将简单网格按照高度细分,改变网格位置
在这里插入图片描述

Virtual Texture虚拟纹理

用Texture Array进行纹理混合时,每个点需要采样4组材质,这是非常昂贵的;并且在很远的地方也需要采样大量同精度纹理,也不合理,因此有了虚拟纹理技术。

原理是把需要用到的纹理tiles based on viewdepend LOD(虚纹理)地装载到内存中的一张物理纹理中,并且保存其uv、mipmap level等信息的映射关系,渲染时直接采样这张图,可以大大节省效率;并且在摄像机移动时,动态的更新替换其中的一部分。并且可以在烘焙tile的时候直接融合好材质再放入物理纹理中。

虚拟纹理应用非常广泛,已经一统天下了
在这里插入图片描述
在这里插入图片描述
正常数据流程是硬盘-内存-显存,而DirectStorage 可以让压缩数据经过内存,直达GPU显卡里解压,CPU只需要传递压缩后的数据,传递效率能提高
索尼的DMA技术能直接把数据从硬盘传到GPU,并且显卡之间的数据交换也可以直连,不需要转手内存了

浮点数精度溢出

浮点数是32位表示,精度是有限的,所以数字越来越小或者越来越大是就会出现问题,比如当相机和某个点距离过大时,最小精度可能会超过1,然后就会出现抖动,这就是浮点数的精度溢出;虽然可以扩大数据精度使用double,但也不能覆盖广阔的数据范围。

解决方案Camera-Relative Rendering:在任何其他几何变换影响物体之前,通过重设世界空间相机位置来转换物体,用相对位置来计算,比如UE5会把一个关卡切成很多个subLevel,分别重置世界坐标

植物道路贴花等

在近处时使用Mesh,到远处后慢慢使用插片来进行表达
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

大气Atmosphere(听个响得了)

大气散射理论

Analytic Atmosphere Appearance Modeling(经验模型)

在这里插入图片描述
类似光线追踪,大气渲染也有类似渲染公式,在实际处理中也有类似 Blinn-Phong的拟合模型。关键参数是view到天顶的角度和到太阳的角度,给出两个参数就可以通过以上公式算出一个解析解,就可以得到颜色

优点:计算简单且有效
缺点:只有地表视角;参数写死,得出的解也是固定的,真实情况的下雨等情况不能覆盖

Participating Media参与介质

指构成大气中各种透明又不透明的粒子,主要有各种气体和气溶胶介质
在这里插入图片描述
光和大气介质的交互:
光被吸收(Absorption)
光被散射(Out-scattering)
自发光(Emission)
周边被点亮的气体,散射过来照亮自己(In-scattering)

辐射传递方程RTE(Radiative Transfer Equation)

这四种合到一起就形成了辐射传递方程RTE(Radiative Transfer Equation)----知道概念,不用记,后续全都预计算
(σ𝑎:吸收系数、σ𝑠:散射系数)
在这里插入图片描述

体积渲染公式VRE(Volume Rendering Equation)

RTE表达的是一个梯度,对RTE积分,就得到体积渲染公式VRE(Volume Rendering Equation)
在这里插入图片描述
其中有2个主要变量:

Transmittance(通透度):假设我在M点放一个东西,到p点还能剩下多少(路径积分的结果)
Scattering Function:路径叠加到的沿途散射过来的光,打到相机的部分

大气物理学(Real Physics in Atmosphere)

大气主要有两个参数者:

太阳:太阳由各种不同的波长的光组成
大气:主要有两类----气体(小于太阳波长)和气溶胶(接近太阳波长)

Rayleigh Scattering(瑞利散射)

当空气中介质尺寸远小于光的波长的时候(气体):

空气中的光是四面八方均匀散射,不太具有方向性
对于越短的波长(蓝紫光),它散射的越远,对越长的波长(红光),散射的越近
Rayleigh Scattering Equation:
在这里插入图片描述

λ:光的波长,也说明波长越短,散射越多
θ:入射光和散射光的夹角(旋转不变)
h:海拔高度
ρ:大气密度,在海平面上等于1,随着高度上升呈指数递减
n:空气折射率
可以看到,在给定海拔高度和空气密度时,散射方向只与face function(形状函数)也就是1+cos^2 θ有关,参数只有一个θ角。

这就可以解释
为什么天空是蓝色----波长短散射多;
为什么晚霞是红色----入射角小蓝色散射出视角了,剩下红色;

在这里插入图片描述

Mie scattering(米氏散射)

当空气中介质尺寸接近或大于波长时(气溶胶):

散射有一定方向性,更多散射再光的传播方向
但对波长不敏感,同等的散射所有波长
米氏散射方程(Mie Scattering Equation)
方程引入了一个Geometry Parameter:g
在这里插入图片描述

g = 0时,就变成了瑞利散射的形状
g > 0 时,更接近于图中章鱼形状
g < 0 时,会往相反的方向散射得更多(g一般大于0)
日常生活中的雾就是气溶胶,表现为无差别散射,所以是白色的;而傍晚的光晕是有方向性的米氏散射,所以有个halo在太阳附近(雨天的路灯也一样)

Variant Air Molecules Absorption(光的吸收)

空气中的臭氧(O3)和甲烷(CH4)能够吸收长波的光,比如臭氧吸收红橙黄光,甲烷吸收红光。(海王星为什么是蓝色—其表面的大量甲烷吸收了红光,显现蓝色)

我们计算也要考虑这种光的吸收,但实际计算过程中会假设这些气体是均匀分布在整个大气中的(实际上并不是,比如臭氧集中在大气上层)

single Scattering vs Multiple Scattering(单次散射 vs 多次散射)

在这里插入图片描述

单次散射:眼睛看到一个点,看到的是该方向所有被太阳光照亮后,散射到我眼睛这个方向的能量和的Transmittance积分
多次散射:其他物体散射的光照亮我实现方向的粒子,相当于光追的多Bounce-----现代3A游戏必须面对的重要问题
「多散射和GI的区别:GI是光照射到了一个面,然后反射到其他地方最后照射的你的眼睛,而多散射则作用于空间中连续不断地空气」
在这里插入图片描述

实时大气渲染

Ray Marching 我们都知道是沿着视线路径把结果一步一步积分的方法

PreComputed Atmospheric Scattering

Transmittance LUT
在这里插入图片描述
这里也可以借助预计算的思想,先用Ray Marching去计算给定点的最终的单次散射的Transmittance,并把最终积分结果存放到 LUT中,通过两个参数:θ:视线和天顶(y+)的夹角;h:海拔高度,就可以获得Transmittance。

并且,如果视点到B的通透度:𝑇(𝑋𝑣−>𝐵),中间点m到B的通透度:𝑇(𝑋𝑚−>𝐵),那么视点到m的通透度为

有了这个公式之后,单次散射公式就可以表示为:

Single Scattering LUT
在这里插入图片描述
这里最重要的是一种参数化思想,将三维角表达为3个角度,做成一组LUT图,叫Single Scattering LUT(4维表),来表示站在所有点在所有太阳角度下看任何方向的一次散射的预计算结果,将其保存在个3Dtexture array里(其实应该用3D texture,方便中间高度采样)。

其中:
η:视点到太阳的方向和天顶夹角,cos值为μs
θ:视线和天顶夹角,cos值为μ
φ:视点到太阳的方向与视线夹角,cos值为v
h:视点海拔高度

Multiple Scattering LUT
在这里插入图片描述
进一步的,通过通透度LUT图和单次散射LUT图,就可以计算出二次、三次以及更多次散射的LUT表(一般3、4次就够了),这些图与一次散射LUT图长得一模一样。

借用以上这些与计算图就可以在runtime下达到一个很好效果的实时计算。

该预计算方法缺点:
预计算耗费很大,多次散射的迭代计算成本高,其次是在移动端没法用。(即使在PC上用computeShade需要几毫秒甚至一秒,但可能分到很多帧去完成)。
没法处理动态环境调整,比如晴天到阴雨的过渡,需要有个平滑的过渡,因此每帧的表都要计算,消耗过大;同时艺术家编辑时调节参数也不方便。
Runtime实时处理时需要做很多逐像素的高维LUT表的插值(为了效率经常要下采样)。

(预计算方法简化版)

前沿的游戏行业简化计算方法,UE正在用的:A Scalable and Production Ready Sky and Atmosphere Rendering Technique,pdf连接
在这里插入图片描述
他假设:散射是低频的,对于空气中的分子的散射是各向同性的,因此光是均匀的,散射也是均匀的,每级散射直接按百分比衰减就行了,多次散射就变成了等比数列,介质多次散射的结果总和就变成了数列和。???

这样一来就极大加快了速度,可以每一帧都进行计算了,那原方法LUT中的高度h参数就没有意义了,同时每帧都算的话,太阳顶角的参数也就不需要了,所以新方法的LUT表中只需要计算出观察方向的天顶角 θ 和一个水平方向环绕360度的夹角 ϕ(对应原来太阳到观察视角水平方向的夹角)即可,化四维为二维,见下图右。
在这里插入图片描述

如果再加上大气中沿着路径的透明度积分影响,那就再加上一个相机距离的参数,形成一个3Dtexture,见上左图。

这样一来,太阳月亮、下雨晴天等情况就都能表达了;只有在空气散射度特别高、雾很浓的情况下,会有比较大的偏差,甚至有色偏。并且效率非常高,支持移动端。

云的渲染

云的类型从低到高有层云、积雨云、高层云、高积云、、卷层云、卷积云等等。

早期实现云的方式有:

Mesh-Based Clod Modeling:mesh硬做,大概模型+noise、腐蚀算法等实现,现在基本没人用了,效率低且不动态
在这里插入图片描述

Billboard Cloud:用半透明插片模拟,虽然效率高,但效果差、云的种类有限
在这里插入图片描述

Volumetric Cloud Modeling

优点:云的形状真实、可以实现大尺寸的云、支持动态天气、支持动态体积光照和阴影
缺点:算法复杂,花费巨大(但因为效果好,现在3a游戏还是会用)
思路:Weather Texture(形状分布+0-1值表示的厚度) + 平移、扰动变形
在这里插入图片描述
这里的noise扰动对其真实性非常重要,比如Perlin Noise(棉絮状噪声)和Worley Noise(细胞结构状)等

在这里插入图片描述
具体实现:先利用低频把云的规则边缘模糊化,再加上一些高频来优化细节。

Rendering Cloud by Ray Marching

那做出来云之后如何进行渲染呢?Ray Marching
在这里插入图片描述
对每个屏幕像素做射线,看能不能打到云
在没打中云时采用较大的步长(云设定在固定高度)
打中云时采用非常小的步长
在打中的每一个点计算通透度以及散射(比大气散射计算更要简化,因为云通透度很低)
效果很好,不过计算还是比较贵,但是是现代游戏引擎的标配。

;