OpenGL进阶之Instancing

Instancing

Instancing绘制我想很多童鞋都不陌生,这个技术主要用来快速渲染大量相同的几何体,可以大大提高绘制效率。每个instance在shader中都有一个独一无二的索引,可以用来访问每个instance对应的渲染参数。使用Instancing技术之所以能够大大提高效率,主要是因为它大大减少了dip(draw indexed primitive)的数量。

在实际应用中,我们可以将所有的渲染参数打包到一个buffer中,将它们一起送到GPU,然后,只需要调用一次绘制命令,就可以绘制大批不同的实体。同样,如果instance data没有改变的话(比如我们明确知道它们是用来绘制静态物体的),则不需要每一帧都将这些数据往GPU送一次,只需要在程序第一次初始化组织数据时传输一次就够了。

可能大家比较熟悉的,还是通过VBO进行Instancing绘制,但在实际的程序编写和优化过程中,还有很多buffer可以用来Instancing,OpenGL也有很多存储数据的方式,像VBO,UBO,TBO,SSBO等等。下面就详细讲解一下利用不同的数据传输方式进行Instancing绘制。

 

Texture instancing

 这种方式是将所有渲染用到的数据都保存在一张纹理当中,然后通过PBO(Pixel Buffer Object)的方式进行数据更新,PBO通过异步的方式实现CPU到GPU之间的数据传输,从而不需要阻塞CPU等待数据传输完成。

 

纹理创建:

glGenBuffers(2, textureInstancingPBO);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[0]); 

//GL_STREAM_DRAW_ARB means that we will change data every frame glBufferData(GL_PIXEL_UNPACK_BUFFER, INSTANCES_DATA_SIZE, 0, GL_STREAM_DRAW_ARB); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[1]); glBufferData(GL_PIXEL_UNPACK_BUFFER, INSTANCES_DATA_SIZE, 0, GL_STREAM_DRAW_ARB); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
//create texture where we will store instances data on gpu glGenTextures(1, textureInstancingDataTex); glBindTexture(GL_TEXTURE_2D, textureInstancingDataTex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT); //in each line we store NUM_INSTANCES_PER_LINE object's data. 128 in our case
//for each object we store PER_INSTANCE_DATA_VECTORS data-vectors. 2 in our case //GL_RGBA32F, we have float32 data //complex_mesh_instances_data source data of instances, if we are not going to update data in the texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, NUM_INSTANCES_PER_LINE * PER_INSTANCE_DATA_VECTORS, MAX_INSTANCES / NUM_INSTANCES_PER_LINE, 0, GL_RGBA, GL_FLOAT, &complex_mesh_instances_data[0]); glBindTexture(GL_TEXTURE_2D, 0);

 

纹理更新:

glBindTexture(GL_TEXTURE_2D, textureInstancingDataTex);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[current_frame_index]); // copy pixels from PBO to texture object
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, NUM_INSTANCES_PER_LINE * PER_INSTANCE_DATA_VECTORS, MAX_INSTANCES / NUM_INSTANCES_PER_LINE, GL_RGBA, GL_FLOAT, 0); // bind PBO to update pixel values
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[next_frame_index]);
//http://www.songho.ca/opengl/gl_pbo.html // Note that glMapBufferARB() causes sync issue. // If GPU is working with this buffer, glMapBufferARB() will wait(stall) // until GPU to finish its job. To avoid waiting (idle), you can call // first glBufferDataARB() with NULL pointer before glMapBufferARB(). // If you do that, the previous data in PBO will be discarded and // glMapBufferARB() returns a new allocated pointer immediately // even if GPU is still working with the previous data. glBufferData(GL_PIXEL_UNPACK_BUFFER, INSTANCES_DATA_SIZE, 0, GL_STREAM_DRAW_ARB); gpu_data = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY_ARB); if (gpu_data) {   memcpy(gpu_data, complex_mesh_instances_data[0], INSTANCES_DATA_SIZE); // update data   glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); //release pointer to mapping buffer }

 

使用texture instancing渲染:

