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 ...
随机推荐
- mvc - view传值到js
http://www.cnblogs.com/akwwl/p/5238975.html
- spring @Async 线程池使用
最近公司项目正逐渐从dubbo向springCloud转型,在本次新开发的需求中,全部使用springcloud进行,在使用时线程池,考虑使用spring封装的线程池,现将本次使用心得及内容记录下来 ...
- 一、认识Node.js
1.什么是Note.js? 简单的说 Node.js 就是运行在服务端的 JavaScript.Node.js 是一个 Javascript 运行环境(runtime).它让 JavaScript 可 ...
- c++类 初始化另一对象
Cbox类中对象a 可以直接赋值给对象b,无论类中数据成员是私有还是共有.且在创建a时调用了一次构造函数,b调用的是另外的默认构造函数: #include<iostream> using ...
- yunpian-python-sdk
快速开始 安装SDK pip install yunpian-python-sdk 代码 使用YunpianClient from yunpian_python_sdk.model import co ...
- 【BZOJ3589】动态树 树链剖分+线段树
Description 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0: 这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1: 小明希望你 ...
- loj #2508. 「AHOI / HNOI2018」游戏
#2508. 「AHOI / HNOI2018」游戏 题目描述 一次小 G 和小 H 在玩寻宝游戏,有 nnn 个房间排成一列,编号为 1,2,…,n,相邻房间之间都有 111 道门.其中一部分门上有 ...
- P3235 [HNOI2014]江南乐
$ \color{#0066ff}{ 题目描述 }$ 小A是一个名副其实的狂热的回合制游戏玩家.在获得了许多回合制游戏的世界级奖项之后,小A有一天突然想起了他小时候在江南玩过的一个回合制游戏. 游戏的 ...
- express + vue 项目搭建
最近建了一个node服务端加vue前端的项目 安装node :npm install node 安装express :npm install express -g (-g全局安装) 构建express ...
- 加快npm包安装的方法
一直以来都感觉使用npm安装包的速度特别的慢,但是由于npm上面的包比较齐全,所以一直在忍受这种安装依赖包的折磨. 不过这种折磨,到今天为止应该是可以结束了,在知乎看到一个这样的说法: 用npmins ...