当前位置:网站首页>第二十二课,实例化(instancing)
第二十二课,实例化(instancing)
2022-07-31 09:08:00 【Elsa的迷弟】
概述
对于拥有同一模型数据的物体(例如:草),每一帧需要渲染成百上千的实例,这些渲染几乎可以瞬间完成,但是上千个渲染函数的调用却会造成极大的性能影响。
glDrawArrays或glDrawElements函数告诉GPU去绘制你的顶点数据会消耗更多的性能,因为OpenGL在绘制顶点数据之前需要做很多准备工作(比如告诉GPU该从哪个缓冲读取数据,从哪寻找顶点属性,而且这些都是在相对缓慢的CPU到GPU总线(CPU to GPU Bus)上进行的)。所以,即便渲染顶点非常快,命令GPU去渲染却未必。
如果我们能够将数据一次性发送给GPU,然后使用一个绘制函数让OpenGL利用这些数据绘制多个物体,就会更方便了。这就是实例化(Instancing)。
实例化
1. 载入实例化对象
加载模型 或 自定义数据
2. 设置实例化对象数量以及各实例位置信息
每一个实例都需要设置一个独有的变换,因为如果每一个实例都渲染在同一个位置,以同样的方式,那么实例渲染将毫无意义。
故需要一个偏移量或model矩阵的数组,来设置每一个实例的变化。
例如:
- 二维空间平移变换
glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for(int y = -10; y < 10; y += 2)
{
for(int x = -10; x < 10; x += 2)
{
glm::vec2 translation;
translation.x = (float)x / 10.0f + offset;
translation.y = (float)y / 10.0f + offset;
translations[index++] = translation;
}
}
- 三维空间变换
unsigned int amount = 1000;
glm::mat4 *modelMatrices;
modelMatrices = new glm::mat4[amount];
srand(glfwGetTime()); // 初始化随机种子
float radius = 50.0;
float offset = 2.5f;
for(unsigned int i = 0; i < amount; i++)
{
glm::mat4 model;
// 1. 位移:分布在半径为 'radius' 的圆形上,偏移的范围是 [-offset, offset]
float angle = (float)i / (float)amount * 360.0f;
float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float x = sin(angle) * radius + displacement;
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float y = displacement * 0.4f; // 让行星带的高度比x和z的宽度要小
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float z = cos(angle) * radius + displacement;
model = glm::translate(model, glm::vec3(x, y, z));
// 2. 缩放:在 0.05 和 0.25f 之间缩放
float scale = (rand() % 20) / 100.0f + 0.05;
model = glm::scale(model, glm::vec3(scale));
// 3. 旋转:绕着一个(半)随机选择的旋转轴向量进行随机的旋转
float rotAngle = (rand() % 360);
model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));
// 4. 添加到矩阵的数组中
modelMatrices[i] = model;
}
3. 向shader传参,设置顶点属性
使用实例化位置信息有两种方式
第一种,使用Uniform传参
但GPU提供给Uniform的数据大小有上限,如果要渲染远超过100个实例的时候,便不能组成工作。替代它的方式是使用实例化数组(就是使用VBO)。
shader.use();
for(unsigned int i = 0; i < 100; i++)
{
stringstream ss;
string index;
ss << i;
index = ss.str();
shader.setVec2(("offsets[" + index + "]").c_str(), translations[i]);
}
疑问:使用VBO不会造成GPU内存不够,猜测Uniform和VBO存储在不同的分区。
第二种,使用实例化数组
(1)将数据绑定到VBO
unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
或
// 顶点缓冲对象
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);
(2)绑定数组信息(一个VAO绑定多个VBO)
注:在模型导入时,已经将模型数据导入到0,1,2号结点,故,在这里只需要将模型变换矩阵绑定。
然而,当我们顶点属性的类型大于vec4时,就要多进行一步处理了。顶点属性最大允许的数据大小等于一个vec4。因为一个mat4本质上是4个vec4,我们需要为这个矩阵预留4个顶点属性。因为我们将它的位置值设置为3,矩阵每一列的顶点属性位置值就是3、4、5和6。
glVertexAttribDivisor(顶点属性号,每隔几个实例走一步)。
因为对于一个实例,需要使用模型VBO中的所有数据,而对于每一个实例,只需要使用模型变换中的一个模型矩阵。故使用glVertexAttribDivisor函数来设置。
- 当第二个参数实例数据的步长为
0(默认位0),及对于一个实例中的每一个顶点使用不同的模型矩阵。 - 当第二个参数实例数据的步长为
1时,对于一个实例中的每一个顶点都使用相同的模型矩阵。 - 当第二个参数实例数据的步长为
2时,每2个实例更新一次属性(模型矩阵) - 依次类推
for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
unsigned int VAO = rock.meshes[i].VAO;
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 顶点属性
GLsizei vec4Size = sizeof(glm::vec4);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size));
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));
glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
glBindVertexArray(0);
}
4. shader编写
vert
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 instanceMatrix;
out vec2 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0);
TexCoords = aTexCoords;
}
frag
#version 330 core
out vec4 FragColor;
in vec3 fColor;
void main()
{
FragColor = vec4(fColor, 1.0);
}
Draw
glDrawElementsInstanced多了一个参数amount表示实例化数量
// 绘制小行星
instanceShader.use();
for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
glBindVertexArray(rock.meshes[i].VAO);
glDrawElementsInstanced(
GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount
);
}
运行结果

边栏推荐
- @RequestBody和@RequestParam区别
- Modular specifications
- MySQL----多表查询
- 【MySQL中auto_increment有什么作用?】
- 第八章 、接口
- Define event types in Splunk Web
- MySQL 排序
- [MySQL exercises] Chapter 5 · SQL single table query
- 服务器上解压文件时提示“gzip: stdin: not in gzip format,tar: Child returned status 1,tar: Error is not recovera“
- Browser usage ratio js radar chart
猜你喜欢

刷题《剑指Offer》day07

Come n times - 06. Print the linked list from end to end

科目三:前方路口直行

js radar chart statistical chart plugin

js department budget and expenditure radar chart
![[MySQL exercises] Chapter 5 · SQL single table query](/img/11/66b4908ed8f253d599942f35bde96a.png)
[MySQL exercises] Chapter 5 · SQL single table query

2019 NeurIPS | Graph Convolutional Policy Network for Goal-Directed Molecular Graph Generation

How to Install MySQL on Linux

【MySQL功法】第3话 · MySQL中常见的数据类型

来n遍剑指--06. 从尾到头打印链表
随机推荐
js部门预算和支出雷达图
C# 正则表达式汇总
SQL statement knowledge
MySQL 高级(进阶) SQL 语句 (一)
postgresql 范围查询比索引查询快吗?
MySQL 操作语句大全(详细)
Aleo Testnet3规划大纲
ecshop安装的时候提示不支持JPEG格式
【软考软件评测师】2012综合知识历年真题
matlab常用符号用法总结
Cloud server deployment web project
JSP exception对象简介说明
Splunk Workflow action 给我们带来的好处
5.for in 和 for of区别和使用
Define event types in Splunk Web
六、MFC文档类(单文档和多文档)
JS中原型和原型链的详细讲解(附代码示例)以及 new关键字具体做了什么的详细讲解
MySQL 排序
来n遍剑指--06. 从尾到头打印链表
多个js雷达图同时显示