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
OpenGL进阶之Instancing的更多相关文章
- [转]OpenGL进阶(一) - 多视口
直接给出原文链接:OpenGL进阶(一) - 多视口
- OpenGL进阶(十一) - GLSL4.x中的数据传递
in out 对于 vertex shader,每个顶点都会包含一次,它的主要工作时处理关于定点的数据,然后把结果传递到管线的下个阶段. 以前版本的GLSL,数据会通过一些内建变量,比如gl_Vert ...
- OpenGL进阶(十四) - UVN Camera实现
提要 3D游戏中最基本的一个功能就是3D漫游了,玩家可以通过键盘或者鼠标控制自己的视角. 之前我们也学习过一个相关的函数,glLookAt,用来制定摄像机的位置,摄像机观察目标位置,还有摄像机的放置方 ...
- OpenGL进阶演示样例1——动态画线(虚线、实线、颜色、速度等)
用OpenGL动态绘制线段.事实上非常easy,但到如今为止.网上可參考资料并不多. 于是亲自己主动手写一个函数,方便动态绘制线段.代码例如以下: #include<GL/glu ...
- 3D Computer Grapihcs Using OpenGL - 15 Draw Element Instanced
友情提示:继续本节之前,需要保存此前的代码,本节为了试验,会对代码做一些修改,但后续的修改需要我们把代码返回之前的进度. OpenGL内置支持Instancing,有专门的函数来处理这件事情. 为了方 ...
- Android游戏与应用开发最佳学习路线图
Android 游戏与应用开发最佳学习路线图 为了帮助大家更好的学习 Android,并快速入门特此我们为大家制定了以下学习路线图,希望能够帮助大家. 一.路线图概括: 二.具体需要掌握知识点: 三. ...
- 一培训机构设计的学习android课程内容:供大家参考
转自:http://www.cnblogs.com/csj007523/archive/2011/06/16/2082682.html 一培训机构设计的学习android课程内容:供大家参考 第一阶段 ...
- 3D Computer Grapihcs Using OpenGL - 14 OpenGL Instancing
如果我们需要绘制两个(或者多个)一样的立方体(或者物体),只是位置.缩放.旋转不一样,那么我们可以不需要多次将这个物体的顶点信息.颜色信息等发送到显卡,而是发送一次,绘制多次,仅仅是每次绘制之前应用不 ...
- ACM进阶计划
ACM进阶计划ACM队不是为了一场比赛而存在的,为的是队员的整体提高.大学期间,ACM队队员必须要学好的课程有:lC/C++两种语言l高等数学l线性代数l数据结构l离散数学l数据库原理l操作系统原理l ...
随机推荐
- android canvas中rotate()和translate()两个方法详解
rotate()和translate() 1.看到这个题目的时候,有人会觉得这不就是一个对画布的旋转和平移的嘛,但是其中的细节的地方还是需要深究一下的. 例如:有个需求将TextView的文字竖直显示 ...
- 如何在ubuntu下使用samba创建共享
快速简单的创建共享,比网上那些乱七八糟过时的文档强太多 原文地址: https://help.ubuntu.com/community/How%20to%20Create%20a%20Network% ...
- 「TJOI2013」循环格
题目链接 戳我 \(Solution\) 我们观察发现循环格要满足每个点的入度都为\(1\) 证明: 我们假设每个点的入读不一定为\(1\),那么必定有一个或多个点的入度为0,那么则不满足循环格的定义 ...
- 宏定义(无参宏定义和带参宏定义),C语言宏定义详解
1.宏定义说明 宏定义是比较常用的预处理指令,即使用"标识符"来表示"替换列表"中的内容.标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏 ...
- Python的特殊属性和魔法函数
python中有很多以下划线开头和结尾的特殊属性和魔法函数,它们有着很重要的作用. 1.__doc__:说明性文档和信息,python自建,不需要我们定义. # -*- coding:utf- -*- ...
- 842. Split Array into Fibonacci Sequence
Given a string S of digits, such as S = "123456579", we can split it into a Fibonacci-like ...
- 处理json
一.json json是一个字符串,只不过长得比较像字典.使用json函数需要导入json库,即import json json的格式只有双引号,不可用单引号 1.json.loads()和json. ...
- 【spring】InitializingBean接口
apollo 源码中有这么一个类 public class ReleaseMessageScanner implements InitializingBean @Override public voi ...
- 公共子序列(luogu P1439)
传送门 题目描述 给出1-n的两个排列P1和P2,求它们的最长公共子序列. 输入输出格式 输入格式: 第一行是一个数n, 接下来两行,每行为n个数,为自然数1-n的一个排列. 输出格式: 一个数,即最 ...
- django中的setting最佳配置小结
Django settings详解 1.基础 DJANGO_SETTING_MODULE环境变量:让settings模块被包含到python可以找到的目录下,开发情况下不需要,我们通常会在当前文件夹运 ...