如何使用gl_InstanceID与glDrawElements绘制选中的OpenGL实例?
问题描述
我希望使用glDrawElements系列函数绘制选中的实例,但不清楚应选用哪个函数及如何设置参数。
已在着色器中使用gl_InstanceID,并通过SSBO传递数据:
glGenBuffers(1, &SSBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, SSBO); glBufferData(GL_SHADER_STORAGE_BUFFER, data_vector.size() * sizeof(data_type), data_vector.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding_index, SSBO);
我有一个bool类型数组drawable_vector,用于判断对应实例是否需要绘制:
for(auto index = 0; index < drawable_vector.size(); index ++){ glDrawElements...(???) }
要求仅当drawable_vector[index]为true时,绘制gl_InstanceID为index的实例,drawable_vector、data_vector的大小与实例总数一致。
补充代码如下:
初始化代码
//VAO glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); //VBO1 - Position glGenBuffers(1, &VBO1); glBindBuffer(GL_ARRAY_BUFFER, VBO1); glBufferData(GL_ARRAY_BUFFER, 3*sizeof(glm::vec4), vertices.data(), GL_DYNAMIC_DRAW); //VBO2 - color glGenBuffers(1, &VBO2); glBindBuffer(GL_ARRAY_BUFFER, VBO2); glBufferData(GL_ARRAY_BUFFER, 3*sizeof(glm::vec4), colors.data(), GL_DYNAMIC_DRAW); //EBO glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_DYNAMIC_DRAW); //SSBO - models in shader glGenBuffers(1, &SSBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, SSBO); glBufferData(GL_SHADER_STORAGE_BUFFER, models.size() * sizeof(glm::vec4), models.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, SSBO); //Set data of shader //in_pos glBindBuffer(GL_ARRAY_BUFFER, VBO1); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (void *) 0); glEnableVertexAttribArray(0); //in_color glBindBuffer(GL_ARRAY_BUFFER, VBO2); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void *) 0); glEnableVertexAttribArray(1); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glBindVertexArray(0);
着色器代码
片段着色器
#version 450 core out vec4 frag_color; in vec4 out_color; void main() { frag_color = out_color; }
顶点着色器
#version 430 core layout (location = 0) in vec4 in_pos; layout (location = 1) in vec4 in_color; layout(std430, binding = 2) buffer DataBuffer { mat4 in_models[]; } buffer_data; uniform mat4 view; uniform mat4 projection; out vec4 out_color; void main() { out_color = in_color; gl_Position = projection * view * buffer_data.in_models[gl_InstanceID] * in_pos; }
渲染代码
use_shader(); shader_set_view("view", view); shader_set_projection("projection", projection); update_models(); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO1); glBindBuffer(GL_ARRAY_BUFFER, VBO2); glBindBuffer(GL_ARRAY_BUFFER, EBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, SSBO); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, SSBO[0]); glDrawArraysInstanced(GL_TRIANGLES, 0, 3, n_instances); glfwSwapBuffers(window); glBindVertexArray(0);
解决方案
针对需求,提供三种不同场景的实现方案:
方案1:逐个绘制选中实例(简单直接)
使用glDrawElementsInstancedBaseInstance函数,指定起始实例ID,每次仅绘制1个选中的实例:
glBindVertexArray(VAO); for (size_t index = 0; index < drawable_vector.size(); ++index) { if (drawable_vector[index]) { // 参数说明:图元类型、索引数量、索引类型、索引偏移、实例数量、起始实例ID glDrawElementsInstancedBaseInstance(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0, 1, index); } }
注意:该方案会产生大量Draw Call,仅适合实例数量较少的场景。
方案2:批量绘制选中实例(高性能)
通过收集选中的实例ID,配合SSBO批量绘制,仅需一次Draw Call:
- 先收集所有需要绘制的实例ID:
std::vector<GLuint> selected_instances; for (size_t index = 0; index < drawable_vector.size(); ++index) { if (drawable_vector[index]) { selected_instances.push_back(index); } }
- 修改顶点着色器,新增一个SSBO存储选中的实例ID:
#version 430 core layout (location = 0) in vec4 in_pos; layout (location = 1) in vec4 in_color; layout(std430, binding = 2) buffer DataBuffer { mat4 in_models[]; } buffer_data; // 新增选中实例ID的SSBO layout(std430, binding = 3) buffer SelectedInstances { uint instance_ids[]; } selected_data; uniform mat4 view; uniform mat4 projection; out vec4 out_color; void main() { // 通过gl_InstanceID获取实际要绘制的实例ID uint actual_id = selected_data.instance_ids[gl_InstanceID]; out_color = in_color; gl_Position = projection * view * buffer_data.in_models[actual_id] * in_pos; }
- 渲染时更新选中实例的SSBO并批量绘制:
// 创建并绑定选中实例的SSBO GLuint selected_SSBO; glGenBuffers(1, &selected_SSBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, selected_SSBO); glBufferData(GL_SHADER_STORAGE_BUFFER, selected_instances.size() * sizeof(GLuint), selected_instances.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, selected_SSBO); // 批量绘制所有选中实例 glBindVertexArray(VAO); glDrawElementsInstanced(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0, selected_instances.size());
方案3:着色器层面剔除(无需修改Draw Call)
将drawable_vector传入SSBO,在顶点着色器中直接剔除不需要绘制的实例:
- 修改顶点着色器,新增存储绘制状态的SSBO:
#version 430 core layout (location = 0) in vec4 in_pos; layout (location = 1) in vec4 in_color; layout(std430, binding = 2) buffer DataBuffer { mat4 in_models[]; } buffer_data; // 新增绘制状态的SSBO layout(std430, binding = 3) buffer DrawableBuffer { bool drawable[]; } drawable_data; uniform mat4 view; uniform mat4 projection; out vec4 out_color; void main() { // 剔除不需要绘制的实例:将顶点移到裁剪空间外 if (!drawable_data.drawable[gl_InstanceID]) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); return; } out_color = in_color; gl_Position = projection * view * buffer_data.in_models[gl_InstanceID] * in_pos; }
- 渲染时更新绘制状态的SSBO,直接绘制所有实例:
// 创建并绑定绘制状态的SSBO GLuint drawable_SSBO; glGenBuffers(1, &drawable_SSBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, drawable_SSBO); glBufferData(GL_SHADER_STORAGE_BUFFER, drawable_vector.size() * sizeof(bool), drawable_vector.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, drawable_SSBO); // 一次绘制所有实例,着色器自动剔除不需要的部分 glBindVertexArray(VAO); glDrawElementsInstanced(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0, drawable_vector.size());
补充修正
你的渲染代码中有两处错误需要修正:
glBindBuffer(GL_ARRAY_BUFFER, EBO);:EBO应绑定到GL_ELEMENT_ARRAY_BUFFER,且VAO已保存EBO绑定状态,渲染时无需重复绑定。glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, SSBO[0]);:SSBO是单个缓冲区ID,应改为SSBO。
内容的提问来源于stack exchange,提问作者KennyTan
相关产品推荐
相关产品推荐

