OpenGL学习-高级OpenGL-实例化

目录

简述:

glDrawArraysInstanced和glDrawElementsInstanced:

gl_InstanceID:

实例化数组:

示例:

实例化渲染示例:

顶点着色器:

设置为实例化数组:

调用glDrawElementsInstanced绘制:

效果展示:

学习链接:


简述:

如果我们想以前一样渲染几千个物体,调用几千个渲染函数会极大的影响性能。

与绘制顶点本身相比,使用glDrawArrays或glDrawElements函数告诉GPU去绘制你的顶点数据会消耗更多的性能,因为OpenGL在绘制顶点数据之前需要做很多准备工作(比如告诉GPU该从哪个缓冲读取数据,从哪寻找顶点属性,而且这些都是在相对缓慢的CPU到GPU总线(CPU to GPU Bus)上进行的)。所以,即便渲染顶点非常快,命令GPU去渲染却未必。

如果我们能够将数据一次性发送给GPU,然后使用一个绘制函数让OpenGL利用这些数据绘制多个物体,就会更方便了。这就是实例化(Instancing)。

实例化这项技术能够让我们使用一个渲染调用来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信,它只需要一次即可。

glDrawArraysInstanced和glDrawElementsInstanced:

如果想使用实例化渲染,我们只需要将glDrawArrays和glDrawElements的渲染调用分别改为glDrawArraysInstanced和glDrawElementsInstanced就可以了。这些渲染函数的实例化版本需要一个额外的参数,叫做实例数量(Instance Count),它能够设置我们需要渲染的实例个数。

gl_InstanceID:

并且GLSL在顶点着色器中嵌入了另一个内建变量,gl_InstanceID。在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1。比如说,我们正在渲染第43个实例,那么顶点着色器中它的gl_InstanceID将会是42。

OpenGL学习-高级OpenGL-实例化_第1张图片
OpenGL学习-高级OpenGL-实例化_第2张图片

实例化数组:

如果我们要渲染远超过100个实例的时候(这其实非常普遍),我们最终会超过最大能够发送至着色器的uniform数据大小上限。它的一个代替方案是实例化数组(Instanced Array),它被定义为一个顶点属性(能够让我们储存更多的数据),仅在顶点着色器渲染一个新的实例时才会更新。

使用顶点属性时,顶点着色器的每次运行都会让GLSL获取新一组适用于当前顶点的属性。而当我们将顶点属性定义为一个实例化数组时,顶点着色器就只需要对每个实例,而不是每个顶点,更新顶点属性的内容了。这允许我们对逐顶点的数据使用普通的顶点属性,而对逐实例的数据使用实例化数组。

示例:

OpenGL学习-高级OpenGL-实例化_第3张图片

我们先生成buffer,把顶点数据绑上

OpenGL学习-高级OpenGL-实例化_第4张图片

然后启用location=2的顶点属性

OpenGL学习-高级OpenGL-实例化_第5张图片

glVertexAttribDivisor的含义是告诉了OpenGL该什么时候更新顶点属性的内容至新一组数据。它的第一个参数是需要的顶点属性,第二个参数是属性除数(Attribute Divisor)。默认情况下,属性除数是0,告诉OpenGL我们需要在顶点着色器的每次迭代时更新顶点属性。将它设置为1时,我们告诉OpenGL我们希望在渲染一个新实例的时候更新顶点属性。而设置为2时,我们希望每2个实例更新一次属性,以此类推。

实例化渲染示例:

顶点着色器:

#version330 core



 

layout (location =0) invec3 aPos;

layout (location =2) invec2 aTexCoords;

layout (location =3) inmat4 aInstanceMatrix;

outvec2 TexCoords;

uniformmat4 projection;

uniformmat4 view;

void main()

{

TexCoords = aTexCoords;

gl_Position = projection * view * aInstanceMatrix *vec4(aPos, 1.0f);

}

顶点属性最大允许的数据大小等于一个vec4。因为一个mat4本质上是4个vec4,我们需要为这个矩阵预留4个顶点属性。因为我们将它的位置值设置为3,矩阵每一列的顶点属性位置值就是3、4、5和6。

设置为实例化数组:

// 顶点缓冲对象



 

unsigned intbuffer;

glGenBuffers(1, &buffer);

glBindBuffer(GL_ARRAY_BUFFER, buffer);

glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);

for(unsigned int i =0; i < rock.meshes.size(); i++)

{

unsigned int VAO = rock.meshes[i].VAO;

glBindVertexArray(VAO);

// 顶点属性

GLsizei vec4Size = sizeof(glm::vec4);

glEnableVertexAttribArray(3);

glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4* vec4Size, (void*)0);

glEnableVertexAttribArray(4);

glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4* vec4Size, (void*)(vec4Size));

glEnableVertexAttribArray(5);

glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4* vec4Size, (void*)(2* vec4Size));

glEnableVertexAttribArray(6);

glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4* vec4Size, (void*)(3* vec4Size));

glVertexAttribDivisor(3, 1);

glVertexAttribDivisor(4, 1);

glVertexAttribDivisor(5, 1);

glVertexAttribDivisor(6, 1);

glBindVertexArray(0);

}

注意这里我们将Mesh的VAO从私有变量改为了公有变量,让我们能够访问它的顶点数组对象。这并不是最好的解决方案,只是为了配合本小节的一个简单的改动。除此之外代码就应该很清楚了。我们告诉了OpenGL应该如何解释每个缓冲顶点属性的缓冲,并且告诉它这些顶点属性是实例化数组。

调用glDrawElementsInstanced绘制:

// 绘制小行星

instanceShader.use();

for(unsigned int i =0; i < rock.meshes.size(); i++)

{

glBindVertexArray(rock.meshes[i].VAO);

glDrawElementsInstanced(

GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount

);

}

效果展示:

(这里和前面模板测试和深度测试的东西没改

OpenGL学习-高级OpenGL-实例化_第6张图片

简单算了一些帧率,就算在有io的情况(这个输入输出在我以前做大文件处理的时候把我坑死了)下,帧率还是很稳定的

实例化还是很棒的,可以用于渲染草、植被、粒子,以及重复的东西

学习链接:

1.实例化 - LearnOpenGL CN

你可能感兴趣的:(图形学学习笔记,图形学,shader,3d渲染,opengl,opengles)