多实例渲染

  OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体。

  设想一个场景,比如太空,我们需要渲染数以万记的星球,如果我们使用常规的做法,渲染的过程应该是是:绘制第一个星球glBindVertexArray——glDrawArrays或glDrawElements,然后使用同样的流程绘制其它的星球。但这种方式非常容易达到计算机的性能瓶颈,就算是渲染的物体是最简单的面片,因为在绘制的整个过程中,绘制物体的时间其实非常的短,而渲染物体的准备工作时间是比较长的,即调用glBindVertexArray和glDrawArrays做的工作,如准备顶点数据,指定GPU从哪个缓冲区读取数据,GPU从哪找顶点属性等,而且这些工作都是在CPU到GPU的总线(CPU-GPU bus)上进行的,所以就算是GPU渲染的速度足够快,但调用绘制指令次数过多,就会影响渲染的效率。

  OpenGL的多实例渲染就是针对这种情况出现的。根据上述的情况我们知道要想提高渲染的效率,关键在于减少OpenGL API绘制指令的调用。基于这种思路,我们可以在一次绘制指令中传输尽量多的传输顶点数据,减少绘制指令的调用,即传输一次数据可以绘制多个物体。而这就是OpenGL中多实例渲染的完成的功能。

  OpenGL的多实例渲染最基本的两个渲染API是glDrawArraysInstanced和glDrawElementsInstanced。其它的如glDrawArraysInstancedBaseInstance的API都可以认为是基于这两个API实现的。

  对比以下glDrawArrays和glDrawArraysInstanced:

  void glDrawArrays(GLenum mode, GLint first, GLsizei count);

  void glDrawArrays(GLenum mode, GLint first GLsizei count, GLzsizei primCount);

  glDrawArraysInstanced多了一个primCount的参数,即渲染实例的个数。当OpenGL执行这个函数的时候实际上它会执行glDrawArrays的primCount次拷贝,每次的mode,first,count都是直接传入的。

  下面我们看一个多实例渲染的简单例子:

       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

       glBindVertexArray(vao);
dShader->Use();
glDrawArraysInstanced(GL_TRIANGLES,,,);

  生成顶点数组对象和缓存对象照旧,关键在于glDrawArraysInstanced的调用,在这里我们传入10表示绘制10次实例。

  下面是顶点着色器:

#version  core

layout(location = ) in vec3 iPos;
layout(location = ) in vec3 iColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj; out vec4 fColor; void main()
{
fColor = vec4(iColor,);
vec3 pos = iPos;
pos = pos + vec3(0.1,0.2f,-0.1) * gl_InstanceID;
gl_Position= proj * view * model * vec4(pos,);
}

  这个着色器中关键在于gl_InstanceID这个内置变量,这个内置变量是一个整数,表示当前实例数,它从0开始计数。gl_InstanceID一直存在于顶点着色器中,就算不使用多实例渲染,此时它的值为0。所以在顶点着色器中可使用gl_InstanceID来做索引,引用一些uniform的数组元素。在上面的着色器例子中,我们使用gl_Instanced来对物体的位置进行移动,做一个偏差。当然我们也可以传入一个uniform的数组,使用gl_InstanceID来引用,如

#version  core

layout(location = ) in vec3 iPos;
layout(location = ) in vec3 iColor; uniform mat4 model;
uniform mat4 view;
uniform mat4 proj; uniform vec3 offset[]; out vec3 fColor; void main()
{
fColor = iColor;
vec3 pos = iPos + offset[gl_InstanceID];
gl_Position = proj * view * model * vec4(pos,1.0);
}

  我们声明了一个offset数组,然后在应用程序中使用如下代码为offset数组赋值。

for(int i=;i<;++i)
{
stringstream ss;
ss >> i;
GLint loc = glGetUniformLocation(program,("offset[ "+ ss.str() + "]").c_str());
glUniform2f(loc,offset.x,offset.y);
}

  效果如图:

  

多实例的顶点属性

  在上面的例子中我们使用了offset数组和gl_InstanceID来渲染实例,但这种方法有个问题就是数组的大小非常容易达到uniform数据大小的上限。为此,我们可以使用另一种方法,就是多实例的顶点属性,它和正规的顶点属性是类似的,在顶点着色器中的声明和数据配置方法完全一致。唯一的区别就是顶点属性针对的是单一顶点,而多实例顶点属性针对的是一个图元实例。简单的理解就是顶点着色器的输入正常情况是一个顶点属性对应一个顶点,而所实例的顶点属性是一个属性对以一个图元(图元中所有的顶点的这一条属性共用同一个数据),即每个实例更新一次这个属性的数据。为了实现这个功能,我们需要一个函数:

  glVertexAttribDivisor(GLuint index,GLuint divisor);

  这个函数是用于设置顶点着色器中index索引的顶点属性如何分配值到每一个实例的。divisor表示每divisor个实例更新一次顶点属性。如果divisor的值是0,表示多实例特性被禁用。下面我们用一个顶点着色器的例子来说明;

#version  core

layout(location = ) in vec3 iPos;
layout(location = ) in vec3 iColor; uniform mat4 model;
uniform mat4 view;
uniform mat4 proj; out vec3 fColor; void main()
{
fColor = iColor;
vec3 pos = iPos;
gl_Position = proj * view * model * vec4(pos,1.0);
}

  应用程序调用:

