【游戏引擎入门到实践】九.渲染地形terrain、植被、道路、贴花Decorator等

1,566 阅读32分钟

讲解如何开发游戏引擎,而不是教大家如何使用现有的引擎做出上图中的效果。

用现有引擎制作上图中的效果比较简单,但如果是开发制作这种效果的引擎,就需要知道底层的原理。

一.地形的网格,最简单且经典的方法~高程图 Heightfield

1.简介

可以称为高度场。

(1)高程图并不是什么新技术,早在几十年前的测绘行业中,人们就已经开始使用等高线 (Contour) 图来表达地形的高低变化,包括人造卫星对地球进行扫描所生成的也是高程图。

(2)经典

虽然高程图技术非常简单,也较为古老,但配合适当的材质纹理和光影之后,它的表现力十分优秀。这也是为什么直到现在,高程图还是现代游戏中地形渲染的主要方法。

2.渲染方法

(1)每隔1米或0.5米生成一个均匀的网格。

(2)再将每个顶点根据高程图进行位移,形成我们想要的地形效果

(3)然后在上面应用各种属性,比如高度、材质等等。

3.优化大地形的海量数据

背景,比如1平方公里的地形,并且每1米生成一个格子,则会生成200万个三角形,计算机还可以进行处理。但如果要表达几千平方公里到上万平方公里的地形,计算机显然无法对海量的三角形进行处理。

1.LOD “Level of Detail”多层级细节

(1)注意

地形在空间上是连续的(即不会像游戏中物体那样一个个独立于其他物体),所以地形LOD需要进行特殊处理。

(2)对网格进行连续细分(Tessellation)。

(3)基于视场FOV(Field Of View)进行优化

(4)综上,对FOV中的三角形进行精细的细化,呈现出很多细节,而对于FOV之外和远处的地形,则可以稀疏地分布三角形。

2.补充知识~fov实现望远镜效果

(1)当FOV越来越窄时,地形就会变得越来越大,三角形就会越来越细密。利用这个效果可以用来实现望远镜功能。比如FPS游戏中常用的三倍镜、八倍镜等。一般游戏中的FOV大约在70到80度左右。

(2)这放大效果实际上并不是真的,并没有实现真正的光学望远镜功能,而只是对相机的FOV参数进行了修改。这样远处的物体在屏幕上会被放大,但代价是视场会变小。

(3)原理,假设你的屏幕具有不变的像素量,那么每个三角形在小的FOV下所占用的像素量就会增加。

4.网格简化的两大原则

1.取近处密一点,远处稀疏一点

2.“Error Bound”

(1)需要在数学上保证,当对这些网格进行简化合并时,因为采样点变少而导致的地形高低之间的误差不能超过一个给定的阈值。

(2)阈值指的是视空间上的阈值,即屏幕上的误差。

比如网格的误差是0.1米,如果在距离10米的地方,我们还是能够注意到。但如果在距离100米之外的地方,我们就注意不到了,而如果在1000米之外,则根本就不会发现。

(3)一般情况下,保证屏幕上的误差不要超过一个像素,一般人是注意不到的。

5.网格简化(稀疏相见)的方法~三角剖分 (Triangle-Based Subdivision)

1.基本概念

(1)一开始的地形是一个个的方格,在方格中间切一刀,它就变成两个等腰直角三角形。如果觉得网格的密度不够,则可以继续切下去,即在等腰直角三角形的斜边中间再切一刀,形成两个更小的等腰直角三角形。如果密度还是不够,我们可以继续切下去。

(2)是一个二叉树结构,所以叫做“Binary Triangle-Based Subdivision”。

(3)下图,高亮区域就是能够观察到的区域,而黑色区域就是无法观察到的区域。

2.问题1~T型连接 (T-Junction)

(1)原因,两个相邻的三角形共享同一条边,而一个三角形的这条边没有被切分,被直接拉直,另外的三角形在这条边上进行了切分,生成了新的顶点。如果在这中间的采样点有地形的高度变化,会导致两层之间产生一个误差,我们会看到地形上有个裂缝。

