Instancing繪製我想不少童鞋都不陌生,這個技術主要用來快速渲染大量相同的幾何體,能夠大大提升繪製效率。每一個instance在shader中都有一個獨一無二的索引,能夠用來訪問每一個instance對應的渲染參數。使用Instancing技術之因此可以大大提升效率,主要是由於它大大減小了dip(draw indexed primitive)的數量。html
在實際應用中,咱們能夠將全部的渲染參數打包到一個buffer中,將它們一塊兒送到GPU,而後,只須要調用一次繪製命令,就能夠繪製大批不一樣的實體。一樣,若是instance data沒有改變的話(好比咱們明確知道它們是用來繪製靜態物體的),則不須要每一幀都將這些數據往GPU送一次,只須要在程序第一次初始化組織數據時傳輸一次就夠了。api
可能你們比較熟悉的,仍是經過VBO進行Instancing繪製,但在實際的程序編寫和優化過程當中,還有不少buffer能夠用來Instancing,OpenGL也有不少存儲數據的方式,像VBO,UBO,TBO,SSBO等等。下面就詳細講解一下利用不一樣的數據傳輸方式進行Instancing繪製。架構
這種方式是將全部渲染用到的數據都保存在一張紋理當中,而後經過PBO(Pixel Buffer Object)的方式進行數據更新,PBO經過異步的方式實現CPU到GPU之間的數據傳輸,從而不須要阻塞CPU等待數據傳輸完成。app
紋理建立:異步
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);
頂點着色器示例:post
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); }
經過將數據存儲在vbo,而後經過制定不一樣的數據佈局,來制定不一樣的渲染參數。優化
buffer建立:ui
//...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); }
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); }
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); }
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); }
這是一個很是有用的繪製命令,它能夠達到跟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