怎样创建网站数据库,苏州网站建设开发公司,在别人网站做的友链_为何百度检测带后缀cn/index.asp,深圳网站优化推广方案主要是给自己看的#xff0c;Games104课程很全面#xff0c;王希老师学识广播#xff0c;课程组也很认真努力。但是在文字版里面有大量的示例#xff0c;比如可能会为了引入话题讲个故事等等。。。对于上课来说引人入胜#xff0c;对于要找工作的我来说#xff0c;每次看…主要是给自己看的Games104课程很全面王希老师学识广播课程组也很认真努力。但是在文字版里面有大量的示例比如可能会为了引入话题讲个故事等等。。。对于上课来说引人入胜对于要找工作的我来说每次看一遍都要看这些有点浪费时间。。。所以来一个精华版的。
Games104总结
1. 游戏引擎导论上中下
数字人虚拟人鹿鸣等等背后都是游戏引擎。好莱坞军事模拟。数字孪生也都是游戏引擎在后面。
各个游戏引擎第一个游戏引擎是卡马克的doom之后是他的quake现在的引擎包括虚幻引擎unity引擎比较厉害的Crytek引擎。 游戏大厂来说育碧的Anvil(铁砧(zhen)引擎)EA寒霜引擎RAGE 一些中间件公司physxspeedtree这种的引擎。
游戏引擎的挑战算力有限实时。 引擎同时需要一个很强大的工具链有着二次开发能力而不是一大堆代码一个SDK。
基础的构建MVVM设计模式其实是就是在说游戏到底分为几层。
ps:打开一个游戏引擎从update函数开始看主要是里面的tick函数。 系统需要有渲染系统动画系统物理系统gameplay(事件系统脚本系统)特效系统除了这些之外还有一些杂项系统比如说寻路系统最后还要有一个相机系统。
游戏引擎最核心的是构建一个工具体系。 这里面最重要的概念是反射体系。你做了一套工具后面不停地升级它的数据格式但过去与未来的数据需要兼容使得你要做1001000个工具这些工具之间还要能通信这就需要反射系统。
OnlineGaming网络 其实就是网络同步这方面的知识包括帧同步状态同步这一些。但是光服务器这一些就比较复杂及其复杂难以招架。
2引擎架构分层(上中下)
引擎架构分层(上)
工具层是首先映入我们眼帘的一层里面有各种各样的编辑器这也就是引擎最上面的那一层----工具层。
功能层渲染动画物理模拟gameplay脚本这些根游戏相关的这些功能这些基础服务叫做功能层。 功能层核心做的事情非常简单就是我们能够让这个世界可以被看见可以动的起来而且还可以玩起来。
资源层 就是把那些来自photoshop3D MAX或者maya等这些软件里面出来的模型图形几何声音视频等各种各样复杂的数据在游戏引擎中统一处理进行处理的这一层就是资源层负责加载管理这些资源。 在功能层下面就是资源层资源层可以理解成给功能层提供各种各样的弹药让他去处理去生成。
核心层 不管是在动画系统还是物理系统或者其他各种各样的系统都会频繁的调用一些底层的代码。
核心层主要任务比如说容积创建内存分配线程管理数学计算。
平台层也叫做平台无关层有的人是用pc有的人是使用mac有的人手机有的人xbox或者playstation。 无论什么样的输入设备到游戏引擎里面都要翻译成统一的语言这种平台的差异不仅仅是在对于硬件设备上的还包括软件的发布平台。
第三方插件 第三方代码在引擎里面是一个非常特别的存在它有些代码是通过SDK的形式直接集成到我们的引擎里面去就是说引擎里面编译的时候就要把第三方代码编译进去。但是还有一些第三方库它实际上是变成了一个独立的工具。它和引擎之间的数据交换只是通过文件格式进行交换。
所以说现代游戏引擎一般都是51层次
引擎架构分层(中)
回顾游戏引擎5层架构平台层、核心层、资源层、功能层和工具层。
假设小明想做一个能动起来的角色进行时间下面就是小明需要在每个阶段做的事情。
资源层: 美术朋友做的模型贴图动画叫做resource资源格式都不一样。工具做的十分复杂吐过直接在引擎中加载效率会很低所以会做一步转换把这些数据全部转换成引擎的高效数据这个转换发生后就叫做Asset(资产)。 从resource到asset有什么不同呢 一个例子一张贴图不管是jpg还是png的都有很多压缩算法。如果在GPU中以数据格式存储的话绘制效率很低。 就类似于一篇文章用word写文件会很大。保存成txt文件会超级小。所以说小明同学第一步就是要把这些数据引擎化变成资产。 同时添加上GUID也就是识别号。游戏资产身份识别号。
这个资产叫做composed asset用来描述这些资源之间到底是什么关系。 相当于一个关系脚本定义了一个资产mesh是什么纹理是什么该用什么动画当引擎读取这个composed asset的时候就知道我们要加载这些资源本身也是资产之一。
游戏引擎中现在游戏中核心的功能是数据之间的关联或者叫做reference。各个游戏资源项无数的网关联在一起里面有一个很有意思的概念叫做GUID。给资产打个唯一标识号也是全局唯一编号实际上是游戏资产的身份识别号。
Runtime asset manager 把游戏原始散乱的文件变成资产进到原始资源系统的时候需要一个实时的资源管理器。
这些资产在资产管理器中一般叫做runtime asset manage(这里的runtime指运行时或实时)。 游戏跑起来是一个一个文件掐头去尾就只是内容但是会相互指向对方。游戏引擎中有一个handle系统。handle系统简单解释就像邮箱一样可以搬来搬去我也不知道你在不在但是我始终有你的邮箱的要是我知道你是105号邮箱邮箱也知道你是105号邮箱的主人这样邮箱的主人在不在我们只需要问这个邮箱就知道了。
简单的来说游戏中最核心的是管理所有这些资产的生命周期所以资源层是游戏引擎非常核心的一个层次。
同时资源生命周期的管理需要不断地加载卸载资源这些都是handle系统和GUID系统在解决问题同时涉及到GC(垃圾回收)。 GC做不好会使得系统的效率变得及其低下。 另外一种叫做延迟加载走到哪加载到哪所有的东西都是从模糊开始慢慢清晰。
功能层: 首先是tick分为逻辑tick和渲染tick。 其实就是利用现代计算机高的计算速度在1/30秒把整个世界的逻辑和绘制全部跑了一遍这就是tick的魔力。
一般会先逻辑tick之后再渲染tick。先逻辑再渲染。 未来的引擎架构一定是一个多核架构。大家开始进行底层架构的时候推荐从多核开始去设计和思考整个底层代码。
引擎架构分层(下)part1
核心层 数学库和数据结构 为了提高运算性能很多公司都自己写数学库不止是数学库ea自己还写了一个estl标准库。。。。 同时现在有一个SIMD单指令多数据相当于一个alu把4个运算全部做掉。
同时内存管理要遵循三大底层逻辑 在游戏引擎中预先申请一块内存在核心层做内存管理的时候会用一个pool先把所有的内存资源管起来然后管各种各样的数据比如向量列表等数据资源的分配。
把数据放在一起2.访问数据的时候尽可能顺序访问3.读写的时候尽可能批量读写
引擎架构分层(下)part2
平台层 不同平台上的很多细节是不同的比如说文件路径这些。平台层的本质是在上面写核心代码/功能/逻辑可以无视平台的差别。平台层又叫做平台无关层就是把平台的差异性全部掩盖掉。 但是不同的渲染API怎么办呢有的用metal有的用vulkan有的用opengles、
所以说有一层十分重要的叫做RHI——Render Hardware Interface 重新定义了一层Graphics API将这些硬件的SDK差别封装起来。
一小段PHI的代码这些函数又被重新定义了一遍而且全部都是虚函数。 平台层超级难写最丧心病狂的是不同的CPU有的连架构都不一样。
工具层实际上就是允许别人以lever editor(地图编辑器)为中心形成的一系列编辑器比如我们很熟悉的蓝图编辑器。 游戏引擎的材质编辑器保证你在预览里看到的效果和在真实游戏里看到的效果是一模一样的。
架构中的基本原则越底层的越不要去动。写需求的时候先想好要做的这件事属于哪一层而不是着急把算法写出来。 各个层次之间的调用一般只允许上面的层次调用下面层次的功能绝对不允许下面反向调用上面一层的功能这就是分层的一个核心的体系结构。
.如何构建游戏世界(上)
面向对象的游戏世界
知道了5层工具层功能层资源层核心层平台层怎么来搭建游戏呢 放眼游戏有各种系统包括可交互动态物静态物等还有地形系统天空系统日夜变换系统。植被(属于动静结合态系统)因为固定在一个地方不能动但是又会随着风雨等做各种交互做出各种各样的变化。还有空气墙等。 把所有这些动静态物体统称为游戏对象GameObject(GO) 有了游戏对象先分析这个游戏对象的属性和行为然后抽象成类。属性就比如它的血量它的几何外形空间位置速度油量等。行为就比如它可以四处移动可以攻击可以加油。 所有在游戏世界里描述的物体可以归类为两类一类属性一类行为。 还可以使用继承多态无人机的子类侦察攻击化无人机只需要多加一个属性表示弹药量一个行为表示攻击就可以了。
组件化
面向对象的逻辑去构建世界有缺陷有些东西并没有那么清晰的父子关系。 比如水陆两栖坦克它的父亲是车辆还是船呢这是个非常经典的面向对象的问题这个问题的经典解法是组件化。我们把对象的行为拆分为无数的组件就像一辆挖土机看起来是挖土机但是把铲子换成不同组件的话可以把挖土机变成压路机推土机举重机挖路机。。。 再回到无人机组件化则可以有motor的组件表示各种运动属性飞行速度加速度等。Health组件告诉无人机到底有多少血。包括AI行为物理行为动画行为等等。
UE所有的东西都派生自一个Uobject可以管理任何对象的生命周期创建完后内存释放就是垃圾回收。 总结起来两件事 第一游戏世界里面几乎把所有的东西抽象成了游戏对象GameObject(GO)这样一个东西。 第二每个GO用各种各样多功能的组件把它组合起来所以各种组件又是游戏对象的原子。
如何让世界动起来
把游戏物体的每一个组件都tick一下只要把游戏世界里面的所有物体都tick一下这个世界就动起来了。 在游戏引擎中并不是按照每个对象进行tick的我们会把一个个系统里面的全部组件进行tick比如说我们先把弹壳系统里各个组件全部tick一遍再去天空的飞机或者拿枪的老兵。 因为这样的话会流水线化在这种的架构下处理效率是最高的。
实体-组件-系统(ESC)架构。
现代游戏引擎为了追求效率会转向每个系统或者每一种组件进行tick就是我要造个流水线进行批处理这样效率高很多。
9.如何构建游戏世界(下)
比如我跳上了坦克然后向远方的敌人开了一炮把敌人击倒了。如果每个游戏对象都是自己独立的tick我这开一炮怎么样让地方被我打伤呢这里面其实就需要游戏对象之间是相互关联的。 硬编码的情况下开炮发生会生成一个炮弹炮弹每个tick往前跑突然在一个tick的时候落地马上要爆炸所以说要检测查询周边的对象如果对象是人就降低血量如果是飞机就坠机如果是石头就飞起来。。。
早期真的是这样写的现在使用事件与事件机制。
事件与事件机制
举个例子我们不能粗暴的去敲别人家的门直接告诉别人你被我打了一下。而是应该给每个人家放个邮箱只需要给方圆20米内的所有对象写邮件告诉每个人不好意思你要扣100点血然后把这个游戏右键放在这个tick等到下一个tick的时候。小兵或者其他对象打开邮件接受事件处理自己的逻辑。
所以一个非常复杂的问题通过一个简单的时间机制变得非常清晰明确这叫解耦合。把游戏对象间的通讯统一变成了事件机制。
所以说举个例子比如unity里面可以简单的注册一个事件然后发送事件最后销毁事件如果物体核心组件收到了这个事件后有一个回调函数会激活然后作相应的处理这一切只需要一个字符串符合就可以了。 它的本质就是说让接口的消息不断扩展定义其实大家做游戏引擎核心的就是要做一个可扩展的消息系统让游戏开发者可以在我们的引擎之上不断的定制玩法和相关的各种各样的消息类型然后他们可以定制各种各样自己想要的组件去对这些消息让事件进行按照自己想要的逻辑的处理这就是现在游戏引擎的核心的一个工作。
但是游戏里那么多对象每一件事情怎么通知到每一个对象呢游戏里面有那么多的大兵、飞机、坦克、大炮。要怎么去管理呢 使用全球唯一标识符(Global Unique ID)去管理如果每个都去问一遍的话就是游戏引擎的N平方挑战。
所以说要引入场景管理
场景管理----分而治之
用一个层级结构去管理整个场景也就是空间加速算法。 思想很简单就像地图一样北京海淀区发生的事情只需要去北京海淀区里面找就可以了不必去上海找。 在实际中可以以以四叉树不断的划分如果每个地方的人数足够少了只有一个到两个了那就不用再划分了。如果人数很多就不停的划分就形成了一个树状结构。
现在比较流行的是BVH也就是比较常用的bounding box。比如视椎体裁剪等工作都是使用的BVH进行场景管理。
总结下就是在游戏中所有物体都是用组件的形式去描述用一个个组件整合出不同的行为所有的组件用tick的方法不断得无tick它的内在的逻辑去往下一个时刻前去。
时序一致性问题
在游戏中很常见的比如说物体间的绑定比如大家特别喜欢的神海里面的飙车当车是对象人是对象怎么进行联动 实际上这时候进行tick就会有一个先后关系的问题比如说我们一般会要求父节点先tick然后人被挂在车上所以后tick。 问题很多时候tick组件的时候是要分散到多CPU上去执行的很多的tick是并行执行的所以说并行执行的时序是非常重要的。
还有一个难题通讯是一个个时间传递的但是当相互发送时很容易出现逻辑混乱性。 精彩回放一般是怎么做的呢它并不是把刚才整个游戏过程全部录下来。而是记录了每一个小伙伴的输入然后把游戏重新跑了一遍。所以要求是确定的。
因此这时候我们要引入一个”邮局”关键的第三方。就能确保时序是严格一致的。
10.绘制系统真正面临的挑战是什么
挑战一复杂性问题 一幅场景里有不同的对象不用的对象需要用到不同的算法比如说水体的算法植被的算法毛发的算法皮肤的算法还有地面的算法等这些算法都完全不同。 同时还要加上大量后处理光照运算等操作。
挑战二硬件的深度适配问题 要对经典计算机架构有所了解比如南桥北桥概念显卡和CPU通信方式等。游戏绘制系统所实现的所有算法都要在现代的PC或者主机上高效运行。
挑战三性能预算问题 游戏引擎需要保证在1/30秒内计算完毕游戏画幅越来越大主流分辨率是1080P现在接近4K了即将到来的是8K时代。
挑战四时间预算分配问题
在一台电脑上跑游戏可以使用100%的显卡性能。但肯定无法让绘制系统用掉100%的CPU时间因为还有gameplay网络等模块也需要使用cpu。
真实游戏开发中有一个重要过程叫做”profileing”性能分析。现在的性能分析过程是全自动的每天都会自动运行如果某个系统运行时间超出了预算就会提醒开发者。
11.设计渲染系统为什么要特别关注显卡
渲染系统对象
游戏世界是通过顶点及其相关信息来表示的在空间中存在很多的顶点顶点连成三角形。三角形形成面面经过投影矩阵投影到屏幕上。然后通过光栅化的过程将三角形光栅化为一个个像素点。然后在每个小像素点上我们去寻找这个像素点对应的材质和纹理将这个像素点渲染成各种颜色。
绘制的最核心工作是计算。
要将绘制与GPU结合在一块考虑软硬件都要懂比如GPU有uniform缓存有tex采样核心比如下图那些模块都要知道是干什么的。
在绘制过程中着色器代码需要通过常量获取很多数值然后进行大量的加减乘除运算。 计算一个Phong模型通过常数访问变量访问加上纹理访问我们就可以得到想要的结果。 在绘制过程中纹理采样的性能消耗十分大。因为需要进行大量的采样和插值工作。 进行一次纹理采样需要采样八个像素点进行七次插值。因此纹理采样是绘制过程中的一个很重要的运算。
了解GPU
游戏绘制是实践性问题需要运行在各种性能的现代硬件上。
两个概念 SIMD单指令多数据对于一个四维向量每进行一次加法操作它的XYZW坐标会同时进行运算。所以一条指令就能够完成四个加法或者四个减法。 如果在阅读C代码的时候看到SSE扩展宏下面的代码就是在调用SIMD指令。 SIMT单指令多线程在多个线程上执行同一条指令这样便可以共用一个指令解码器加快效率。 GPU是高并行的多利用GPU高并行的优势。
图中的结构是重复的 图形处理集群中有很多流多处理器在每个流多处理器中国很多小核心。计算机术语中称为ALU(算数逻辑单元)在NVIDIA中称为CUDA,如果向流多处理器发送一条指令这些CUDA核心可以同时执行同一条指令。 有专门的硬件进行纹理采样工作比较复杂的运算比如正弦余弦指数对数等超越函数运算因为超越函数的运算速度比较慢所以显卡中有一些专门的SFU(特殊功能单元)负责处理这些运算。还有一个tensor core就是用于人工智能处理核心。
同时还有一个RTcore用来加速光线追踪BVH算法的硬件逻辑电路这就是现代GPU架构。在GPU上的运算都被分配到每个流多处理器上处理而流式多处理器中的几十个核心不仅可以进行并行处理相互之间还可以交换数据从而进行协作。因此费米架构中的流式多处理器相对于之前的架构增加了共享内存Shared Memory。如果同学们有过并行化编程的经验就会知道如果CPU之间还可以交换数据那么就可以实现一些非常酷炫的运算。以上大致说明了SIMT的概念。
尽量减少在CPU和GPU之间传送数据超级慢。 如果CPU已经准备好数据交给显卡去计算但需要等显卡计算完毕之后再将结果从显卡回读到CPUCPU再基于显卡的计算结果进行一些判断然后再告诉GPU如何进行绘制。这称之为数据的“Back Force”。这简直是灾难。。。。。。 而且绘制和逻辑通常不同步如果绘制需要等待back force则可能会导致半帧到一帧的延迟。出现逻辑和画面不同步的问题。。。 所以一个原则是尽可能将数据单向传输。
缓存对于现代计算的性能影响很大在CPU上先去cache去取数当cache miss时需要去内存中读取A的值。利用好数据的局部性。 在流水线中哪一块没有处理好是性能的瓶颈哪一块就是bounds比如ALU Bounds(数学运算速度太慢)。 Fill Rate Bounds写入缓存速度太慢。 移动端最关注的是功耗因为移动端的芯片处理能力有限数据访问对移动端来说十分昂贵因此开发出了TBR。分块渲染技术。
12.如何高效渲染小型游戏场景
可渲染物体
游戏中大部分物体GO所有的GO构成了世界。 要区分一个概念即一个逻辑上所表达的游戏对象和游戏中可以绘制的物体是不同的。因此我们在介绍组件时提到过一个组件mesh component。有的引擎会叫做skinned mesh component引擎会假设这个网格是有骨骼的可以进行变形。 我们需要在mesh component当中放一个renderable的可渲染对象。会有很多花纹很多纹理还有法线等属性这些可绘制的属性就是renderable最简单的构建块(building block)。
使用EBO使得三角形顶点存储数变少减少存储压力。但是很久之前会使用三角形带的方式进行一笔画那种的存储三角形顶点这样甚至连索引都省了而且还对缓存比较友好。随着硬件的发展已经不太使用这种方法了。
为什么每个顶点都要存一个法向量。一般来说计算出每个三角形的朝向然后使用邻近的几个三角形法向量平均就可以计算出来顶点的法向量的朝向。但如果是硬表面的折线就会出现不同表面的两个顶点的位置重合的情况。这两个顶点的法向完全不一样很次在游戏引擎绘制的时候在定义你的顶点数据时一定要为每个顶点单独定义它的法向方向。
材质系统从phong模型到基于物理的材质到次表面散射材质等变化很大。 有了这些材质模型之后接下来我们需要的是纹理在表达一种材质的时候纹理扮演了非常重要的角色。 Shader Graph。当艺术家想表达各种各样的材质时会像搭积木一样将各种元素按照自己的方法进行组合。组合完之后引擎就会生成一段Shader代码而这段Shader代码又会被编译成一个Block和网格存储在一起。各种各样的网格和Shader代码组合在一起就形成了多彩的游戏世界。因此着色器代码也是一种关键的可渲染数据。
子网格对每个游戏对向上的网格根据应用材质不同切分成很多子网格对于每个子网格分别应用各自的材质、纹理和着色器代码。一般情况下我们会将网格的顶点和三角形全部存放在一个大的缓冲区中所以对于每个子网格只需要存储偏移值Offset。换言之只需要存储索引缓冲区中的起始位置和结束位置的偏移值即可因为每个子网格只使用了大缓冲区中的一小段数据。这样就可以对每个子网格亦即缓冲区中某个起始位置到结束为止所形成的所有三角形单独应用材质、着色器和纹理进行绘制。子网格是现代游戏引擎中经常用到的一个概念如果大家打开虚幻引擎或者其他引擎都会看到类似的结构。有些引擎中可能不将其称之为子网格但基本原理是一样的。 (其实这个子网格就相当于我在Blender当中进行抠模型的时候那样)。。。。。。
为了节约空间在现代游戏引擎中通用的做法是建立一个池(pool)将所有的网格都放到一起形成一个网格池将所有纹理放到一起形成一个纹理池。当绘制一个场景时中的各种角色、以及各种小兵时我们会发现这些角色和小兵只是通过了一个引用指向了各自需要的数据比如网格材质等。
用池的概念去管理网格是最省的。材质池纹理池。
这就引入了游戏引擎架构中一个很经典的概念叫做实例化(instance)。刚刚介绍的数据都是实例定义即我们定义了一个小兵它的renderable成员应该是什么。当你进行引擎开发时一定要区分清楚哪些数据是定义哪些数据是实例。一般来说在创建了实例之后还可以再为每个实例增加一点变化。
GPU有个特点就是改变参数特别影响GPU的高速运行比如改变贴图着色器代码等。每次改变参数所有32个小核都会停下来等待参数修改完成然后再运转。
对上述过程优化对一个场景来说有很多的物体使用的都是同一个材质具有相同的参数相同的纹理。于是我们可以将整个场景的物体按照材质进行排序将具有相同材质的网格分组到一起然后设置一次材质绘制这一组拥有相同材质的子网格。直观上看计算量是一样的但是这样的运行速度确实会变快。对于底层API来说会将GPU的状态设置专门抽象成一个Render State Object。具体API不同基础逻辑相似其实就是说预先设置好显卡的状态尽量不要动然后进行一大堆运算。所以在绘制的时候可以用材质进行排序将同样的子网格归集在一起。
并且在绘制的时候依次绘制这些物体并依次设置顶点缓冲和索引缓冲也是很浪费的。如果我们使用的是现代的绘制API可以在一个DrawCall中设置一次顶点缓冲和索引缓冲以及所绘制的一堆位移数据。 即将一系列数据送入显卡后通过一次绘制调用(Drawcall)就可以让成百上千个物体全部创建出来这就是GPU Based Batch Rendering的思想。
尽可能把工作给GPU来做。
可见性裁剪
裁剪掉看不见的物体渲染这些物体会造成渲染浪费。因此可见性裁剪是游戏绘制系统的一个最基础的底层系统。
每个物体都有一个包围盒对于包围盒来说当我们给定一个四棱锥形的视椎体时我们可以通过一些简单的数学运算判断物体的包围盒是否位于视锥之中这就是可见性裁剪的基础思想。
包围盒不仅用于绘制系统还用于ai逻辑物理等模块。包围盒有很多种最简单的是球形包围盒最常用的是轴对齐包围盒(aabb)我们只需要存储两个顶点就可以将轴对齐包围盒构建出来。而且除了包围球之外轴对齐包围盒的计算效率也是最高的如果让包围盒的xyz边和物体的xyz坐标系平行这种包围盒就叫做定向包围盒(OBB)还有一种包围盒叫凸包常用于物理运算中。 有了包围盒我们就可以进行相交计算就可以进行裁剪。显然这样的效率不高因为很多无效判定。联系到BVH层次包围盒BVH是将包围盒一层一层地沿着树形结构向上合并当进行裁剪计算时可以从上到下一层层地进行计算和查询。在BVH中沿该节点一次向下迭代计算和查询直到叶节点。这时我们就可以具体得知哪些物体可见哪些不可见。
BVH使用相当广泛原因是BVH在构建树形结构的时候速度上比较有优势。但是对于小兵这种动态的来说建立完BVH后BVH中节点发生移动时需要进行重建而BVH算法的重建成本比较低所以说我们广泛使用BVH。特别是对于拥有大量动态元素的场景时。
潜在可见集是卡马克大神发明的一种算法方法很简单使用BSP树将空间划分成一个个格子每个格子之间通过一个入口Portal连接。
在建筑物中房间都是通过门和窗连接在一起的当玩家处于一个房间当中时通过每个入口所看到的其他房间是不一样的。PVS的想法十分淳朴即计算在每个房间中通过该房间的门窗所能看到的其他房间并且只渲染所能看到的房间。
PVS除了用于可见性裁剪之外还可以用于资源加载。
GPU的发展使得很多裁剪方法不再使用上述的方法完成GPU本身就可以完成这项工作比如GPU提供的遮挡查询(Occlusion Query)功能。即将很多物体的数据传入显卡在显卡会反馈回一个比特位数组每个比特位一次记录了各个物体的可见性。显卡的并行计算能力十分强大所以计算起来非常迅速。包括视椎体裁剪我们也可以直接将包围盒数据传递给显卡由显卡来完成计算。
Hi-Z就是early z即在逐个绘制像素时有的像素会被别的像素遮挡这时就不必绘制这个像素。最简单方法是先将场景绘制一遍但不对像素进行着色而只计算每个像素的深度。 现在有一种更复杂的方法比如基于层次结构Hierarchy的方法来进行深度的处理。但是整体思想还是大同小异的。方法都是利用GPU高速的并行化能力以尽可能廉价的成本形成遮挡物深度图然后将可以裁剪掉的物体尽量的裁剪掉这种做法对于复杂场景十分有用。
13.理解绘制系统记住四点就够了
纹理压缩
在游戏引擎中我们一般都会将纹理压缩存储。一般压缩和没压缩之前对比一下相差会有十倍以上。而在游戏引擎中我们没法使用一些流行的、非常优秀的算法对图片进行压缩因为这些压缩后的图片无法进行随机访问。举例来说对于Jpeg格式的文件如果给定一个UV系统无法快速从jpeg中获取到相应坐标的信息而且这个计算成本非常高。 在游戏引擎中我们一般采用基于块(block based)的压缩方法。我们将图片切成一个个小方块最经典的就是4x4的小方块然后进行压缩。
对于DXT类型的纹理在一个4×4的色块中可以找到最亮的点和最暗的点即颜色最鲜艳和颜色最暗的点然后将该方块中的其他点都视为这两个点之间的插值。因为对于很多图片来说相邻的像素之间都有一定的关联度Coherence。所以我们可以存储一个最大值和一个最小值然后为每个像素存储一个距离最大值和最小值的比例关系这样就可以近似地表达整个色块中的每个像素的颜色值。在计算机图形学领域中纹理压缩Texture Compression都是基于这个思想称为块压缩Block Comppression。在DirectX中最经典的就是DXT系列的压缩算法。块压缩系列压缩算法的最新版本已经演进到了BC7。DXT系列压缩算法的优势在于当我们生成了一个纹理后就可以在CPU上对纹理进行实时压缩。因为无论是压缩还是解压缩这一系列算法的效率都非常高。 另外一类压缩算法就是手机上使用的压缩算法使用较多的就是ASTC算法。ASTC压缩分块不再是严格的4x4了可以是任意形状而且ASTC压缩效果很好解压缩的效率也不低。然而ASTC算法压缩时的性能消耗较大因此无法在运行中进行压缩。
Houdini就是一个这样的工具使用程序化生成算法能够生成漂亮的地形网格。
渲染管线
游戏引擎的绘制系统来说并不是一个静态的系统技术一直在进步。其中一个很重要的发展方向叫做”Cluster-Based Pipeline”。这是一条新的模型表达主管线基本思想来自2015年育碧软件的刺客信条大革命 我们可以介绍一些最基础的概念。当我们面对一个非常精细的模型时我们可以将其分成一个个小的分块可以称之为Meshlet或者Cluster。而每一个Meshlet都是固定的比如32个或者64个三角形大小。因为对于现代计算机来说显卡已经能够基于数据高效地创建很多几何细节而并不需要像传统的管线那样需要预先将顶点缓冲区和索引缓冲区构建好再将网格数据传入显卡。现代显卡可以凭空计算出很多几何信息而且如果我们输入一个三角形现代显卡可以借此生成无数个三角形。因为很多计算是完全一致的所以可以很好地利用上GPU的并行性能。
在新的着色器hull shader壳着色器domain shader域着色器还有曲面细分着色器。这些shader的核心思想是我们可以使用一个算法基于数据凭空生成很多的几何细节而且可以根据距离相机的远近选择所生成的几何细节的精度。对于显卡来说最高效的数据组织方式就是大小一致的一个个小分块。
Nanite是将meshlet的思想往前深入了一步做的更加工业化成熟。 一个非常重要的趋势越来越多的绘制系统包括一些复杂的处理都已经从CPU转移到GPU以利用现代GPU的高速处理能力。这就是GPU驱动(GPU-Driven)的思想即将很多在CPU上进行的一些复杂计算全部转移到显卡。利用GPU帮助CPU分担负载。
14.所有渲染一个方程就可以表示
渲染方程L是辐射率E辐照度。 渲染方程的几个难题 第一个挑战是和光源相关的问题这其中分为两个问题第一个问题对光源的可见性问题即“Visibility to Lights”。即对于某个点我们能够看到而这个点是否能被光线照射到。更熟悉的解释就是阴影。 第二个问题是大家不太注意到的这就是光源自身的复杂性。根据光源的形状分为方向光(平行光线)聚光灯点光源。面光源是最烦的面光源意味着当光源自身发生转动的时候阴影的形状也会发生变化这就是光源自身的复杂性。 这个问题的难度在于对于入射的irradiance来说我们实际上很难得到它。 第二个挑战是在硬件上如何高效积分也就是怎么解这个方程进行着色。 没办法使用数值方法通过采样求解效率太低了。只能用于离线渲染。 第三个挑战是在光线的弹射(bounding)问题这导致所有的物体都可能成为光源。这个比较经典的案例就是康奈尔盒。 这个问题在于无限递归。比如whitted-style raytracing最后的计算量爆炸。
15.渲染中的光的数学魔法
基础光照解决方案
对于环境光来说人们使用环境贴图由六片图片拼接而成类似于将一个立方体的六个面展开之后所形成的图像。根据观察者所在的位置以及表面的法线方向进行反射找到立方体所在位置的图像。这样就可以表达光场在物体表面所形成的强烈反射效果。通过这样简单的组合我们就基本上实现了渲染方程。我们将半球形的光场分布模拟成一个均匀的环境光来表示Irradiance的低频信息再加上一个突出的主光源然后通过环境贴图来表达Irradiance中的一些高频信息这样就可以表示BRDF中的Specular项。
其实就是把灯光进行特例化后得到方程的解析解。
Blinn-Phong模型十分简单分为三项分别是环境光项(Ambient)、漫反射项(Diffuse)和高光项(Specular)。
漫反射(Diffuse)项的计算是使用入射角的余弦项乘以入射光再乘以一个反射率因为有些能量会在这个过程中被吸收。高光(Specular)项则用来模拟一些特别亮的效果看上去很像金属很像光滑表面感觉得效果。将漫反射项和高光项相加(还有Ambient项不过一般只是Diffuse项的一个百分比乘积就得到了Blinn-Phong材质模型)。
两个问题 1是能量不守恒 2是对艺术家们来说一点也不友好无法表现一些复杂材质做啥都有塑料感。
对于阴影来说阴影贴图是一切阴影算法的基础。 阴影贴图方法的思想非常简单。因为我们需要知道的是某个点到光源的可见性。首先我们从光源的视角渲染一次场景渲染过程中并不需要进行着色只需要计算出每个点的深度信息即可。这个深度就表示了每个点到光源的距离。然后从真正的相机位置渲染场景并将每个点反向投影到光源视角的场景中以得到该点在阴影贴图中的深度。将该点的深度与从阴影贴图中对应位置采样得到的深度值相比较如果该点的深度更大则说明该点位于阴影中。否则该点就不在阴影中。该方法十分简单。
阴影贴图的问题是走样()和自遮挡加一个bias。
16.上个时代的3A游戏使用的是什么光照技术
对于游戏场景来说百分之90的物体都不会动而太阳的角度也是固定的因此我们可以通过空间换时间的思路预先计算出光照的信息。
全局光照我们将其分解成直接光照和间接光照。 全局光照十分重要如果只有直接光照没有间接光照的话画面会不那么真实。
这次我们的挑战什么我们所得到的全局光照信息其实是在一个球面空间进行采样我们可以将球面像地图一样展开成一个贴图。然而这种贴图数据量十分大即使我们可以保存整个场景的光照信息当我们为材质计算光照信息的时候仍然需要进行积分计算。
使用蒙特卡洛采样后进行累加实际上是一个卷积运算计算量十分巨大。因此我们需要一种方法能够实现快速积分运算。
把全局光照信息看成是空间上的一个连续信号我们可以通过傅里叶变换将其变换成频域中的多个不同频率信号的累加。然后截取前几项完成对空间中的信号的近似表达。
傅里叶变换两个优点第一个优点是可以高效的压缩数据。第二个优点是可以降低卷积运算的复杂度。 因此我们使用球面调和函数也称球谐函数来表示全局光照的diffuse分量(低频信息)
对于场景中任意一点来说每一点都有一个探针(Probe)记录了该点的irradiance我们将每个点的irradiance信息展开然后使用球谐函数对其进行压缩。随后在程序中对irradiance进行重建如果我们想知道任意一个方向上的光强只需要一次线性的向量运算即可。 寒霜引擎的报告称使用了2阶球面调和函数的信息就已经足够了。因为使用球面调和函数是为了表达环境光。 球面调和函数方法的主要思想是在球面上的两个函数的卷积可以简化成将两个函数分别投影到球面调和函数上然后对投影得到的结果进行卷积。
光照贴图(lightmap)
我们可以通过球面谐波函数将整个场景的光照信息烘焙(light farm)到一张图上。 通过上述方式烘焙出来的贴图称为Atlas翻译成中文叫做航海图或者图集即将很多几何体平铺到一张图上。首先需要对世界的几何信息进行简化不能使用非常精细的几何体。因为过程中有一步称为参数化Paramitization会将三维空间中复杂的几何体投影到二维空间。对于简单的几何体来说比如立方体或球体参数化是比较简单的。而对于任意形状的物体来说参数化是比较复杂的因此需要对几何体进行简化并在参数空间中进行分配。这里有个细节需要注意即我们希望对于同样的面积或体积所分配的纹理的精度基本相同。
图中将每个纹素都标记成了不同的颜色如果将该图反向投影回场景每个格子的大小都比较均匀。这是光照贴图的一个很重要的要求。这个要求实现起来并不简单。DirectX在十几年前专门开发了一个API来帮助开发人员进行参数化展开当时还存在不少问题。
下面我们就可以进行光照计算了。这是通过很多台高配置的电脑所组成的集群来进行计算的也叫做“Light Farm”。美术制作完场景之后将光源等参数设置好然后就可以开始烘焙。烘培过程一般需要半个小时到一个小时左右。这项技术会让3A游戏的开发效率急剧下降后来我们自己开发引擎时由于这个原因没有应用光照贴图技术。当然这样的效果会比应用了光照贴图技术的效果差因为光照贴图烘焙出来的效果确实很棒。它可以烘焙出非常漂亮的全局光照效果再叠加上直接光照基本上可以实现光线的反弹以及相邻建筑物之间的软阴影。这时再加上主光源整个的光照环境就非常饱满。再配合上材质的效果你会发现这种感觉非常真实。
光照贴图的优点一光照贴图虽然烘焙的速度及其之慢但是运行时的效率非常高因为运行时的成本很低。 二光照贴图是离线烘焙的在将整个空间进行分解之后可以产生很多细节而微妙的效果。
光照贴图的缺点一烘焙的时间很长超级无敌长。二他只能处理处理静态物体和静态光。三光照贴图的存储和GPU占用的消耗也很大。 光照贴图本身也是一个空间换时间的策略。
光照探针
光照探针(light probe)。在光照贴图算法中需要对场景进行参数化将其”拍平”到二维空间上。我们可以在空间中撒一堆采样点叫做光照探针。每个点上的光照信息用一个探针来表示我们可以在场景中布置一堆光照探针。对于每个光照探针采样它的整个光场当物体或角色在场景中移动时就可以利用物体周围的光照探针中存储的光照信息进行插值计算出物体上的光照信息。图中的小球再移动时颜色一直在变化是因为随着小球的移动周围光照探针中存储的光照信息也在变化。这就是一个简单的空间体素化的算法。先将光照探针变成了一个个的点然后将这些点互相连接形成了很多四面体随后就可以进行插值了。 光照探针算法不是很难但是需要写一个光照探针自动化生成的算法。
在对光照探针采样时采样密度会很大。为了避免占用过大的空间我们会使用一些压缩算法来对光照信息进行压缩。因为我们只需要处理光照信息而且大部分是漫反射信息。漫反射信息非常低频我们可以使用球面调和函数对其进行压缩。 游戏场景中还有一些发光物体产生高频信息当角色走在这种环境时反射信息经常变化。因此又是制作一种专门记录反射信息的探针叫做反射探针Reflection Probe。反射探针数量不多但采样精度很高。因为反射对于高频信息非常敏感这样才能反射出周围的环境。反射探针一般会和光照探针分开采样。反射探针加上光照探针就能够实现不错的全局光照效果。
反射探针和光照探针运行效率非常高并且能够同时处理静态物体和动态物体。而且还可以在运行时对反射探针和光照探针进行更新。当场景中发生变化时包括角色位置发生变化时现代计算机上的光照探针是可以实时更新的。这里介绍一个光照探针的实现细节在实现时我们会在探针位置放置一个相机然后分别向周围的六个位置上下前后左右观察拍摄6张照片。将这6张图片拼成一个图然后使用GPU的着色器对其进行处理。一般来说在对光照探针进行更新时我们不会每帧都对其进行更新而会每隔几帧进行更新或者当场景中的位置信息发生巨大变化时才会进行更新。即使在这时引擎也不会立即更新而是会寻找一帧相对较空的时间进行更新。这也是现代引擎架构中所存在的一个理念即一些可以延迟进行的更新可以延后进行因为立即进行处理可能会导致帧率不稳定。
和光照贴图相比光照探针可以给出同样的光照感包括环境的效果。然而对于光照贴图技术所能够实现的软阴影以及物体之间的交叠感来说光照探针就无能为力了。同时对光的渗透效果(color bleeding)的实现光照探针也不擅长。归根接地是因为光照探针的采样率相比光照贴图来说少太多了。。。。
17.什么是迪士尼信条
基础光照解决方案
PBR先介绍了微表面理论。 微平面理论使用一个假设来解释光学现象即光线会在一个表面发生无数次反射。而对于一个金属表面来说看起来粗糙还是光滑实际上和该表面上法线的聚集度有关。如果表面的所有微表面的法线方向比较集中表面看起来就会闪闪发光。如果法线都朝向一个方向就会表现出类似于镜面的效果。而如果法线分散地比较开表面看上去就会比较粗糙。这符合人类观察的直觉。 所以对于PBR材质来说实际上包括两部分效果一个就是漫反射项一般用 表示。 实际上是对球面上的所有漫反射能量的积分这部分可以简单表示为 。其中c是进入物体的能量。如下图所示
D使用GGX模型。F使用施利克近似。G使用史密斯公式 PBR两种模型SG模型高光光泽度模型。没有什么参数全部属性都是用图来表示。
上图中间图片的第一张图片是漫反射纹理有三个通道分别表示RGB颜色分量。然后是Specular纹理控制Fresnel参数。大家需要注意Fresnel参数的RGB系数是不一样的。对于黄金、铜这种带颜色的金属来说它们对于不同色彩、不同角度的反应是不一样的。最后一张图是Glossiness纹理控制着粗糙度和光滑度。SG模型是一个全模型它非常完整非常经典而且符合迪斯尼信条即每个参数都在0到1之间。 我们通过纹理采样就可以得到diffuse、specular、normal和glossiness参数。然后就可以编写Shader代码。如下所示
SG模型的f0就是specular。因为specualr不好控制太灵活了所以说又出现了MR模型。
MR材质模型中有一个Lerp线性插值操作根据metallic的值来控制金属和非金属效果。 灵活性下降了但是不容易出问题。。。所以说一般都倾向于MR模型而不是SG模型。
18.经典3A游戏光照解决方法
基于图像的光照
IBL(image-based lighting)。一个点的光照来自于四面八方的我们一般说来自于一个球面。在计算机中我们可以用cube map来表达球面上的光照信息。 但是使用蒙特卡洛采样解方程实在太慢了。。
对于ibl来说分成diffuse部分和specular部分。 漫反射部分是漫反射辐照度贴图 对于漫反射项来说它本质上就是一个余弦函数在球面上的分布。对于任何一个法向的朝向点在给定一个光照时我们在计算出这个面和球面上所有点的余弦波瓣积分之后就可以提前计算出漫反射结果。这样计算出的图叫做“Diffuse Irradiance Map”。
镜面光部分是使用split sum的思想最核心的点在于对粗糙度(roughness)的处理处理预滤波环境贴图和LUT查找图。该方法利用了硬件Cubemap中的mipmap功能将粗糙度不同的结果存储在mipmap的不同层级中就像大家现在看到的这张图。
对镜面光照的lighting项进行求解先查询mipmap等级之后确定mipmap后在mipmap中进行查找预滤波环境光贴图。
BRDF项使用LUT来搞定里面的值分别是菲涅尔的比例和偏差。横坐标是视线和法线夹角的余弦值纵坐标是粗糙度。
经典阴影方法
对阴影主流的解决方法————Cascade Shadow,级联阴影映射。 其实类似于一种mipmap的思想根据距离玩家的距离生成不同精度的阴影。符合近大远小的原则。在远处人眼采样率和阴影贴图的采样率同时下降形成了完美的匹配。
挑战cascade shadow需要在不同层级之间需要进行不断的插值否则可能会出现一条很硬的边界。如果我们不做任何处理当推动相机的时候会发现有一个固定的地方在这个地方阴影会破掉。这是因为不同层级分辨率不同导致的。 Cascade shadow需要多次进行绘制和裁剪比较昂贵但是阴影绘制都是比较昂贵的。
软阴影的生成
PCF利用滤波的方法来处理阴影的软硬PCSS自适应滤波核的PCF方法VSSM使用数学方法计算出深度的均值和方差然后利用切比雪夫不等式进行估算得到阴影的软硬比例。
总结上个时代光照一般使用光照贴图和光照探针来解决。一般来说很多引擎这两个方法都会用因为这两个方法分别解决了不同的问题。而对于材质来说主要就是PBR方法。而对于背光面即环境光照的表达基本以IBL为主。PBR只需掌握两个模型一个是SG模型一个是MR模型。掌握了以上知识之后基本上就可以处理光照效果了。对于阴影则可以使用cascade shadow方法然后在使用VSSM或者PCSS给阴影加上软阴影效果。
前沿实时全局光照
屏幕空间GI基于SDF的GI基于体素的GIVXGIRSM。这些GI都在相互竞争。 Lumen是多种算法的组合是一个超级复杂的工程问题现在实时光照的革命正在进行要拥抱变化不要停滞不前。
3S的皮肤材质毛发渲染次表面散射模型等复杂材质的渲染都推到了一个非常高的维度。毛发主要是借助于Geometry shader技术的发达。
Virtual Shadow Map技术卡马克提出过一个概念叫做虚拟纹理virtual texture。将游戏环境中所有要用到的纹理全部打包到一张巨大的纹理上这个巨大的纹理就叫做虚拟纹理。需要使用的时候将纹理调出来使用不用的时候就将纹理卸载掉。 虚拟阴影贴图想法和虚拟纹理想法很相似在对一个很大的区域生成Cascade Shadow时该方法会计算哪些地方真正需要生成shadow map所生成的shadow map密度是多少然后在一块完整的虚拟的shadow map分配所需要的空间分块来生成shadow map。相机在进行渲染时每次命中一个物体就可以知道应该取哪个shadow map的值然后再去获取shadow map的值。这个方法能解决cascade shadow 在有些场合下空间利用率不高、占用空间过大的问题。
Shader 的管理Uber Shader进行shader 的组合编写修改任意出问题的起始位置编译器会自动编译出各种分支的组合。
19.如何用0和1诠释我们生活的大地
地形的几何
表达地形的方法高程图(也称为高度场)高程图是现代游戏中地形渲染的主要方法。
LOD叫做level of detail即根据物体在屏幕上占据的面积大小或距离的远近或者观察对物体的信号的敏感度设置物体的细节精度。 但是在地形上的LOD和游戏中的物体不同。对于游戏中的物体来说当其向相机远处移动时我们可以将其切换成一个更低精度的模型。但地形在空间上是连续的不会像游戏中物体那样一个个独立于其他物体所以地形的LOD需要进行特殊处理。 一个简单思想是对网格进行连续细分。 地形渲染有一个优化点是我们真正关心的内容是在FOV中的内容所以可以对FOV中的三角形进行精细的细化呈现很多细节对于FOV外和远处的地形可以稀疏地分布三角形。当fov越小时三角形可以越来越细密形成望远镜功能。 所以说在绘制地形和游戏中的物体时需要决定绘制精度时我们不仅要考虑它的远近还要考虑到它的FOV。 对网格简化有两个基础原则。第一个原则我们采取近处密一点远处稀疏一点的方式进行简化。第二个原则很麻烦叫做”Error Bound”即我们需要在数学上进行保证当对这些网格记性简化合并时因为采样点导致的地形高低差之间误差不能超过一个给定的阈值。
我们可以使用三角形剖分一开始地形是一个个方格我们在方格中间切一刀它就变成两个等腰直角三角形。如果我们觉得网格的密度不够还可以继续切下去即在等腰直角三角形的斜边中间再切一刀形成两个更小的等腰直角三角形。 可能会出现T-连接问题。
对于给定的大型地形比如128x128km大小我们对其不断进行切分很多引擎最终会将其切分到512x’512m大小称其为block。存储核心依然是四叉树它的存储方式和纹理的存储方式类似都是一个方块一个方块地进行存储而不会存储三角形。因为存储三角型纹理会浪费存储空间。
从事引擎开发的话非常推荐基于四叉树的帝乡表示方法而不要使用基于三角形的方法。因为基于三角形的方法只是一个渲染方法而基于quad的方法中暗含了资源管理的逻辑。比如虚拟纹理的方法就是基于quad的方法来实现的。
基于Quad的方法和基于三角形的方法一样也会产生T型连接主要是由于两侧的切分次数不同导致的。和基于三角形方法不同我们不需要切分次数少的一次而是采用缝合的方法。即将切分更细一侧多出来的点吸附到切分更稀疏的一侧上去。
使用高程图生成地形这样生成的地形密度非常高。我们可以使用网格简化的方法将一些不必要的顶点全部简化掉但会对一些顶点对齐到一些特征上。
十多年前网格需要程序员来进行构建然后再使用一些技巧将顶点在Shader中一个个拼接起来。现在我们可以使用GPU硬件自动完成。比如DirectX 11提供的Hull Shader、Domain Shader和Geometry Shader这些Shader就实现了细分表面的功能。比如Hull Shader的功能就是生成一个细分所使用的的面片Patch我们可以将面片理解成一小块的几何区域由若干个三角形组成同时受一些控制点控制。Hull Shader的代码有两个阶段一个阶段是“control point phase”用于告知硬件控制点数据一个阶段是“patch constant phase”用于告知细分器Tessellator一些常数信息比如细分次数。这时硬件管线中有一个固定的硬件阶段叫做细分器Tessellator会将面片细分成很多细密的三角形。下图中的中右图Tessellated Mesh就是细分后的结果。
这时的数据会由Domain Shader进行处理Domain Shader会将新插入的顶点根据采样的高度图来移动顶点的位置这样会形成地形的高低起伏感。然后就是Geometry ShaderGeometry Shader会将移动过位置的顶点的顶点数据再计算一遍比如纹理的UV坐标、以及需要传递到像素着色器的数据等等。 动态地形比如玩家围绕自己的周围有一个地方生成了一个地形变形的纹理玩家在此之上发生的所有打斗所踩的脚印都会记录在这张纹理上。然后玩家移动的时候纹理会跟随玩家移动以保证数据的一致性。这时只要整个地形都是实时由GPU细分出来的我们就可以在运行时给地形加上各种各样的变化再辅之以一定的细节比如再加上一层offset增强材质的表现添加粒子效果等就可以实现非常酷炫的地形变形效果。
20.手把手教你在游戏引擎中实现地形系统
地形的材质
有了几何表达之后就要给地形上色我们使用MR模型需要保存base color法线方向roughnessmetallic(作为alpha通道存入到base color中)还有高度图需要进行保存。
如果需要将几种材质混合在一起还需要一个solatting贴图即混合贴图这个贴图的每个通道对应了一种材质的权重。
这种混合将每个材质渲染的结果按照alpha混合方法混合在一起这种方法其实是有问题的。因为真实世界中各种材质是按照一定的逻辑混合在一起的不是简单的混合。比如石头和草。 所以我们利用之前存的高度场纹理在两种材质过渡区域中如果发现需要混合两种材质就使用物体的高度对权重进行调整。比如物体高权重就下降得慢一点。如果物体的高度较低那么权重就很快降低。这样就能够实现沙子好像入侵到石头中但又不会长在石头上的效果。 如果两种颜色的切换是01切换直接切换可能会变成高频信息产生很多抖动。我们可以为高度值添加一个偏移。在数学上当两者材质加权高度差小于0.2时我们就不使用01切换而使用各自的权重进行插值。这样在过渡区域会有一点点颜色混合。
两个概念纹理数组和3D纹理 在现代GPU数组中我们使用纹理数组来存储这些纹理将所有的颜色纹理打包成一个数组。 对于3D texture来说当我们在对其进行采样的时候在当前的mipmap精度下我们需要对上下左右前后八个点进行采样。我们可以理解成采样需要落在一个xy平面上而3D纹理还有一个Z坐标当我们对任意点进行采样时Z坐标可能不会精确地落在每一层的纹理上而是需要对Z坐标的上一层四个点进行采样然后进行双线性插值而对于下一层的四个点也得进行采样并进行双线性插值然后对这两个插值的结果再进行一次插值这样才能得到我们需要的纹理值。所以3D纹理的采样是一个三线性插值因此采样的消耗很大。
而对于纹理数组来说虽然也是很多层纹理叠在一起但是每层纹理之间是没有关系的因此在对其进行采样时第三个坐标就不是Z坐标而是索引(index).索引值只是我们对底基层纹理进行采样而不会对中间层(比如1.5层)进行采样。这样也符合我们表达地表材质的方式。
当进行材质混合时会在splatting map中存储两个值一个是用于混合的权重还有一个是所使用的材质的索引。我们使用索引来获取纹理数组中的相应材质的对应采样点信息然后乘以权重再依次获取每个材质的信息和权重讲这些颜色值混合到一起。
在绘制小石子路的凹凸感的时候会使用贴图的方式进行渲染。 当我们使用法线贴图时会形成明暗相间的凹凸感。这就是视差贴图视差贴图的方法比较简单假设地表几何体有凹凸感。当人眼看过去的时候因为空间高度不一样所以会产生视差。当人眼看过去时本该看到的是A点但因为存在高度差A点被B点所遮挡所以人眼看到的是B点。 这种偏移是使用raymarching方法实现的即通过一条光线一步步往前走直到和某处相交这时会产生更强烈的立体感。和凹凸贴图相比视差贴图立体感更强但是更昂贵。
还有一个更彻底的方法叫位移贴图它将近处的网格进行更多次细分然后根据高度图信息对地形进行真实的变形。但是对于现在的很多游戏来说大部分游戏还是各种凹凸贴图技术的结合应用。
之前说了纹理采样的性能消耗非常大采样一个最简单的2D纹理需要采样8个点进行7次插值。在实际应用中如果一个点上有4种材质那么采样次数也要相应的乘以材质数量计算量会急剧增加十分昂贵。 纹理数据储存在显存当中获取数据时需要经常来回寻址效率非常低。因此基于splatting的材质混合的性能消耗很大。
在任何一个游戏中我们在任何时刻所看到的地形只是游戏场景中的一部分因为太远的地方会被裁剪掉。即使太远处的地形仍然可以看到我们也可以使用很粗糙的几何体和纹理进行表达所以我们引入虚拟纹理。
上面的几句翻译为下面的 构建虚拟索引纹理以表示整个场景的所有混合地形材质 仅根据视图的LOD数据加载材质数据的tile块。 把混合的材质预烘焙到tiles当中并且把它们存到物理纹理中。
虚拟纹理的核心思想就是只将需要用到的数据装载在内存中而其他不用的数据仍然在硬盘上。该方法依然是分块的思想 虚拟纹理的相关介绍和实现 知乎上关于虚拟纹理的文章写的很好。
虚拟纹理的优点
第一好处是地形数据在显存中占用的空间极大地变小了。我们只需要用到的地方纹理永远是以上次的1/4精度向下递减上确界是1/3可以减少纹理的混合操作次数 对于屏幕上任意像素来说该像素所用到的所有纹理在每次渲染时都需要进行混合操作(blending)实际上这个混合操作可以在这个分块被加载时执行一次在后续渲染中只要这个分块依然位于此处没有变化我们就不需要对其进行混合操作。这个过程叫做烘焙。
现在虚拟纹理的方法已经一统天下了了因为好处实在太大了。
游戏运行时我们需要在硬盘内存和显存三处来回调度数据中间有一个内存管理算法不断发布调度指令。但是数据是通过总线来传递的当从硬盘读取数据时CPU会发出呼指令将数据从硬盘读到内存然后再从内存读取到显存。实际上显存才是数据的消费者和内存还有CPU没有关系。
第一个技术是”Direct Storage”。在现在的一些新型显卡上DS可以让数据只在内存中经过但是不进行解压缩等处理。直接到显存中解压这样传输过程中的数据都是压缩过的也就是几乎是最小的那些数据。 最高效的技术是DMA技术。在Direct Storage中数据还需要在内存中中转一次而DMA方法就是我们可以直接将数据从磁盘读取到显卡中。把CPU和内存给绕过去了。可能会改变PCIE架构。
在绘制地形的时候可能存在的问题还有浮点数精度溢出且这个问题当地形越做越大时就会遇到这个问题。 计算机中是IEEE754的float标准计算机用了32个比特位来存储浮点数的所有整数和小数信息所以当我们表达一个特别大的距离又要求距离精度精确时就会出现精度不足的问题。当浮点数越来越小或者越来越大的时候特别是越来越大的时候数字的小数部分的精度会变得很低甚至会超过1.
针对浮点数精度溢出的情况可以考虑将所有的float型都换成double型。但数据量会膨胀。有一个很简单的方式现代引擎中常用的一个方法叫做”Camera Relative Rendering”。即相机位置是相对的。渲染方法很简单将物体坐标从世界坐标系转换到相对相机的位置。 量级的数据就可以用浮点数轻松表示。 在UE5中实现了subLevel即将一个关卡切分成很多小的子关卡。在每一个子关卡中会将世界坐标系重置一次这样就可以解决浮点数的精度问题。
植被道路和贴花
渲染树来说在近处看到的树是真正的网格而到了远处会逐渐过渡并使用插片来表树木。这些插片不会让观察者感觉到任何错误而且会越来越稀疏到更远处就会变成billboard而且一次会绘制一大批的billboard。 一个中间件speedtree渲染数目很成功。
Decorator(装饰物)是另一个在环境中经常遇到的。Decorator主要是指地面上的草丛、灌木丛、小碎石等这些内容一般都会使用最简单的网格来表达。 Decorator的实现方法是插片方法。
道路系统最常见的实现方法是样条曲线即使用各种控制点对曲线进行拖拽和控制。 但是没有那么简单在设置道路时程序不仅需要将整个道路上的所有贴图全部生成还需要对高度场进行切割和腐蚀对高度场进行处理。
贴花(Decal)实际上是小贴片一开始出现在射击游戏中枪击墙壁墙壁上会有很多弹孔这些弹孔就是贴花。再加上一些法线相关效果。
无论是道路贴图还是贴花在现代游戏pipeline中都会被烘焙到虚拟纹理中。在虚拟纹理的每个分块中混合了原始的地形和材质在有道路的地方在混合上道路的纹理对于贴花的地方也把贴花混合上去。在进行渲染时可以一次性将所有这些效果全部渲染出来这也是虚拟纹理的一个优势因为虚拟纹理将所有的复杂性都放在了烘焙过程中而在运行时进行渲染时成本十分低。
21.刻画游戏氛围的”大气”该如何实现
大气散射理论
两个元素天空、云层不能混为一谈。还有一个效果是雾效我们会在后处理效果中介绍雾效因为雾效不只是天空中的大气现象的一种表现。
为什么天空是蓝色的 当太阳直射天空时大量的蓝色光会在大气场景中被散射开然后经过多次的散射进入人眼。这时候就会发生瑞利散射现象。而傍晚时的天空是红色的因为光线斜射到人眼所处的位置很多蓝色光在大气层边缘直接向大气层外进行散射因此天空中蓝色的光线越来越少。红色成分越来越高因此天空呈现出红色。
22.超真实的云朵效果是如何渲染的(稍微阅读下就可以因为大量数学公式推导大气辐射模型那些。。。)
实时大气渲染
算法Ray Marching沿着一条视线进行步进并将沿途的结果一步步积分起来。
云的渲染
一些比较重要的点柏林噪声沃利噪声。 云的渲染主要是使用了RayMarching方法。