GAMES104 笔记 -引擎架构分层和整体pipeline
Posted 奇迹小缘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GAMES104 笔记 -引擎架构分层和整体pipeline相关的知识,希望对你有一定的参考价值。
目录
现代游戏引擎
目前市面上的游戏引擎可分为三种:
- 商业引擎(虚幻,unity,Cry)
- 自研引擎
- 开源引擎
什么是游戏引擎
- 构建虚拟世界的基础技术
- 人类想象力或创意的生产力工具
- 复杂性的艺术
- rendering,我们需要先把角色和场景给渲染出来
- animation,我们需要赋予动画给角色执行不同的操作
- physic,我们需要判断其是否发生物理碰撞等物理信息
- control,需要通过设备输入来执行角色的操作
- Internet,需要进行网络同步
引擎开发的难点
- 游戏世界的细节和画面质量受到硬件的影响,比如算力,带宽的延迟,存储空间等硬件的限制。
- 网络延迟与同步
- Real-time:实时,任何算法若无法在1/30s内完成计算,则视为无效。
- 游戏引擎不仅需要面对程序员,还需要面向游戏内容的制作者,比如提供良好的可视化工具链给artist和designer使用进行二次开发。
- 版本迭代兼容性,就像一架飞机在飞行,我们需要在其飞行期间更换零件并且不妨碍飞机的正常运作。
游戏引擎分层
- 工具层(TOOL LAYER)-UE/UNITY
- 功能层(Function Layer)
工具层只能实现一些资源的编辑,场景渲染,物体是可移动的,绑定animation,NPC互动,物理方面
- 资源层(Resource Layer)
而游戏不仅仅需要代码,我们需要大量的数据和文件,主要是美术,音频和动画。因此游戏引擎中有一层用来加载和管理这些数据和文件,使得工作流与游戏高效运行
- 核心层(Core Layer)
容器的创建,内存分配,或者是多线程的管理等
核心层像是一个工具箱,遇到什么问题就给一套对应的工具让你去处理,需要处理内存管理时核心层给你提供内存管理的工具,需要处理容器分配时核心层给你提供容器分配的工具,需要进行数学运算时核心层提供数学模块的工具。
游戏相关的逻辑管理是建立在核心层的基础上的,因此coer layer是最核心的一层。
- 平台层(Platform Layer)
不同硬件对应的平台也不同(PS\\PC\\Switch\\ios\\android\\VR……),不同硬件的输入方式、渲染接口、功能特性各不相同,引擎有一层需要对不同平台进行处理。
- 至此我们有了平台层,解决了不同平台特性的问题
- 然后我们建立所有功能处理的内核,核心层
- 再建立资源层对资源进行加载和管理
- 接着我们建立功能层来进行与游戏相关的操作
- 最后我们建立工具层将功能层进行的与游戏相关的操作可视化为编辑器出来供使用者们使用
为什么要进行分层
- 通过封装分层让每一层只关注自己的事情来减低复杂度,也就是解耦合;
- 底层提供基础服务;
- 顶层不需要知道底层的具体实现;
- 顶层部分迭代频繁,底层相对稳定;
- 一般只允许顶层调用底层;
资源层
Importing
Resource的文件格式是不同的,以类似maya,max这些格式的文件来说,它们是适合在自己的软件Maya,3DMax打开,如果用引擎来加载这些数据的话是很低效的。贴图可以是 png,或者 jpg 等,他们有自己的压缩算法,如果直接在 GPU 中使用会很费性能,可以将其统一转成 dds 格式进行使用。
因此我们需要将这些resource转换成引擎的高效数据,这一步叫做importing,而转换后的高效数据我们称其为asset。
关联资产Composite asset
现代引擎最核心的观念是数据之间的关联(reference)。
现代游戏引擎中我们希望对每一个资产都设置GUID(唯一识别号),从而避免资产位置改变而导致路径找不到的问题(路径无关性)。
Runtime Asset Manager
现在我们已经加载了角色的resource并import成了引擎的asset,接下来我们需要对这些asset进行实时管理,不能让他一直加载到内存里呀,因此资源层还需要处理运行时的资源加载与卸载。
在Runtime Manager中这些asset各自拥有各自的指向关联在一起。
对于资源的管理需要对以下几个方面进行平衡
- 硬件性能:可以申请多少内存
- 资源回收:回收资源时的耗时
- 资源加载:加载耗时 - 同步异步处理
功能层
Tick
执行一次tick,就是将游戏里的所有模块逐步执行一遍。在现代计算机强大的计算能力基础之上,每隔1/30秒执行一次tick,将游戏的逻辑和绘制跑一遍。
通常Tick分为两个功能:逻辑Tick与渲染Tick。
在游戏中,我们先将游戏场景模拟出来(ticklogic),再进行渲染(tickrender)。
TickLogic一般做的是:
- 读取输入输出
- 计算世界物理信息
- 移动camera,角色和物体
- 碰撞检测等
- 以及各种游戏玩法的事都属于逻辑,比如角色A打到了角色B,因为不管看没看见,A都打到了B并造成了伤害
多核
现代计算机CPU为多核处理,因此引擎根据这一特性进行多线程计算。比如Logic和Render我们分配到不同的线程中执行,甚至再多开一个线程来执行加载。
核心层
高性能计算
在普通的ticklogic和tickrendering里只需要简单的线性代数计算,但是我们仍需要重新写数学库,这是因为游戏引擎是一个实时的东西,因此我们需要高效的计算。
- SIMD:并行化计算,一个ALU处理多个运算。
数据结构与容器
原生语言通常会有自带的数据结构,但其中的实现通常不够高效,并且难以拓展。因此游戏引擎经常会自己实现一些底层数据结构,使得几乎没有内存碎片,而且访问效率高。
内存管理
游戏引擎的内存管理十分接近操作系统。游戏引擎的内存性能瓶颈:
- 内存池(分配器)
- Cache Miss
- 内存对齐
内存管理的目的是为了让计算机更快速的处理:
- 把数据放在一起
- 尽可能按顺序访问数据
- 尽量一起读写数据
平台层
平台层存在的意义是为了让你可以无视平台的区别,把平台的差异度掩盖掉。
文件路径
不同平台的文件路径格式不一样。
硬件渲染接口
对于图形API来说,有OpenGL,DirectX,Vulkan等,需要一个RHI(Render Hardware Interface)封装各个平台的SDK的区别
硬件CPU架构
工具层
工具层的意义是为了让别人使用以地图编辑器为主从而形成的一系列编辑器来帮游戏添加或修改内容。
编辑器能及时的实时在我们的游戏引擎中调整效果,保证我们在引擎编辑中看到的效果和实际游戏运行起来的效果一致,提升开发效率;
工具层的代码量占的比例很多。
Games101笔记 P11~?
贝塞尔曲线(Bezier Curve-General Algebraic Formula)
三个点的贝塞尔曲线迭代公式:
展开得到
n个控制点的贝塞尔曲线,每一个时刻曲线上的点的坐标,可以通过二项式展开得到。
其中\\(B^n_j(t)\\)就是伯恩斯坦多项式,也就是二项式展开的系数,写作:
用语言表述就是,用伯恩斯坦系数给各个控制点加权得到贝塞尔曲线的解。
优美的性质->1.线性不变:贝塞尔曲线只需要记录控制点的位置,在线性变换下,对控制点进行变换之后重新生成的曲线与直接对曲线进行变换得到的曲线相同。
2.凸包性:贝塞尔曲线一定在控制点形成的凸包内。
逐段的贝塞尔曲线
因为在控制点很多的时候,每一个控制点对曲线的影响很小了,人们更趋向于使用较少的控制点和较多的贝塞尔曲线,来刻画曲线,一般使用四个控制点来处理一段曲线,然后把各段曲线连起来,可以得到不错的效果。
当一条贝塞尔的曲线的终点和另一条贝塞尔曲线的起点重合时即\\(a_n=b_0\\),我们叫它\\(C_0\\)连续。也就是函数连续。
在\\(C_0\\)连续的情况下,当一条贝塞尔曲线的终止向量的和另一个贝塞尔曲线的起始向量反向相等时,我们称之为\\(C_1\\)连续。理解起来就是这条曲线在这点导数连续。
Bezier Surfaces(贝塞尔曲面)
用四条贝塞尔曲线的点当作四个控制点,再生成一条贝塞尔曲线,然后遍历之前的贝塞尔曲线,就可以得到贝塞尔曲面。
如图,四个贝塞尔曲线上的点形成了新的控制点,然后绘制新的贝塞尔曲线。
最后形成贝塞尔曲面:
网格细分(Mesh Subdivision)
一、Loop Subdivision(发明人叫Loop和循环没关系)
对新顶点
通过提高三角形的数量来提高模型的质量,把一个三角形分成等大的四块三角形。对于一般情况添加的点来说它会由两三角形所共享,那么这个点就可以由已知的两个三角形所确定。具体就是取加权平均数。
其中E点就在AB线段上,C、D就是两个三角形的另外两个顶点。
对旧的顶点
对于旧的顶点,参考周围的老的顶点,按照“度”(就是一个节点与周围的连线的数量)来进行加权平均,当然旧的点也需要参考自己的坐标。
其中 u 就是旧节点的度,这里是6。u 就是需要周围点的权值,参考度来生成。除去周围节点的权值,剩下的就是旧节点自己所占的权值了。
二、Catmull-Clark Subdivision(General Mesh)
Loop Subdivision只能处理三角形网格,Catmull则可以细分四边形网格。
定义
Non-quad face——非四边形面
Extrordinary vertex(degree!=4)——奇异点(度不等于4的点)
操作
在每一条边上取中电,每一个面上取中点,然后把这些点连起来,增加面密度。
经过一次操作,把所有的非四边形面变成了奇异点,使得所有的面都是四边形面,又因为非四边形面细分之后得到奇异点,所以在第一次细分之后不会再出现非四边形面。
点的更新
第一类:四边形中间的点(Face Point)
直接取顶点的平均即可
第二类:在边上的点(Edge Point)
参照构成该边的两个点和附近两个区域的内点进行平均(无视图中的f )
第三类:顶点(Vertex Point)
使用周围的八个点和自身,进行加权平均计算
网格简化(Mesh Simplification)
目的
Goal : reduce number of mesh elements while maintaining the overall shape.
在保证模型不走样的情况下,减少网格元素。
edge collapsing 边塌缩
边塌缩不是随便删除点就行,需要进行二次度量误差Quadric Error Metrics
Quadric Error Of Edge Collapse 二次度量误差
计算二次度量误差,给每一个边进行二次度量误差检查,给每一个边进行计算,得到一个二次度量误差。从二次度量误差最小的开始。然后每次塌缩最小的一条边,再更新被影响的边。
Shadows(阴影)
Shadow Mapping
只能处理点光源
Key idea 中心思想
如果一个点不在阴影里面,那么这个点可以被光源看到,也可以被相机看到。
Pass1 :Render from Light 从光源进行光栅化
>Depth image from light sourse 从光源进行光栅化,记录点的深度
Pass2A:Render from eye
从摄像机进行光栅化
Pass2B:Project to light
把摄像机中的点和这个点对于光源的深度和之前光源光栅化记录的深度进行比较,如果深度一样,说明这个点是可见的,可以被光源和摄像机同时看到。但是如果深度不一样,如图红色的深度是对于光源的深度,蓝色的深度是记录的深度,但深度大于记录的深度时,说明这个点无法被光照到。
Problems with shadow maps
shadow maps只能做硬阴影,也就是说阴影要么是黑,要么是白,但是现实生活中是软阴影,因为非点光源导致的影子是存在本影和伴影,所以Shadow maps 只能做点光源的阴影,但是不能做立体光源的阴影,也就是软阴影。
Ray Tracing 光线追踪
Why Ray Tracing?
Rasterization could\'t handle global effects well
光栅化不能很好的表现全局的效果
And especially when the light bounces more than once
光栅化无法很好的处理光线在场景中反射了不止一次的情况
(Soft)shadows 不能很好的表示和软阴影
Glossy reflection 光泽反射
indirect illumination 间接光照
光栅化在处理多次反射的时候会比较麻烦,而且不能保证物理上的正确性。相当于光珊化是一种快速的近似。而光线追踪是非常非常慢的,很真实的图形,一般用于离线,比如动画的渲染。
Basic Ray-Tracing Algorithm
Light Rays 光线
Three ideas about light rays
1.Light travels in straight lines 光线沿直线传播
2.Light rays do not "collide" with each other if they cross 光路互不干扰
(though are wrong)
3.Light rays travel from the light sources to the eye(光路可逆)
Ray Casting 光线投射
1.Generate an image by casting one ray per pixel通过每像素投射一条光线来生成图像
2.Check for shadows by sending aray to the light通过向灯光发送光线来检查阴影
在光线投射中,记录每一根光线碰到的第一个物体closest scene,那么就完美的解决了深度测试的问题,不需要再考虑深度测试。
找到一个交点之后,我们考虑这个点会不会被照亮,然后从这个点连一条线到光源,如果路径上没有阻挡,那么就可以说明这点被光照亮了,再利用法线进行着色Shading
这种方法光线还是只反射了一次。
Recursive(Whitted-Style) Ray Tracing
在光线投射的基础上,我们把从像素出发的点在碰到物体之后进行反射和折射。
最后的着色则由各个点的着色的加权叠加组成,每一个点都需要计算它的shadow ray
问题一:Ray-Surface Intersection(交点)
Ray is defined by its origin and a directino vector
光线定义为一个起点和一个方向向量
光线的方程:
与最简单的图形球的交:
球:
若相交,那么交点会满足两个方程,我们把光线的方程带入球的方程:
然后解时间t,需要满足:
1.t是正的
2.t不是虚数
3.t得是最小的
与隐式表面的交:
隐式表面就是L:\\(p:f(p)\\)表示的一个方程
带入得到:
同样的需要满足,t是正的,t不是虚数
目前的求根的方法已经很发达了,所以求根不是问题
怎么计算交点?
Simple idea:jusr intersect ray with each triangle
直接计算每一个三角形与光线是否相交。(很慢,但有效)
把问题分解为两个小的问题:
1.光线经不经过三角形所在平面
2.光线与平面的交点在不在三角形内
三角形所在平面
点法式
平面方程为
求交点
简单的方法Check:$0<t<\\infty $
交点是不是在三角形内
交点与顶点构成的向量与边的向量进行叉乘
快速的方法一步到位 Moller Trumbore Algorithm
其中\\(\\vec O+t\\vec D\\)就是光线的位置,如果在三角形内,那么一定可以用三角形的重心公式写出来,又这是三维向量,那么就是三个未知数,三个方程,可以直接解线性方程组,就能判断是否相交。甚至可以直接计算行列式来进行在否的判断,而不需要计算点的具体位置。用到克莱默法则
问题二:加速求交(从三角形数量上)
Bounding Volumes 包围盒
1.Object is fully contained in the volume.
2.If it doesn\'t hit the volume, it doesn\'t hit the object.
3.So test BVol first, then test object if it hits.
如果碰不到包围盒,那么肯定碰不到物体,所以首先测试包围盒,如果能碰到包围盒,再测试物体本身。
以上是关于GAMES104 笔记 -引擎架构分层和整体pipeline的主要内容,如果未能解决你的问题,请参考以下文章
GAMES101课程学习笔记—Lec 14~16:Ray Tracing BRDF渲染方程全局光照路径追踪