(2)看到远处总有很多地方会露出白边,这是因为裂缝后面的天空被漏出来了。

(3)产生裂缝的原因,可以看这个解答

gpu渲染管道不是用精确的值,用的浮点数,误差会扩大。

computergraphics.stackexchange.com/questions/1…

2-1.解决方案

(1)如果三角形的边上,有相邻三角形对这条边的切分比自身密,就继续切分这条边。

(2)直到达到和相邻三角形一样的切分数即可。

3.问题2~不符合直觉

(1)无论是对地形数据的管理,还是中间的剖分算法,都不符合我们在制作地形时的直觉

(2)更偏向于对地形进行切块处理(方块)。所以在现代游戏中,大家用的最多的还是基于四叉树的地形表达方式,这种方法非常符合直觉。

6.主流方法~分块(block/Quad)的四叉树地形表达

1.一般引擎的方案

(1)Block或者叫Quad

对于一个给定的大型地形来说,比如128×128KM大小,可不断进行切分,很多引擎最终会将其切分到512×512m大小,我们将其称为Block。

(2)Patch

每个Block可以再切分成8×8块,即64×64m大小,可以将其称为Patch(这个命名可以自己定义)。

2.优点

(1)符合规范,方便处理

1)使用分块的方法来管理地形,包括高程图和材质贴图的内容,会更加符合其他数据的规范,也符合人类对世界认知的规范。

2)Block或Patch的存储核心仍然是四叉树,四叉树的结构非常规整,可很方便地对其进行各种处理。

3)对于512×512m的一个地形块,将其高程图贴图、植被、树木、建筑物等都打包到一起,当该地形块出现在玩家视野中时,可以一次性将所有数据加载上来。

(2)节省内存

对于一个Block来说,它在计算机中的存储方式和纹理的存储方式类似,都是一个方块一个方块地进行存储,而不会存储三角形纹理。不会因为存储三角形纹理会浪费存储空间。

(3)有利于其他优化

1)推荐实现基于四叉树的地形表示方法,而不要使用基于三角形的方法。因为基于三角形的方法只是一个渲染方法,而基于Quad的方法中暗含了资源管理的逻辑。

2)比如虚拟纹理(Virtual Texture)等优化方法,都是基于Quad的方法来实现的。

3.问题~一样也会有T型连接

原因也是,两侧的切分次数不同而造成的。

4.解决方案~ “缝合(Stitching)”

(1)和基于三角形的方法不同,不需要将切分次数较少的一侧继续切分,因为这样做会影响几何拓扑结构。

(2)取而代之的是,使用一种称为 “缝合(Stitching)” 的方法。

(3)将切分更细一侧多出来的点吸附到切分更稀疏的一侧上去。

(4)上图将多出来的点移到了上面 (红点)。这样上图中的三角形和最上面的点发生了重合,生成了一个面积为零的三角形 (退化三角形),这并不会影响到这两个网格之间的水密性 (Watertight)

(5)光栅化时处理退化三角形的方法,不进行绘制,因为这个三角形的面积为零。

7.地形简化~使用不规则的三角形来表达地形

这种方法用的不是特别多,但现代的一些前沿游戏中还是有人使用。

1.背景和原理

(1)使用高程图生成地形,这样生成的地形密度非常高。

(2)如果所生成的地形中有很多区域是平坦的(比如沙漠),无论使用上述的哪种方法,都会浪费很多三角形。

(3)于是可使用网格简化 (Mesh Simplification) 的方法,将一些不必要的顶点全部简化掉,但会将一些顶点对齐到一些特征上 (特征指的是能够标识地形中特殊区域的地方)

(4)简化部分的三角形是细长的,也就是会有不规则的三角形出现;

2.优点

(1)所绘制的三角形数量比前两种方法要少。甚至可以少一个数量级。

3.缺点

(1)预处理

这个方法需要进行预处理,在运行时是很难对网格进行调整的。除了几款特殊游戏之外,一般游戏都没有使用这种技术。

