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等待数据传输完成。

纹理创建:

  1. glGenBuffers(2, textureInstancingPBO);
  2. glBindBuffer(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[0]);
  3.  
  4. //GL_STREAM_DRAW_ARB means that we will change data every frame
  5. glBufferData(GL_PIXEL_UNPACK_BUFFER, INSTANCES_DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
  6. glBindBuffer(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[1]);
  7. glBufferData(GL_PIXEL_UNPACK_BUFFER, INSTANCES_DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
  8. glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
  9. //create texture where we will store instances data on gpu
  10. glGenTextures(1, textureInstancingDataTex);
  11. glBindTexture(GL_TEXTURE_2D, textureInstancingDataTex);
  12. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  13. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  14. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  15. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  16. 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
  17. //for each object we store PER_INSTANCE_DATA_VECTORS data-vectors. 2 in our case
  18. //GL_RGBA32F, we have float32 data
  19. //complex_mesh_instances_data source data of instances, if we are not going to update data in the texture
  20. 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]);
  21. glBindTexture(GL_TEXTURE_2D, 0);

纹理更新:

  1. glBindTexture(GL_TEXTURE_2D, textureInstancingDataTex);
  2. glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[current_frame_index]); // copy pixels from PBO to texture object
  3. 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
  4. glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, textureInstancingPBO[next_frame_index]);
  5. //http://www.songho.ca/opengl/gl_pbo.html
  6. // Note that glMapBufferARB() causes sync issue.
  7. // If GPU is working with this buffer, glMapBufferARB() will wait(stall)
  8. // until GPU to finish its job. To avoid waiting (idle), you can call
  9. // first glBufferDataARB() with NULL pointer before glMapBufferARB().
  10. // If you do that, the previous data in PBO will be discarded and
  11. // glMapBufferARB() returns a new allocated pointer immediately
  12. // even if GPU is still working with the previous data.
  13. glBufferData(GL_PIXEL_UNPACK_BUFFER, INSTANCES_DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
  14. gpu_data = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY_ARB);
  15. if (gpu_data)
  16. {
  17.   memcpy(gpu_data, complex_mesh_instances_data[0], INSTANCES_DATA_SIZE); // update data
  18.   glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); //release pointer to mapping buffer
  19. }

使用texture instancing渲染:

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

顶点着色器示例:

  1. version 150 core
  2. in vec3 s_pos;
  3. in vec3 s_normal;
  4. in vec2 s_uv;
  5. uniform mat4 ModelViewProjectionMatrix;
  6.  
  7. uniform sampler2D s_texture_0;
  8. out vec2 uv;
  9. out vec3 instance_color;
  10. void main()
  11. {
  12.   const vec2 texel_size = vec2(1.0 / 256.0, 1.0 / 16.0);
  13.   const int objects_per_row = 128;
  14.   const vec2 half_texel = vec2(0.5, 0.5);
  15.   //calc texture coordinates - where our instance data located
  16.   //gl_InstanceID % objects_per_row - index of object in the line
  17.   //multiple by 2 as each object has 2 vectors of data
  18.   //gl_InstanceID / objects_per_row - in what line our data located
  19.   //multiple by texel_size gieves us 0..1 uv to sample from texture from interer texel id
  20.   vec2 texel_uv = (vec2((gl_InstanceID % objects_per_row) * 2, floor(gl_InstanceID / objects_per_row)) + half_texel) * texel_size;
  21.   vec4 instance_pos = textureLod(s_texture_0, texel_uv, 0);
  22.   instance_color = textureLod(s_texture_0, texel_uv + vec2(texel_size.x, 0.0), 0).xyz;
  23.   uv = s_uv;
  24.   gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0);
  25. }

 

vertex buffer Instancing

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

buffer创建:

  1. //...code of base vertex declaration creation
  2. //special atributes binding
  3. 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)
  4. const int per_instance_data_size = sizeof(vec4) * PER_INSTANCE_DATA_VECTORS;
  5. glEnableVertexAttribArray(4);
  6. 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
  7. glVertexAttribDivisor(4, 1);
  8. glEnableVertexAttribArray(5); //5th vertex attribute, has 4 floats, sizeof(vec4) data offset
  9. glVertexAttribPointer((GLuint)5, 4, GL_FLOAT, GL_FALSE, per_instance_data_size, (GLvoid*)(sizeof(vec4)));
  10. //tell that we will change this attribute per instance, not per vertex
  11. glVertexAttribDivisor(5, 1);

渲染:

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

