SRP如何提升了性能

渲染的原理

渲染过程,一般都是先设置各类状态(设置如何清除各种显卡中的帧缓冲,设置使用的Shader程序,设置要绘制的顶点数据(以及Shader中的顶点数据如何对应到这些顶点数据),设置要使用的Uniform变量,设置面剔除,设置深度缓冲、模板缓冲,Blend混合等等),然后调用DrawCall命令来绘制。

其中设置这些状态的过程Unity中就叫SetPassCall,而设置这些状态的命令的性能消耗是非常高的,特别是其中从内存传输数据到显卡内存中这些。

一、如何减少SetPassCall

既然SetPassCall这么耗性能,那么如何减少呢?在老版的Unity渲染流程中,可以发现有两大块可以节省很多性能:
1.在Shader使用中的很多Uniform变量,其实都是不变的(大部分时间),比如灯光那些(位置,方向,亮度之类的),相机的View矩阵、投影矩阵,以及自定义的一些也是很少变化的。这里的很少变化指的是,每一帧变化这种情况。
2.很多Uniform变量的数据只发生很小的改变,也要对这个整个Uniform进行重新设置数据。同时,Uniform变量本身也是有数量限制的。

鉴于以上原因,以OpenGL为例子,他提供了一种方法来优化上面的情况,即UBO(Uniform Buffer Object)对象与Uniform Block(Uniform块),在DX中或者Unity中叫常量缓冲区。
即可以上传一个全局的Uniform组合块,他们可以在各种Shader中通用,不用每渲染一个物体,就要设置一次这些Uniform的值,同时还提供了接口来修改这个块中的一部分值。

这样当渲染相同Shader的时候,将那些相同的Uniform变量组合到一起,就可以节省多次设置这些状态值的开销。
同时如果有部分值变化了,也可以通过只修改部分的接口,来提升整体的性能。

二、OpenGL中的UBO使用

1.设置块布局

由于硬件不同所以在构造uniform组合时,要明确规定这个组合的数据要如何分布,所以在Shader中要明确设置布局(这里是std140布局,默认的共享布局方式会导致不同的内存分布)。

layout (std140) uniform ExampleBlock
{
    float value;
    vec3  vector;
    mat4  matrix;
    float values[3];
    bool  boolean;
    int   integer;
};

这样在内存设置好对应的内存布局后,就可以映射到对应的显卡内存中。

2.创建并使用Uniform Buffer Object

代码如下(示例):

unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存
glBindBuffer(GL_UNIFORM_BUFFER, 0);

这样就可以申请并设置对应的缓冲对象数据,然后去填充这个数据:

glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // GLSL中的bool是4字节的,所以我们将它存为一个integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); 
glBindBuffer(GL_UNIFORM_BUFFER, 0);

3.Shader与Uniform Buffer Object绑定

由于UBO可能有多个,Shader中的Uniform Buffer Block也可能有多个,所以需要一种方式来映射他们。OpenGL中使用绑定点的方式来映射。

UBO的绑定:

glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);

对应Shader中的绑定

unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");   
glUniformBlockBinding(shaderA.ID, lights_index, 2);

这样Shader中的Uniform Buffer Block就知道去哪一个UBO去取数据了,且已经分配好布局,能搞正常拿到对应的数据。

4.修改部分数据

如果我们想只修改其中的一部分数据,我们可以

glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b);

来修改一个UBO中的一部分数据。

总结

Unity使用SRP时,对我们封装了上面类似OpenGL的一大串功能设置及相关功能,且做到各个平台兼容。SRP Batch就是利用全局的数据缓冲的方式,以及局部修改的方式,来帮助提高渲染性能。