多实例渲染

  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. POJ - 3253 Fence Repair 优先队列+贪心

    Fence Repair Farmer John wants to repair a small length of the fence around the pasture. He measures ...

  2. E20180525-hm

    sensitive adj. 敏感的; 感觉的; [仪] 灵敏的; 易受影响的; lookup v. 查找; 查表; speedy  adj. 快的,迅速的; 敏捷的 marshal  vt. 整理, ...

  3. [Xcode 实际操作]九、实用进阶-(25)使用Storyboard(故事版)的约束功能,使项目快速适配各种分辨率的设备

    目录:[Swift]Xcode实际操作 本文将演示使用故事版的约束功能,使项目快速适配各种分辨率的设备. 在项目导航区打开并编辑主故事版[Main.storyboard]. 在当前故事版中,已经存在一 ...

  4. 《ERP真的免费不花钱·企业自主实施OdooERP》试读:第十章-仓库条码操作案例

    文/开源智造联合创始人老杨 本文来自<企业自主实施OdooERP>的试读章节.书籍尚未出版,请勿转载.欢迎您反馈阅读意见. 案例背景 各位读者同学,本案例假定读者已经完成了进销存案例练习. ...

  5. spring boot 配置https 报这个错误:java.lang.IllegalArgumentException: Private key must be accompanied by certificate chain

    找了接近半天的时间,原来是那么小的问题 server.ssl.key-store=test.jksserver.ssl.key-store-password=123456server.ssl.key- ...

  6. Zynq7000开发系列-2(VMware与Ubuntu安装使用)

    一.前言 在嵌入式开发中,是无法避免使用Linux系统的,因为在开发之前必须先搭建起交叉编译环境,而后关于Bootloader.Linux Kernel的裁剪移植,File system的制作,底层驱 ...

  7. 洛谷 P2231 [HNOI2002]跳蚤

    https://www.luogu.org/problemnew/show/P2231 题意相当于:有n个位置a[1..n],每个位置可以填[1,m]中任一个整数,问共有多少种填法满足gcd(a[1] ...

  8. python基本的数据类型

    一.python的基本数据类型 int => 整数,主要用来进行数学运算 str ==> 字符串 可以用来保存少量数据并进行相应操作 bool ==> 判断真假,True,False ...

  9. springMVC-接收数据-参数绑定

    接收数据-参数绑定 #Method Arguments概观 Same in Spring WebFlux The table below shows supported controller meth ...

  10. msyql 死锁

    1.使用 show processlist; 查询当前进程; 找到Command 状态是query 并且Time 时间很长的id kill掉即可 2.select * from information ...