(2)定制化,这种方法生成的数据的重用性也比较差。

8.基于硬件GPU的运行时细分 (GPU-Driven Tessellation)

1.背景

十几年前,类似于上面的网格需要程序员来进行构建,再使用一些技巧将顶点在Shader中一个个拼接起来。

现代显卡提供了强大的运行时细分网格的功能。

2.DirectX 11提供的shader

提供了Hull Shader、Domain Shader和Geometry Shader ,这三个Shader实现了细分表面的功能

2.1 Hull Shader

(1)功能:生成一个细分所使用的的面片(Patch)。

面片理解成一小块的几何区域,由若干个三角形组成,同时受一些控制点控制。

(2)步骤

1)一个阶段是“control point phase”,用于告知硬件控制点数据;

2)一个阶段是“patch constant phase”,告知细分器(Tessellator)一些常数信息,比如细分次数。

3)最后硬件管线中有一个固定的硬件阶段叫做细分器(Tessellator),将面片细分成很多细密的三角形

2.2 Domain Shader

会将新插入的顶点,根据采样的高度图来移动位置,这样会形成地形的高低起伏感。

2.3 Geometry Shader

(1)会将移动过位置的顶点数据再计算一遍,比如纹理的UV坐标、以及需要传递到像素着色器的数据等等。

(2)可迅速生成无数的细节。

3.“Mesh Shader”~新一代渲染管线引入

(1)将上面三个Shader的功能在一个Shader中完成。

(2)输入我们想要的形状,Mesh Shader会自动生成一个个Meshlet,并进行插值和凸包运算。

即自动完成了Hull Shader、Domain Shader、Geometry Shader的工作,以及Vertex Shader和Tessellation的工作。

(3)注意,Mesh Shader需要DirectX 12以后版本的支持。

4.动态地形(地形变形效果

(1)概念

利用GPU驱动的实时细分生成地形的功能,可以实现实时的地形变形功能。

比如拖拉机在在泥地里压出很多轮印;雪地地面变形效果。

(2)拖拉机压泥地

大概思路,假设地面以下有很多小弹簧,一旦受到压力,弹簧就会被压下去,同时会将物质一点点往上挤。

(3)雪地变形

黑神话悟空的雪地打斗场景,具体实现会有很多不同,介绍一个思路。

(1)玩家围绕自己的周围,有一个地方生成了一个地形变形的纹理,

(2)玩家在此之上发生的所有打斗,所踩的脚印都会记录在这张纹理上。

(3)然后玩家移动的时候,纹理会跟随玩家移动,以保证数据的一致性。

(4)这时只要整个地形都是实时由GPU细分出来的,就可以在运行时给地形加上各种各样的变化,再辅之以一定的细节,比如再加上一层offset,增强材质的表现,添加粒子效果等,就可实现非常酷炫的地形变形效果。

(4)更复杂的弹坑效果

比如雪地效果只是视觉上的,它并不会改变人的高度。

而弹坑会让人陷入进去,因此还需要更新和处理物理碰撞问题,以及其他的一些细节问题。

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

地面上会有很多洞,包括悬崖上也会有很多反向的峭壁等等。

1.简单实现~额外添加物体

比如悬崖,在生成了山之后,在以前的游戏引擎中,在山里面插入了一个物体。这样就会有悬崖的感觉。

2.优化实现~无效顶点

比如想在山上挖出一个隧道

(1)高度场生成的地形是水密的,无法在地形上挖洞。

(2)可以在要挖隧道的地方做上标记,在顶点着色器中,输出顶点位置时,将相应的标记位置输出“NaN”(即无效数字)

(3)现代GPU会识别出这类无效顶点,而不渲染这些顶点组成的三角形。这就是我们前面提到的退化三角形,这样我们就可以在地形上实现挖洞操作了。

(4)这样挖洞会存在很多锯齿(Zigzag),并不美观。可以让美术制作一个隧道模型,将隧道模型插入这个位置,这样我们就实现了隧道效果。

三.地形中很少使用的~ 体素化 (Volumetric)方法

1.存储权重

在三维空间中的每个点处存储一个权重值,比如0到16之间的一个数值,这个值表示这个空间有没有物质以及物质的密度。

2.Marching Cube~MC算法(移动立方体算法)

细节见这篇:blog.csdn.net/AndrewFan/a…

(0)30-40年前提出来。对于空间中的Voxel的采样,找到一个等值面,简单理解为模型的外表面。

(1)体元是立方体,体素是8个顶点。

(2)包含体数据内容的体素点称为实点,而其外的背景体素点都称作虚点。这样一个三维图像就是由各种实点和虚点组成的点阵。

(3)体元的8个体素点每个都可能是实点或虚点,那么一个体元一共有2的8次方即256种可能的情况。

(4)例子,一个字节中的8个位来分别对应一个体元中八个体素的分布情况。

1)如上图实点是1、2、3、6,用字节二进制表示为~01001110,转为十进制为78。