顶点着色器:

  1. #version 150 core
  2. in vec3 s_pos;
  3. in vec3 s_normal;
  4. in vec2 s_uv;
  5. in vec4 s_attribute_3; //some_data;
  6. in vec4 s_attribute_4; //instance pos
  7. in vec4 s_attribute_5; //instance color
  8. uniform mat4 ModelViewProjectionMatrix;
  9. out vec3 instance_color;
  10. void main()
  11. {
  12.   instance_color = s_attribute_5.xyz;
  13.   gl_Position = ModelViewProjectionMatrix * vec4(s_pos + s_attribute_4.xyz, 1.0);
  14. }

  

Uniform Buffer Instancing:

buffer创建:

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

着色器数据访问:

  1. #version 150 core
  2. #extension GL_ARB_bindable_uniform : enable
  3. #extension GL_EXT_gpu_shader4 : enable
  4. in vec3 s_pos;
  5. in vec3 s_normal;
  6. in vec2 s_uv;
  7. uniform mat4 ModelViewProjectionMatrix;
  8. bindable uniform vec4 instance_data[4096]; //our uniform with instances data
  9. out vec3 instance_color;
  10. void main()
  11. {
  12.   vec4 instance_pos = instance_data[gl_InstanceID*2];
  13.   instance_color = instance_data[gl_InstanceID*2+1].xyz;
  14.   gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0);
  15. }

  

Texture Buffer Instancing:

buffer创建:

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

顶点着色器数据访问:

  1. #version 150 core
  2. #extension GL_EXT_bindable_uniform : enable
  3. #extension GL_EXT_gpu_shader4 : enable
  4. in vec3 s_pos;
  5. in vec3 s_normal;
  6. in vec2 s_uv;
  7. uniform mat4 ModelViewProjectionMatrix;
  8. uniform samplerBuffer s_texture_0; //our TBO texture bufer
  9. out vec3 instance_color;
  10. void main()
  11. {
  12.   //sample data from TBO
  13.   vec4 instance_pos = texelFetch(s_texture_0, gl_InstanceID*2);
  14.   instance_color = texelFetch(s_texture_0, gl_InstanceID*2+1).xyz;
  15.   gl_Position = ModelViewProjectionMatrix * vec4(s_pos + instance_pos.xyz, 1.0);
  16. }

  

SSBO Instancing:

Buffer创建

  1. glGenBuffers(1, ssbo);
  2. glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
  3. glBufferData(GL_SHADER_STORAGE_BUFFER, INSTANCES_DATA_SIZE, complex_mesh_instances_data[0], GL_STATIC_DRAW);
  4. glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
  5. glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // unbind

渲染代码:

  1. //bind ssbo_instances_data, link to shader at 0 binding point
  2. glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo_instances_data);
  3. glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo_instances_data);
  4. glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
  5. ssbo_instancing_shader.bind();
  6. glBindVertexArray(geometry_vao_id);
  7. glDrawElementsInstanced(GL_TRIANGLES, BOX_NUM_INDICES, GL_UNSIGNED_INT, NULL, CURRENT_NUM_INSTANCES);
  8. glBindVertexArray(0);

顶点着色器:

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

  

Multi draw indirect:

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

代码实例如下:

  1. //fill indirect buffer with dips information. Just simple array
  2. for (int i = 0; i < CURRENT_NUM_INSTANCES; i++)
  3. {
  4.   multi_draw_indirect_buffer.vertexCount = BOX_NUM_INDICES;
  5.   multi_draw_indirect_buffer.instanceCount = 1;
  6.   multi_draw_indirect_buffer.firstVertex = i*BOX_NUM_INDICES;
  7.   multi_draw_indirect_buffer.baseVertex = 0;
  8.   multi_draw_indirect_buffer.baseInstance = 0;
  9. }
  10. glBindVertexArray(ws_complex_geometry_vao_id);
  11. simple_geometry_shader.bind();
  12. glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, (GLvoid*)multi_draw_indirect_buffer[0], //our information about dips
  13. 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