glVertexAttribDivisor(,);

  这个顶点着色器中有一个iColor的属性,索引是1,按正常的顶点属性来理解的话,这个属性每个顶点更新一次。调用glVertexDisivor设置多实例特性后,iColor属性是每个实例(每三个顶点即一个三角形)变换一次。第一个1表示索引,第二个1表示每个实例更新一次iColor数据。

  效果跟上面的一致,但这个时候我们没有使用gl_InstanceID。不过在使用所实例顶点属性的时候有一点要注意,一个顶点属性数据最大等于一个vec4,所以一个mat4会占用多个索引位置,比如layout(location=1) in mat4 m, 这个m会占用1,2,3,4四个位置,使用glUniform4fv的时候也要调用4次。如:

// 顶点缓冲对象
unsigned int buffer;
glGenBuffers(, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[], GL_STATIC_DRAW); for(unsigned int i = ; i < rock.meshes.size(); i++)
{
unsigned int VAO = rock.meshes[i].VAO;
glBindVertexArray(VAO);
// 顶点属性
GLsizei vec4Size = sizeof(glm::vec4);
glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * vec4Size, (void*));
glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * vec4Size, (void*)(vec4Size));
glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * vec4Size, (void*)( * vec4Size));
glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * vec4Size, (void*)( * vec4Size)); glVertexAttribDivisor(, );
glVertexAttribDivisor(, );
glVertexAttribDivisor(, );
glVertexAttribDivisor(, ); glBindVertexArray();
}

  这个段代码参照:https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/10%20Instancing/

  本实践的源代码:https://github.com/xin-lover/opengl-learn/tree/master/chapter-13-geometryshader

Linux OpenGL 实践篇-14-多实例渲染的更多相关文章

  1. Linux OpenGL 实践篇-16 文本绘制

    文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...

  2. Linux OpenGL 实践篇-5 纹理

    纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...

  3. Linux OpenGL 实践篇-4 坐标系统

    OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...

  4. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

  5. Linux OpenGL 实践篇-2 创建一个窗口

    OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...

  6. Linux OpenGL 实践篇-15-图像数据操作

    OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...

  7. Linux OpenGL 实践篇-13-geometryshader

    几何着色器 几何着色器是位于图元装配和片元着色器之前的一个着色器阶段,是一个可选阶段.它的输入是一个图元的完整的顶点信息,通常来自于顶点着色器,但如果细分计算着色器启用的话,那输入则是细分计算着色器的 ...

  8. Linux OpenGL 实践篇-12-procedural-texturing

    程序式纹理 简单的来说程序式纹理就是用数学公式描述物体表面的纹路 .而实现这个过程的着色器我们称之为程序纹理着色器,通常在这类着色器中我们能使用的输入信息也就是顶点坐标和纹理坐标. 程序式纹理的优点 ...

  9. Linux OpenGL 实践篇-11-shadow

    OpenGL 阴影 在三维场景中,为了使场景看起来更加的真实,通常需要为其添加阴影,OpenGL可以使用很多种技术实现阴影,其中有一种非常经典的实现是使用一种叫阴影贴图的实现,在本节中我们将使用阴影贴 ...

随机推荐

  1. CF-807C

    C. Success Rate time limit per test 2 seconds memory limit per test 256 megabytes input standard inp ...

  2. TypeScript完全解读(26课时)_2.TypeScript完全解读-基础类型

    2.TypeScript完全解读-基础类型 src下新建example文件夹并新建文件.basic-type.ts.截图中单词拼错了.后需注意一下是basic-type.ts 可以装tslint的插件 ...

  3. Flutter实战视频-移动电商-04.底部导航栏切换效果

    04.底部导航栏切换效果 博客地址: https://jspang.com/post/FlutterShop.html#toc-291 我们要做的效果图: 新建四个页面 home_page.dart ...

  4. Oracle中的集合运算

    前言:在实际项目中对多表进行集合运算使用非常广泛,以下是对集合操作的部分总结. 一,建表,插入测试数据 create table a( numbers integer ) create table b ...

  5. 436. Find Right Interval

    Given a set of intervals, for each of the interval i, check if there exists an interval j whose star ...

  6. UpdateObjectClient 空白目录有问题

    UpdateObjectClient 如果目标文件夹下面有空白目录的话 有可能造成无法下载所有文件

  7. MySql 5.6 遇到的一些问题

    1. 卸载5.5之后,使用 mysql installer 时,没法设定 data directory .  最后再次删除mysql installer之后,使系统干净后才能重新安装, 然后界面出现了 ...

  8. IT兄弟连 JavaWeb教程 Cookie和Session应用结合使用

    一般对于不要求安全的非敏感数据,建议存储在Cookie中! 对于敏感的数据,占用空间较小的,建议存储在Session中! 对于敏感的,较大的数据,存数据库!

  9. C 语言实例 - 判断元音/辅音

    C 语言实例 - 判断元音/辅音 C 语言实例 C 语言实例 判断输入的字母是元音,还是辅音. 英语有26个字母,元音只包括 a.e.i.o.u 这五个字母,其余的都为辅音.y是半元音.半辅音字母,但 ...

  10. bzoj1458士兵占领

    传送门 和上一题差不多,每行和每列分别看做一个点,障碍点坐标的行和列就不建边,再按照有源汇上下界建图就好了,唯一的区别就是这个题求的是最小流 这个题的数据好水呢,建错图也能A呢 #include< ...