3.1 实例化是什么?为什么要实例化?
实例化就是生成一个个体,比如一棵树,一簇草丛,实例化的目的是为了节省性能,因为每一棵树我们不能调用一次绘制API,我们需要把尽可能多的东西,合批,去渲染,实例化就是我们把一个东西生成多份,比如很多树,一次绘制。
3.2 定义
几何体:游戏物体就像类和子类的关系,每一个实例基本体有共同的属性,比如顶点缓存,实例类型,骨骼等,而具体到每一棵树又都有不同的属性,比如树叶的颜色,树叶的数量。几何体就是子类。
一个批次中要渲染很多个几何体。
3.3 实现
渲染实现包含四种方法:静态合批、动态合批、顶点常量实例化、调用几何体实例化的API
静态批次:数据一次性让GPU绘制出来,不支持蒙皮(骨骼),灵活性低,但是简单,需要一次性分配内存,即:顶点缓存和索引缓存,定义完就不可以更改了。
动态合批:动态生成几何体,支持蒙皮,但是耗费时间,也需要预先定义存储空间。
顶点常量实例化:把一个几何体复制多份,传递给GPU,然后在顶点着色器中,根据顶点常量来制 造几何体实例的差异性。 限制就是:每一个批次设置的顶点常量有限制。
调用几何体绘制的API:限制:只能批次渲染同一个几何体包的实例,但是节省了内存消耗
四:分段缓冲
4.1 问题
如果一个场景中有很多个相同几何体包的不同实例,虽然我们可以在同一个批次中渲染它们,但是这需要我们每个实例都进行差异化处理,比如旋转矩阵等,也可以让美工在做的时候,把相邻的模型合并成单个模型,但是这样增加了美工的工作量。
4.2 分段缓冲
分段缓冲会自动合并相似的实例,同时保留了渲染独立实例的能力。
原理就是为每个实例分组,形成一个链表,然后分段渲染。
五:用多流来优化资源管理
当一个场景中有多的顶点数据需要渲染,则有的GPU能渲染,有的GPU 不能渲染,我们需要根据不同的GPU 调整我们的数据内容,多流:就是根据不同的情况,采取不同的结构体,不同的结构体里面的内容不一样,比如用于顶点渲染的基本结构体,包含顶点位置,法线位置等
如果和当前的性能不匹配,就用最基本的结构体,传递最基本的数据。
资源管理:
每一个资源都有一个GUID,用来全局搜索该资源,同时用寄存器来存储网格数据,让GPU 直接和显存交互,而不用从内存里读取。
六:遮挡查询
前提:
遮挡查询的目的是找到哪些视野中看不到的物体,从而从绘制的数据里面剔除它。
最简单的方法就是:
对屏幕空间的像素进行深度排序,也就是z-test,但是这样有两个缺点,一个是要知道一个像素绘不绘制需要等待查询的结果,这十分耗费时间,第二个缺点是复杂的场景需要遮挡查询的物体非常多。
6.4 层
6.4.1 为什么使用层?
我们把场景中的物体分层,也就是上面说的栅格单元,可以使用k-d 树,八叉树 等,把空间中的物体最终集合到一个一个的单元当中,这样以单元为单位,如果该单元的父节点不可见,则就不需要渲染该单元的物体了,如果可见,递归遍历子节点,进行遮挡查询,如果是叶子节点,直接渲染,对于复杂的场景,减少了遮挡查询的次数。
6.4.2 停滞
它和最基本的遮挡查询都有一个共同的缺点,就是停滞,在返回一个几何实例的遮挡结果时,CPU停滞,等待GPU查询,当返回结果时,GPU停滞。
6.4.3 查询额外的开销
我们查询的是先查询父节点,再查询子节点,对于场景中大多数不可见的物体,减少了查询的次数,但是如果都是可见的物体,则增加了查询次数,因为要遍历整个单元内的所有物体。
6.5 针对上面两个问题的优化
6.5.1 猜测
我们根据上一帧的结果,猜测下一帧的结果,但是这有可能猜错,所以需要后面的校正,这就消除了停滞的问题,也就是每时每刻都让CPU和GPU有事干,当查询结果和当前结果不对时,校正当前的结果。
结果无非就两种情况,实际可见,预测不可见,这需要在遮挡查询的时候校正;实际不可见,预测可见,这种情况渲染的结果是正确的,只不过要多花费点时间。
6.5.2 提升
为了解决查询数目的问题,就是要减少查询的节点数量
我们只查询上一帧看的见得的叶子结点,以及层中有可能看得见的节点,如果一个节点的父节点被遮挡了,就不用查询该节点了。
6.5.3 算法
我们用一个DP表来记录已经查询过得节点的遮挡结果,当我们遮挡查询时,先看表里是否已有记录,同时如果遮挡查询的结果和表里的不一致,要马上更新DP表
我们通过查询的节点能够倒推出父节点的遮挡结果。减少了查询的次数