2)其等值面由4个三角形T1、T2、T3、T4组成这四个三角形的顶点分别所在边如下表所示:

三角形顶点所在边
T1e3,e11,e6
T2e0,e3,e6
T3e0,e6,e5
T4e0,e5,e9

3)那么对于78这个体元配置,我们在表中就可有如下的记录:

体元配置二进制形式实点三角形集合
..............
7810011101,2,3,6(3,11,6),(0,3,6),(0,6,5),(0,5,9)
..............

4)代码中表格的表示为

{3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}

5)完整的表TriTable,见连接的文章。

TriTable是一个256*16的二维数组,每行对应一个体元配置,

行内有16个数,每三个数为一组,代表一个三角形,-1代表无效值。

之所以设16个位置是因为部分配置可能最多包含5个三角形片,那么就会占到5*3=15个空位

最后一个填-1代表结束的标记。

(5)MC算法的核心思想就是利用这256种可以枚举的情况,来抽取出体元内的等值三角面片。

(6)把这些面片组合起来,就是物体的三角网格mesh了。

(6)注意,考虑旋转对称性,去重后,只有15种基本模式,来切分这个立方体,并形成一个水密的三角面片集,将形状表达出来。

3.Marching Cube算法的应用

(1)CT扫描

因为CT扫描人体所得到的数据其实是一个个的点,最后我们所看到的人体形状就是用这种方法生成的。

(2)模拟空气动力学中螺旋桨搅动时形成的压强场,或者速度场,也可以使用这种体素化的表示和Marching Cube的方法来将其表达出来。

(3)数字人

1)最早数字人出现在大约20年前,美国一个研究机构将一个死刑犯的遗体(经过他本人的同意)迅速冷冻起来

2)然后使用机器每隔1毫米对人体进行切片并拍照,这样可得到真实人体的体素化表达。

3)这时我们可以使用Marching Cube方法将人体从表皮到内脏的细节全部表达出来。

4.Marching Cube做地形渲染的缺点

(1)使用Marching Cube方法来做地形会比较复杂,现在相关的研究工作也很少。

(2)因为不可能对所有的地形都使用同一个精度,也需要进行近密远疏的处理。

(3)在这种疏密相间的情况下,如何保证地形的水密性?

(4)有人对这个问题进行了研究,列举了100多种的情况,虽然看起来很复杂,但对于显卡来说很简单,因为显卡可以通过查表来处理这些情况。

(5)这个算法用在地形上,还很不成熟,需要关注下一代硬件的发展,包括现有的一些前沿算法。

5.地形的一些前沿技术

有些技术可以实现“全动态地形”,玩家可以在地上挖洞,挖到敌人的背后偷袭别人,这种技术真的很酷。

四.地形的材质

背景:

前面的方法得到了地形网格mesh。现在需要上色,也就是材质处理,使用前面讲的主流 的MR材质。

一.混合贴图Splatting

1.Alpha混合

(1)概念

