上接:https://blog.csdn.net/weixin_44506615/article/details/151156446?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL
实例化 Instancing
以往当我们在场景中要大量绘制相同模型的物体时,代码会像是这样
for (unsigned int i = 0; i < modelCount; i++)
{Bind();glDrawArrays(GL_TRIANGLES, 0, verticesCount);
}
这样CPU会发起很多次绘制调用 (DrawCall) ,这个操作是相对比较缓慢的,很快就会称为性能的瓶颈
所以我们将使用实例化的方式,仅需一次绘制调用就可以绘制多个物体
1、gl_InstanceID
要使用实例化渲染,我们只需要将 glDrawArrays
和 glDrawElements
换成 glDrawArraysInstanced
和 glDrawElementsInstanced
即可,这两个新接口在最后多了一个参数为实例数量
在顶点着色器中,我们可以通过 gl_InstanceID 来知道当前绘制的是第几个实例
首先我们先修改一下Model
和Mesh
类
Model
// Model.h
/**
* 绘制
*/
void Draw(Shader shader, int instanceCnt = 0);// Model.cpp
void Model::Draw(Shader shader, int instanceCnt)
{for (unsigned int i = 0; i < meshes.size(); ++i){meshes[i].Draw(shader, instanceCnt);}
}
Mesh
// Mesh.h
/**
* 绘制
*/
void Draw(const Shader& shader, int instanceCnt = 0);// Mesh.cpp
void Mesh::Draw(const Shader& shader, int instanceCnt)
{// ...//绘制if (instanceCnt > 1)glDrawElementsInstanced(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0, instanceCnt);elseglDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);// ...
}
然后我们需要初始化实例的偏移数组,接着传入到uniform变量中使用gl_InstanceID
读取
Main.cpp
// gl_InstanceID + uniform 实例化偏移数组
glm::vec3 instanceOffsets[9];
int instanceOffsetsIdx = 0;
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 3; j++){glm::vec3 offset;offset.x = i * 5;offset.y = j * 5;offset.z = 0;instanceOffsets[instanceOffsetsIdx++] = offset;}
}// 主循环
// gl_InstanceID + uniform 实例化
for (int i = 0; i < 9; i++)
{shader.SetVec3("offsets[" + std::to_string(i) + "]", instanceOffsets[i]);
}
model.Draw(shader, 9);
最后修改背包的顶点着色器
VertexShader.glsl
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
// 去掉了多余的Positionuniform mat4 model;
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;
} vs_out;// 实例偏移
uniform vec3 offsets[9];void main()
{vec3 offset = offsets[gl_InstanceID];gl_Position = projection * view * model * vec4(aPos + offset, 1.0);FragPos = vec3(model * vec4(aPos + offset, 1.0));vs_out.FragPos = FragPos;Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Normal = Normal;TexCoords = aTexCoords;vs_out.TexCoords = aTexCoords;
}
编译运行,顺利的话可以看见以下图像
我们只进行了一次绘制调用便绘制了9个背包
2、 实例化数组 (Instanced Array)
当我们需要绘制的实例数量较多的时候,我们很可能会超过最大能够发送至着色器的uniform数据大小上限,我们就需要使用实例化数组的方式
实例化数组的使用方式和其他顶点属性类似,我们先修改背包的顶点着色器
VertexShader.glsl
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 offset; // 实例化数组方式out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
// 去掉了多余的Positionuniform mat4 model;
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Debug;
} vs_out;void main()
{gl_Position = projection * view * model * vec4(aPos + offset, 1.0);FragPos = vec3(model * vec4(aPos + offset, 1.0));vs_out.FragPos = FragPos;Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Normal = Normal;TexCoords = aTexCoords;vs_out.TexCoords = aTexCoords;
}
接下来我们为实例化数组创建VBO并绑定
Main.cpp
// 初始化instanceOffsets ...// 实例化数组方式
for (auto it = model.meshes.begin(); it != model.meshes.end(); ++it)
{// 需要绑定到背包模型的VAO上it->BindVAO();unsigned int instanceVBO;glGenBuffers(1, &instanceVBO);glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * 9, &instanceOffsets[0], GL_STATIC_DRAW);glEnableVertexAttribArray(3);glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glBindBuffer(GL_ARRAY_BUFFER, 0);glVertexAttribDivisor(3, 1);it->UnBindVAO();
}
这里最后我们调用了 glVertexAttribDivisor
这个函数告诉了OpenGL什么时候更新顶点属性的内容至新一组的数据
它第一个参数为需要的顶点属性
第二个参数为 属性除数 (Attribute Divisor)
默认时属性除数为0,告诉OpenGL我们需要在顶点着色器每次迭代时更新
这里我们设置为1,告诉OpenGL我们需要在每渲染一个新实例的时候更新,这里我们每个新实例都需要更新offset
同理,设置为2时表示每两个实例更新一次
上述过程可以考虑封装进Model类,这里先从简直接暴露绑定和解绑VAO的接口
Mesh
// Mesh.h
/**
* 绑定VAO
*/
void BindVAO();/**
* 解绑VAO
*/
void UnBindVAO();// Mesh.cpp
void Mesh::BindVAO()
{glBindVertexArray(VAO);
}void Mesh::UnBindVAO()
{glBindVertexArray(0);
}
最后调用绘制命令
Main.cpp
// 实例化数组方式
model.Draw(shader, 9);
编译运行,顺利的话可以看见和 gl_InstanceID + Uniform
一样的效果
完整代码可在顶部git仓库中找到