OpenGL进阶之Instancing的更多相关文章

  1. [转]OpenGL进阶(一) - 多视口

    直接给出原文链接:OpenGL进阶(一) - 多视口

  2. OpenGL进阶(十一) - GLSL4.x中的数据传递

    in out 对于 vertex shader,每个顶点都会包含一次,它的主要工作时处理关于定点的数据,然后把结果传递到管线的下个阶段. 以前版本的GLSL,数据会通过一些内建变量,比如gl_Vert ...

  3. OpenGL进阶(十四) - UVN Camera实现

    提要 3D游戏中最基本的一个功能就是3D漫游了,玩家可以通过键盘或者鼠标控制自己的视角. 之前我们也学习过一个相关的函数,glLookAt,用来制定摄像机的位置,摄像机观察目标位置,还有摄像机的放置方 ...

  4. OpenGL进阶演示样例1——动态画线(虚线、实线、颜色、速度等)

            用OpenGL动态绘制线段.事实上非常easy,但到如今为止.网上可參考资料并不多. 于是亲自己主动手写一个函数,方便动态绘制线段.代码例如以下: #include<GL/glu ...

  5. 3D Computer Grapihcs Using OpenGL - 15 Draw Element Instanced

    友情提示:继续本节之前,需要保存此前的代码,本节为了试验,会对代码做一些修改,但后续的修改需要我们把代码返回之前的进度. OpenGL内置支持Instancing,有专门的函数来处理这件事情. 为了方 ...

  6. Android游戏与应用开发最佳学习路线图

    Android 游戏与应用开发最佳学习路线图 为了帮助大家更好的学习 Android,并快速入门特此我们为大家制定了以下学习路线图,希望能够帮助大家. 一.路线图概括: 二.具体需要掌握知识点: 三. ...

  7. 一培训机构设计的学习android课程内容:供大家参考

    转自:http://www.cnblogs.com/csj007523/archive/2011/06/16/2082682.html 一培训机构设计的学习android课程内容:供大家参考 第一阶段 ...

  8. 3D Computer Grapihcs Using OpenGL - 14 OpenGL Instancing

    如果我们需要绘制两个(或者多个)一样的立方体(或者物体),只是位置.缩放.旋转不一样,那么我们可以不需要多次将这个物体的顶点信息.颜色信息等发送到显卡,而是发送一次,绘制多次,仅仅是每次绘制之前应用不 ...

  9. ACM进阶计划

    ACM进阶计划ACM队不是为了一场比赛而存在的,为的是队员的整体提高.大学期间,ACM队队员必须要学好的课程有:lC/C++两种语言l高等数学l线性代数l数据结构l离散数学l数据库原理l操作系统原理l ...

随机推荐

  1. android canvas中rotate()和translate()两个方法详解

    rotate()和translate() 1.看到这个题目的时候,有人会觉得这不就是一个对画布的旋转和平移的嘛,但是其中的细节的地方还是需要深究一下的. 例如:有个需求将TextView的文字竖直显示 ...

  2. 如何在ubuntu下使用samba创建共享

    快速简单的创建共享,比网上那些乱七八糟过时的文档强太多 原文地址: https://help.ubuntu.com/community/How%20to%20Create%20a%20Network% ...

  3. 「TJOI2013」循环格

    题目链接 戳我 \(Solution\) 我们观察发现循环格要满足每个点的入度都为\(1\) 证明: 我们假设每个点的入读不一定为\(1\),那么必定有一个或多个点的入度为0,那么则不满足循环格的定义 ...

  4. 宏定义(无参宏定义和带参宏定义),C语言宏定义详解

    1.宏定义说明 宏定义是比较常用的预处理指令,即使用"标识符"来表示"替换列表"中的内容.标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏 ...

  5. Python的特殊属性和魔法函数

    python中有很多以下划线开头和结尾的特殊属性和魔法函数,它们有着很重要的作用. 1.__doc__:说明性文档和信息,python自建,不需要我们定义. # -*- coding:utf- -*- ...

  6. 842. Split Array into Fibonacci Sequence

    Given a string S of digits, such as S = "123456579", we can split it into a Fibonacci-like ...

  7. 处理json

    一.json json是一个字符串,只不过长得比较像字典.使用json函数需要导入json库,即import json json的格式只有双引号,不可用单引号 1.json.loads()和json. ...

  8. 【spring】InitializingBean接口

    apollo 源码中有这么一个类 public class ReleaseMessageScanner implements InitializingBean @Override public voi ...

  9. 公共子序列(luogu P1439)

    传送门 题目描述 给出1-n的两个排列P1和P2,求它们的最长公共子序列. 输入输出格式 输入格式: 第一行是一个数n, 接下来两行,每行为n个数,为自然数1-n的一个排列. 输出格式: 一个数,即最 ...

  10. django中的setting最佳配置小结

    Django settings详解 1.基础 DJANGO_SETTING_MODULE环境变量:让settings模块被包含到python可以找到的目录下,开发情况下不需要,我们通常会在当前文件夹运 ...