贴图的每个通道对应了一种材质的权重,按照Alpha混合方法混合在一起的。

(2)alpha混合的问题

比如下图中从沙子逐渐向石头过渡时,如果使用阿尔法混合,就可以看到一个很柔和的区域,但这种效果并不真实,因为看起来就像在石头上蒙了一层沙子,而沙子浮在了石头缝上。

右图是真实渲染效果,草会先从石头缝中间蔓延过去,而不会直接长在石头上面。

2.混合贴图的优化

2-1.高度场纹理
1.做法

(1)利用之前存储的高度场纹理,在两种材质过渡区域中,如果发现需要混合两种材质,就使用物体的高度对权重进行调整。

(2)物体高度高一些,权重就下降得慢一点,高度较低,权重就很快降低。这样就能实现沙子好像入侵到石头中,又不会长在石头上的效果。

2.问题

在两种颜色的切换 (也就是两种材质间的切换) 是一个01切换,在近处看问题不大。

但当相机发生移动时,这种切换可能会变成高频信息,会产生很多抖动。

特别是位于远处以很低的视角观察这个地形时,切换位置的分界线处会显得比较突出。

2-2.优化高频抖动问题,添加偏移 (Bias)

(1)加上0.2的扰动,当两种材质的加权高度差小于0.2时,我们就不使用01切换,而使用各自的权重进行插值。

(2)这样在过渡区域还是会有一点点颜色的混合,看起来也比较自然,而且混合效果看上去也会更稳定。

二.纹理数组 (Texture Array)

1.背景

(1)实际项目中可不止三四种混合,大项目可能几十上百种材质需要混合。

(3)在现代GPU中,使用纹理数组 (Texture Array) 来存储这些纹理,即将所有的颜色纹理打包成一个数组。

2.纹理数组和3D纹理的区别

2-1.3D Texture ~ 3D纹理

(1)在进行采样时,在当前的mipmap精度下,需要对其上下左右前后八个点进行采样。

(2)可理解成采样需要落在一个XY平面上,而3D纹理还有一个Z坐标,当对任意点进行采样时,Z坐标可能不会精确地落在每一层的纹理上。

(3)而是需要对Z坐标的上一层的四个点进行采样,然后进行双线性插值,而对于下一层的四个点,也得进行采样,并进行双线性插值

(4)然后对这两个插值的结果再进行一次插值,这样才能得到我们需要的纹理值。

(5)所以3D纹理的采样是一个三次线性插值,因此采样的消耗很大。

2-2.Texture Array纹理数组

(1)虽然也是很多层纹理叠在一起,但每层纹理之间是没有关系的。

(2)因此在对其进行采样时,第三个坐标就不是Z坐标了,而是索引 (Index)

(3)索引值指示我们对第几层纹理进行采样,而不会对中间层 (比如1.5层等) 进行采样。

(4)这样也符合我们表达地表材质的方式,地表材质数量再多,它们之间也没有关系。

3.纹理数组在地表材质渲染中的使用

(1)进行材质混合时,在Splatting Map中存两个数据,一个是 混合的权重,另一个是所使用的材质的 索引

(2)使用索引来获取纹理数组中的相应材质的对应采样点信息,再乘以权重

(3)再依次获取每个材质的信息和权重,将这些颜色值混合到一起。

三.立体凹凸效果的前沿技术

0.背景

(1)目前(2023年初)大部分游戏还是各种凹凸贴图技术的结合应用,比如Bump mapping、法线等技术,只是在光照计算时用到了凹凸信息,但并没有实际改变几何体的凹凸信息。

(2)越来越多的产品尝试引入下面的视差贴图和位移贴图。是未来的一个方向。

1.视差贴图~Parallax Mapping

1.概念

假设地表的几何体是有凹凸感的。当人眼看过去的时候,因为它的空间高度不一样,所以会产生视差。

如图,当人眼看出去时本该看到的是A点,但因为存在高度差,A点被B点所遮挡,所以人眼看到的是B点。

2.原理

