实例渲染

假设想渲染一个有一支庞大军队经过的场景,要用一个士兵的模型渲出成千上万的士兵大军。一种方法是每次绘制一个士兵都调用一次draw call,并更新当前士兵相关的一致变量(位置、尺寸等)。例如,每个士兵都处于不同的位置,每个士兵都可以更高或者更矮尺寸不一等。因此,每次draw call都要更新当前士兵模型的WVP矩阵,这样开销会很大。

实例渲染:一个实例就是要渲染的模型在场景的一次实际出现,这里例子中就是一个士兵。实例化渲染意味着可以在一次draw call中渲染多个实例,并为每个实例提供其特有的属性。

方法1:在一个单独的顶点缓冲VB中定义实例的属性(例如:WVP矩阵)。通常顶点处理器会逐顶点每次处理一个顶点数据,有了存放实例数据的顶点缓冲VB后,定点处理器在每次处理一个顶点时要等所有顶点都已经被绘制,存有实例数据的VB提供的属性数据是针对所有顶点公共的。(第一种是将实例数据作为顶点属性传递)

方法2:使用一个内置的叫做gl_InstanceID的shader变量,它是用来告诉当前的实例索引index的。我们可以根据这个索引值来定位一致变量数组中对应的实例数据来进行相应的渲染操作。

image-20230127161506622

这里有一个包含100个顶点的模型,每个顶点都有位置、法线、纹理坐标属性,三个属性中每个属性都有他们自己的顶点缓冲。另外,还有第四个顶点缓冲buffer存放着三个WVP矩阵。先使用第一个WVP矩阵应用到100个顶点pos位置的绘制上,然后再使用第二个WVP矩阵绘制一遍,然后再使用第三个绘制,并且这个过程是在一个draw call中实现的,而不是三个。WVP矩阵会作为输入参数传送到顶点处理器中,但由于第四个VB被标记为存有实例数据,所以WVP矩阵在所有顶点绘制完之前是不会变化的。

cpp文件:

​ 由于WVP矩阵是一个4x4矩阵,不能仅为其启用一个顶点属性,因为顶点属性最多只能包含4个浮点或整数。因此需要启用和配置4个连续顶点属性的循环。每个属性将包含矩阵中的一个向量(第一个属性1包含三个MVP矩阵的第一个向量,第二个属性2包含三个MVP矩阵的第二个向量……)。四个属性中的每一个都由四个浮点组成,一个矩阵中的属性与下一个矩阵之间的距离是4x4矩阵的大小,因此一共占了1、2、3、4顶点属性。

​ glVertexAttribDivisitor()函数使其成为实例数据而不是顶点数据。它采用两个参数:第一个是顶点数组属性,第二个参数告诉OpenGL在实例化渲染过程中属性前进的速度。默认情况下,除数为零。如果除数是10,这意味着前10个实例将使用缓冲区中的第一条数据,接下来的10个实例会使用第二条数据。此处希望每个实例都有一个专用的WVP矩阵,因此使用1的除数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建存储了3个MPV矩阵的顶点属性数组,glm::mat4类型
static glm::mat4 WVPMats[] = {MVP1,MVP2,MVP3};
//创建并绑定缓冲区
GLuint MVPbuffer;
glGenBuffers(1, &MVPbuffer);
glBindBuffer(GL_ARRAY_BUFFER, MVPbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4)* 3, WVPMats, GL_DYNAMIC_DRAW);

for (unsigned int i = 0; i < 4; i++) {
glEnableVertexAttribArray(1 + i);
glVertexAttribPointer(1 + i, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4),
(const GLvoid*)(sizeof(GLfloat) * i * 4));
glVertexAttribDivisor(1 + i, 1);
}

该函数的唯一变化是将实例数作为第四个参数。

对于(i=0;i<NumInstance;i++)

如果(i mod除数==0)

从带有实例数据的VBs中获取属性i/除数

对于(j=0;j<NumVertices;j++)

从带有顶点数据的VBs获取属性j

1
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 3);

顶点着色器中:

不再将WVP和世界矩阵作为统一变量,而是将它们作为顶点属性。VS不关心它们的值只会在每个实例中更新一次,而不会在每个顶点中更新。WVP矩阵占据位置1-4。

1
2
3
4
5
6
7
#version 330
layout (location = 0) in vec3 Position;
layout (location = 1) in mat4 MVP;
void main()
{
gl_Position = MVP * vec4(Position, 1.0);
};

如果使用第二种方法

顶点着色器

​ gl_InstanceID是一个内置变量,仅在VS中可用。由于计划在FS中使用它,因此必须在此处访问它,并在常规输出变量中传递它。gl_InstanceID的类型是整数,因此使用相同类型的输出变量。由于光栅化器无法对整数进行插值,必须将输出变量标记为“flat”。

1
2
3
4
5
flat out int InstanceID;
void main()
{
InstanceID = gl_InstanceID;
};

片段着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 330
flat in int InstanceID;
out vec4 FragColor;
vec4 gColor[4] = {
vec4(0.5, 0.0, 0.0,0.0),
vec4(0.0, 0.5, 0.0,0.0),
vec4(0.0, 0.0, 0.5,0.0),
vec4(0.0, 0.0, 0.0,0.0)
};
void main()
{
FragColor = gColor [InstanceID % 4];
};

效果如下:

image-20230127172248210
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码

请我喝杯咖啡吧~

支付宝
微信