//bind texture with instances data
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureInstancingDataTex);
glBindSampler(0, Sampler_nearest);
glBindVertexArray(geometry_vao_id); //what geometry to render
tex_instancing_shader.bind(); //with what shader
//tell shader texture with data located, what name it has static GLint location = glGetUniformLocation(tex_instancing_shader.programm_id, s_texture_0); if (location >= 0)   glUniform1i(location, 0); //render group of objects glDrawElementsInstanced(GL_TRIANGLES, BOX_NUM_INDICES, GL_UNSIGNED_INT, NULL, CURRENT_NUM_INSTANCES);

 

顶点着色器示例:

version 150 core
in vec3 s_pos;
in vec3 s_normal;
in vec2 s_uv;
uniform mat4 ModelViewProjectionMatrix;

uniform sampler2D s_texture_0;
out vec2 uv;
out vec3 instance_color;
void main()
{
  const vec2 texel_size = vec2(1.0 / 256.0, 1.0 / 16.0);
  const int objects_per_row = 128;
  const vec2 half_texel = vec2(0.5, 0.5);
  //calc texture coordinates - where our instance data located   //gl_InstanceID % objects_per_row - index of object in the line   //multiple by 2 as each object has 2 vectors of data   //gl_InstanceID / objects_per_row - in what line our data located   //multiple by texel_size gieves us 0..1 uv to sample from texture from interer texel id   vec2 texel_uv = (vec2((gl_InstanceID % objects_per_row) * 2, floor(gl_InstanceID / objects_per_row)) + half_texel) * texel_size;   vec4 instance_pos = textureLod(s_texture_0, texel_uv, 0);   instance_color = textureLod(s_texture_0, texel_uv + vec2(texel_size.x, 0.0), 0).xyz;   uv = s_uv;   gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0); }

 

vertex buffer Instancing

通过将数据存储在vbo,然后通过制定不同的数据布局,来制定不同的渲染参数。

 

buffer创建:

//...code of base vertex declaration creation
//special atributes binding
glBindBuffer(GL_ARRAY_BUFFER, all_instances_data_vbo); //size of per instance data (PER_INSTANCE_DATA_VECTORS = 2 - so we have to create 2 additional attributes to transfer data)
const int per_instance_data_size = sizeof(vec4) * PER_INSTANCE_DATA_VECTORS;
glEnableVertexAttribArray(4);
glVertexAttribPointer((GLuint)4, 4, GL_FLOAT, GL_FALSE, per_instance_data_size, (GLvoid*)(0)); //tell that we will change this attribute per instance, not per vertex
glVertexAttribDivisor(4, 1);
glEnableVertexAttribArray(5); //5th vertex attribute, has 4 floats, sizeof(vec4) data offset
glVertexAttribPointer((GLuint)5, 4, GL_FLOAT, GL_FALSE, per_instance_data_size, (GLvoid*)(sizeof(vec4)));
//tell that we will change this attribute per instance, not per vertex glVertexAttribDivisor(5, 1);

 

渲染:

vbo_instancing_shader.bind(); //our vertex buffer wit modified vertex declaration (vdecl)
glBindVertexArray(geometry_vao_vbo_instancing_id);
glDrawElementsInstanced(GL_TRIANGLES, BOX_NUM_INDICES, GL_UNSIGNED_INT, NULL, CURRENT_NUM_INSTANCES);

 

顶点着色器:

#version 150 core
in vec3 s_pos;
in vec3 s_normal;
in vec2 s_uv;
in vec4 s_attribute_3; //some_data;
in vec4 s_attribute_4; //instance pos
in vec4 s_attribute_5; //instance color
uniform mat4 ModelViewProjectionMatrix;
out vec3 instance_color;
void main()
{
  instance_color = s_attribute_5.xyz;
  gl_Position = ModelViewProjectionMatrix * vec4(s_pos + s_attribute_4.xyz, 1.0);
}

  

Uniform Buffer Instancing:

 

buffer创建:

glGenBuffers(1, dips_uniform_buffer);
glBindBuffer(GL_UNIFORM_BUFFER, dips_uniform_buffer);
glBufferData(GL_UNIFORM_BUFFER, INSTANCES_DATA_SIZE, &complex_mesh_instances_data[0], GL_STATIC_DRAW);
//uniform_buffer_data
glBindBuffer(GL_UNIFORM_BUFFER, 0);
//bind iniform buffer with instances data to shader
ubo_instancing_shader.bind(true);
GLint instanceData_location3 = glGetUniformLocation(ubo_instancing_shader.programm_id, "instance_data");
//link to shader
glUniformBufferEXT(ubo_instancing_shader.programm_id, instanceData_location3, dips_uniform_buffer); //actually binding

 

着色器数据访问:

#version 150 core
#extension GL_ARB_bindable_uniform : enable
#extension GL_EXT_gpu_shader4 : enable
in vec3 s_pos;
in vec3 s_normal;
in vec2 s_uv;
uniform mat4 ModelViewProjectionMatrix;
bindable uniform vec4 instance_data[4096]; //our uniform with instances data
out vec3 instance_color;
void main()
{
  vec4 instance_pos = instance_data[gl_InstanceID*2];
  instance_color = instance_data[gl_InstanceID*2+1].xyz;
  gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0);
}

  