使用Ray Marching方法实现,即通过一条光线,一步步地向前走,直到和某处相交,会产生更强烈的立体感。

3.缺点

(1)每个像素的运算会稍贵一点,因为光线需要往前走几步进行测试。

(2)视差贴图只能够产生视觉上的凹凸感,仔细观察几何体的边界,会发现,它还是像那个刀切过去一样光滑。

2.位移贴图~Displacement Mapping

(1)依赖硬件的细分功能

(2)在运行时,对近处的网格进行更多次细分,然后根据高度图信息对地形进行 真实的变形

四.虚拟纹理(Virtual Texture

1.背景~基于Splatting的材质混合的性能消耗很大

1.纹理贴图的混合是非常昂贵的expensive

(1)最简单的2D纹理,需要采样2层x4个点 一共八个像素点的数据,并且进行七次插值运算。

(2)在实际应用中,如果有4到5种材质混合在一起 (一般是4种),即一个点上有4种材质,那么采样次数也要相应地乘以材质数量,计算量会急剧增加,同时还有其他的各种运算。

2.纹理数组方案,性能消耗也很非常大

(1)无论是现代的GPU还是CPU,都脱始于图灵机架构。这些纹理数据存储在显存中,可认为它是一个二维结构,获取数据时需要经常来回寻址。

(2)磁盘的读写头需要不停地来回移动。

(3)对于内存来说,内存是随机寻址的,不存在读写头,但也要在不同的地址之间来回跳转,所以效率也非常低。

3.类似LOD的思想进行优化

任何时刻所看到的地形只是游戏场景中全部地形的一小部分,因为太远的地方会被裁剪掉。即使太远处的地形仍然可以看到,也可以使用很粗糙的几何体和纹理进行表达。

2.概念

1.核心思想

只将需要用到的数据装载在内存中,而其他不用的数据仍然在硬盘上。

2.分块思想

(1)可将地形分成512×512的小块。假设最密集的分块是4米×4米,为其配置了一个1024×1024大小的纹理。

(2)当我们在远处观察时,这个地块也会变得稀疏,假设我们可以用8米×8米来表达这个地块,则相应的纹理精度也不需要那么高。

(3)类似于gis的层级,每缩小一级,少到上一层的1/4。

(4)利用mipmap的思想,当需要看到某个分块时,可临时将该分块用到的材质、几何、网格等烘焙出来,存到一个缓存空间中。这就是真正存到显存中的纹理,称之为 物理纹理(Physics Texture)。当绘制这些分块时,会通过一个索引反向指向这些纹理。

(5)原理类似于操作系统内存管理中的页面调度策略。

3.优点

1.地形数据在显存中占用的空间极大地变小了。

在拉远镜头时,我们都可以比上次看得更远,但纹理永远是以上次1/4的精度向下递减。所以这就形成了一个级数如下,而数学上可以证明这个级数是有上确界(阈值)的。

2.减少纹理的混合操作的次数。

(1)混合操作可以在这个分块被加载时,执行一次。

(2)在后续渲染中,只要这个分块仍然位于该处,没有发生变化,就不需要再次对其进行混合操作。

(3)进行屏幕上的绘制时,可以直接取出该分块中的数据,这个过程叫做烘培(Bake)

3.虚拟纹理的方法基本上已经一统天下,因为它的好处实在太大。

五.补充~硬件优化数据调度的前沿技术

0.背景

(1)在游戏运行时,基于现在计算机的PCIE架构,需在硬盘、内存和显存三处来回调度数据,中间有一个你可以叫做内存管理的一个算法,它会不断发布调度指令,让资源在这三处硬件中流动。

(2)这个方法会出现一个问题,现在的计算机架构实际上是通过总线来传递数据的,当从硬盘读取数据时,CPU会发出指令将数据从硬盘读到内存,然后再从内存读取到显存。

(3)实际上,显存才是这些数据的消费者,跟内存没有太大关系,和CPU更没有关系。

1.Direct Storage,显卡解压

(1)游戏中用到的大量数据都是压缩过的,传统管线是CPU解压,再传给GPU。

(2)在现在一些新型显卡上,Direct Storage可以让数据只在内存中经过,不用cpu解压,直接在显卡中对数据进行解压。

(3)数据从硬盘到内存、从内存到显卡的传输传递的都是压缩后的数据,传输效率会很高。

(4)而现代显卡的算力至少是现代CPU的10倍,所以利用显卡的算力进行解压完全没有问题。

2.DMA技术,直接不经过cpu

(1)直接将数据 从磁盘读取到显卡中

(2)如果DMA技术未来随着显卡能够成为现代计算机的一个基本配置的话,古老的PCIE架构可能会在未来几年内被逐步淘汰掉。大家可以直接往显存中写入数据。因为很多工作根本不需要CPU牵涉进来。

(3)比如开发PS5上的游戏,我们就可以直接利用硬件的DMA功能。

3.英伟达新显卡的NVLink功能

(1)将两个显卡直接连到一起,互相直接交换数据。

(2)显卡之间的数据交换以前都需要先回到内存,从内存中转到其他显卡上,而现在可以直接进行数据交换。

六.地形绘制的浮点数精度溢出问题

1.背景

(1)计算机中浮点数是按照一定的格式标准存放的(参考IEEE 754)

(2)计算机能够存储的数字信息量有限,对于一个浮点数来说,计算机只使用了32个比特位来存储浮点数的所有整数和小数信息。

(3)所以当表达一个特别大的距离 (比如从地球到月球的距离),而又要求距离的精度足够精确时,就会出现精度不足的问题。

(4)当浮点数越来越小或者越来越大,特别是越来越大时,数字的小数部分的精度会变得很低,甚至会超过1。

(5)开发大型开放世界游戏时,可能会涉及到一些比较复杂的物理模拟运算。这时浮点数精度将会是一个非常严重的问题。由于模拟的数值表达不准确,最后的物理模拟效果会直接出错。

(6)比如,星际旅行题材的游戏也会遇到。

2.问题的例子

1.第一个例子,0-6万公里的距离变化

上图中展示的就是使用浮点数和双精度浮点数的精度曲线。

而右图中的视频则展示了在距离很大的情况下,由于浮点数错误而导致的失真

当进行图形运算时,如果相机离物体越来越远,图中的球体就会随着相机往远处走。

(1)当相机和球体越走越远时,就会发生抖动。

这并不是球体真的发生了移动,而是因为浮点数的精度已经不够,所以球体到相机的距离在计算机内表示的数值会发生变化,从而使球体产生了抖动。

2.第二个例子,窗台海报抖动

以现在的浮点数精度,如果我们以米为单位,当我表示2-3公里以上的距离时,1米内的距离就会变成万分位的数字。比如我这边放一个墙,墙面上有一个很小的窗台,窗台嵌进墙壁大约0.05米,然后在窗台表面再贴一层画报,这个画报距离墙的距离是1毫米。

这时,当墙壁位于两公里之外时,画报就会在墙壁前后来回抖动。

3.解决办法

1.全部换成双精度

全部换成双精度型数据当然可以解决这个问题,但数据量会膨胀,这对于游戏引擎是一个很大的压力

2.Camera Relative Rendering 相机相对渲染

(1)将物体坐标从世界坐标系转换到相对于相机的位置。

(2)比如举例中的墙壁,它世界坐标系的位置是2050.1米,而画报位置是2050米,两者之间的距离只有0.1米。

(3)相机位于2040米处。我们可以将相机的位置强行设置为0,这时墙壁的位置就变成了10.1米,画报的位置则变成了10米,这个量级的数据就可以用浮点数轻松表示。

(4)也是虚幻引擎和Unity中的标准方法。

3.虚幻5的新方法“Sub Level”

(1)将一个关卡切成很多小的子关卡。

(2)在每一个子关卡中,会将世界坐标系重置一次,这样也可以解决浮点数精度的问题。

五.渲染植被、道路、贴花

一.树木的渲染Foliage

1.一套很专门的技术来生成和表达游戏中的树木,不详细介绍了,大概思路如下。

(1)在近处,我们看到的树是真正的网格。

(2)而到了远处,会逐渐过渡并使用插片来表示树木,插片不会让观察者感觉到任何错误,而且会越来越稀疏。

(3)到更远处就会变成Billboard公告板, 而且一次会绘制一大批Billboard。

2.成熟插件

树木的渲染是一个非常有挑战性的问题,一个树木渲染的插件就可以卖到几十万的价格。

介绍一个软件——SpeedTree,可能是目前最成熟的一个植被渲染 (Foliage) 中间件。

二.Decorator装饰物

也不深入介绍。

(1)Decorator主要指地面上的草丛、灌木丛、小碎石等,这些内容一般都会使用最简单的网格来表达。

(2)初级的实现方法,前面提到的插片+billboard方法,走进发现草会随着观察者的移动而转动。

(3)有时使用的是View-Dependent的方法。

(3)3A游戏中高级的方法,额外再去了解。

三.道路系统

0.背景

路系统本身就十分复杂。可以看 UE 5发布的“City Sample”, 使用程序生成了曼哈顿的道路。

(1)道路系统比较麻烦,因为道路之间会出现穿插。

(2)不能将两个贴图直接混合在一起,因为需要生成很多斑马线、行人和马路。

在具体算法实现时,有很多情况需要处理。

1.实现方法~样条曲线 (Spline)

(1)使用各种控制点,对曲线进行拖拽和控制。

(2)对艺术家很友好,布置几个控制点,程序就可以自动生成连接这几个控制点的光滑曲线。

2.难点

对于程序员来说,所需要考虑的情况并非如此简单。

(1)在设置道路时,程序不仅需要将整个道路上的所有贴图全部生成

(2)还需要对高度场进行 切割和腐蚀(Erosion)

1)山体是有坡度的,道路不能凭空出现在空中,也不能直接穿过山体,而需要进行处理,以使道路沿着山体蜿蜒,或者通过隧道穿过山体。

2)类似于我们在实际环境中修路,有时需要将山体挖掉一部分,有时候需要将地形填上一部分。因此当道路通过样条曲线拉出来之后,就需要对高度场进行处理。

四.Decal贴花系统

1.场景

射击游戏墙壁上会出现很多弹孔,这些弹孔就是贴花。

2.优化

(1)加上一些和法线相关的效果,比如前面提到的Parallax Mapping视差贴图效果,使弹孔看起来好像真的凹下去一样,以增加真实感。

五.小节

1.虚拟纹理

(1)无论是道路贴图还是贴花,在现代游戏的pipeline中,都会被烘焙到虚拟纹理中。

(2)在虚拟纹理的每个分块中,混合了原始的地形和材质,在有道路的地方,我们会再混合上道路的纹理,

(3)而对于出现贴花的地方,我们会将贴花也混合上去。

(4)在进行渲染时,可以一次性将所有这些效果全部渲染出来。

这也是虚拟纹理的一个巨大的优势,因为虚拟纹理将所有的复杂性都放在了烘焙过程中,而在运行时进行渲染时,它的成本非常低。而且烘焙完一次之后,只要这个分块一直存在,也不需要进行更新,这也是一个优势。

2.地形编辑器

(1)首先生成了一个高度场。

(2)然后艺术家们开始手动为高度场贴上各种纹理。

(3)这些效果都可以手动进行调整,以满足艺术家们的设计需求。

(4)demo演示

使用程序化的方法生成流水对山体的腐蚀的过程。

可以生成道路,并且可以根据山体的坡度和表面的材质 (比如表面是石头还是土地),来生成植被。

包括利用山体的海拔高度,我们可以选择让植被从阔叶林到针叶林进行过渡。

(5)游戏引擎开发者来说,需要理解和学习很多自然界中的各种规律和现象,以及其背后的原理,因此游戏引擎的开发者一般都比较博学。