Texture Buffer Instancing:

 

buffer创建

tbo_instancing_shader.bind();
//bind to shader as special texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, dips_texture_buffer_tex);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, dips_texture_buffer);
glBindVertexArray(geometry_vao_id);
glDrawElementsInstanced(GL_TRIANGLES, BOX_NUM_INDICES, GL_UNSIGNED_INT, NULL, CURRENT_NUM_INSTANCES);

 

顶点着色器数据访问:

#version 150 core
#extension GL_EXT_bindable_uniform : enable
#extension GL_EXT_gpu_shader4 : enable
in vec3 s_pos;
in vec3 s_normal;
in vec2 s_uv;
uniform mat4 ModelViewProjectionMatrix;
uniform samplerBuffer s_texture_0; //our TBO texture bufer
out vec3 instance_color;
void main()
{
  //sample data from TBO
  vec4 instance_pos = texelFetch(s_texture_0, gl_InstanceID*2);
  instance_color = texelFetch(s_texture_0, gl_InstanceID*2+1).xyz;
  gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0);
}

  

SSBO Instancing:

 

Buffer创建

glGenBuffers(1, ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, INSTANCES_DATA_SIZE, complex_mesh_instances_data[0], GL_STATIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // unbind

 

渲染代码:

//bind ssbo_instances_data, link to shader at 0 binding point
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo_instances_data);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo_instances_data);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
ssbo_instancing_shader.bind();
glBindVertexArray(geometry_vao_id);
glDrawElementsInstanced(GL_TRIANGLES, BOX_NUM_INDICES, GL_UNSIGNED_INT, NULL, CURRENT_NUM_INSTANCES);
glBindVertexArray(0);

 

顶点着色器:

#version 430
#extension GL_ARB_shader_storage_buffer_object : require
in vec3 s_pos;
in vec3 s_normal;
in vec2 s_uv;
uniform mat4 ModelViewProjectionMatrix;
//ssbo should be binded to 0 binding point layout(std430, binding = 0) buffer ssboData { vec4 instance_data[4096]; }; out vec3 instance_color; void main() {   //gl_InstanceID is unique for each instance. So we able to set per instance data   vec4 instance_pos = instance_data[gl_InstanceID*2];   instance_color = instance_data[gl_InstanceID*2+1].xyz;   gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0); }

  

Multi draw indirect:

这是一个非常有用的绘制命令,它可以达到跟instancing一样的效果,一次函数调用,多次dips。它对绘制一批Instance,不同geometry非常有用。

代码实例如下:

//fill indirect buffer with dips information. Just simple array
for (int i = 0; i < CURRENT_NUM_INSTANCES; i++)
{
  multi_draw_indirect_buffer.vertexCount = BOX_NUM_INDICES;
  multi_draw_indirect_buffer.instanceCount = 1;
  multi_draw_indirect_buffer.firstVertex = i*BOX_NUM_INDICES;
  multi_draw_indirect_buffer.baseVertex = 0;
  multi_draw_indirect_buffer.baseInstance = 0;
}
glBindVertexArray(ws_complex_geometry_vao_id);
simple_geometry_shader.bind();
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, (GLvoid*)multi_draw_indirect_buffer[0], //our information about dips
CURRENT_NUM_INSTANCES, //number of dips 0);

glMultiDrawElementsIndirect在一次调用中,进行了多次glDrawElementsInstancedIndirect的绘制,但需要注意的是,这条指令有个很烦人的特点,就是每次glDrawElementsInstancedIndirect绘制对应对立的gl_InstanceID,每一次调用,gl_InstanceID都会从0重新开始。这在实际使用过程中一定要注意。

 

以上就是在实际instancing绘制中,主要会用到的数据组织方式,每一种方式都有自己的使用场景,需要根据实际的项目需要以及平台架构来决定哪种方式的效率最高。

 

引用link:

https://www.gamedev.net/articles/programming/graphics/opengl-api-overhead-r4614/

https://www.g-truc.net/post-0518.html

http://sol.gfxile.net/instancing.html

 

转载于:https://www.cnblogs.com/hellobb/p/8891374.html

OpenGL Instancing是一种批量绘制技术,它可以在一次调用中绘制多个相同的物体,从而提高渲染效率。下面我们将介绍OpenGL Instancing的使用方法。 1. 创建实例数组 在OpenGL中,我们需要创建一个实例数组,它包含了多个实例的属性。例如,我们可以创建一个包含多个立方体位置的实例数组: ```c++ glm::vec3 positions[] = { glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(2.0f, 5.0f, -15.0f), glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f), glm::vec3(2.4f, -0.4f, -3.5f), glm::vec3(-1.7f, 3.0f, -7.5f), glm::vec3(1.3f, -2.0f, -2.5f), glm::vec3(1.5f, 2.0f, -2.5f), glm::vec3(1.5f, 0.2f, -1.5f), glm::vec3(-1.3f, 1.0f, -1.5f) }; unsigned int instanceVBO; glGenBuffers(1, &instanceVBO); glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(positions), &positions, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); ``` 这将创建一个包含10个立方体位置的实例数组,并将其存储在一个VBO中。 2. 更新顶点着色器 接下来,我们需要更新顶点着色器,以便可以访问实例数组的属性。我们可以使用“in”关键字声明一个实例数组属性,并将其设置为顶点着色器的输入变量。 ```c++ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec3 aInstancePos; out vec3 FragPos; out vec3 Normal; out vec3 InstancedPos; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; InstancedPos = aInstancePos; } ``` 在此示例中,我们使用“layout(location = 2)”关键字声明了一个名为“aInstancePos”的实例数组属性,并将其设置为顶点着色器的输入变量。我们还添加了一个名为“InstancedPos”的输出变量,以便将实例数组属性传递到片段着色器。 3. 更新渲染代码 接下来,我们需要更新渲染代码,以便使用实例数组的属性。我们可以使用glVertexAttribPointer函数来指定实例数组属性,并使用glVertexAttribDivisor函数来设置实例数组属性的更新频率。 ```c++ glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(2); glVertexAttribDivisor(2, 1); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); ``` 在此示例中,我们使用glVertexAttribPointer函数指定了实例数组属性,并使用glVertexAttribDivisor函数将其更新频率设置为1。这意味着每次绘制时,实例数组属性都会更新一次。 4. 绘制实例化对象 现在我们可以使用glDrawArraysInstanced函数来绘制多个相同的物体。 ```c++ glBindVertexArray(VAO); glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 10); glBindVertexArray(0); ``` 在此示例中,我们使用glDrawArraysInstanced函数绘制10个相同的立方体,每个立方体由36个顶点组成。 总结 以上就是OpenGL Instancing批量绘制技术的使用方法。通过使用实例数组和glDrawArraysInstanced函数,我们可以在一次调用中绘制多个相同的物体,从而提高渲染效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值