OpenGl L22实例化

一、实例化

当我们渲染多个相同的物体时,会给性能带来很大的开销。因为我们在渲染之前,会绑定缓冲,设置顶点属性等一系列操作。而这些都是在相对缓慢的CPU到GPU总线上进行的。
而如果我们将数据一次性的发送给GPU,然后利用绘制函数让OpenGL利用这些数据去绘制多个物体,就方便了,这个过程叫做实例化
实例化这项技术能够让我们使用一个渲染调用来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信,它只需要一次即可。如果想使用实例化渲染,我们只需要将glDrawArraysglDrawElements的渲染调用分别改为glDrawArraysInstancedglDrawElementsInstanced就可以了。
这些渲染函数的实例化版本需要一个额外的参数,叫做实例数量(Instance Count),它能够设置我们需要渲染的实例个数。
这样我们只需要将必须的数据发送到GPU一次,然后使用一次函数调用告诉GPU它应该如何绘制这些实例。GPU将会直接渲染这些实例,而不用不断地与CPU进行通信。
然而,这样渲染出来的结果是完全相同的,并且还在同一个位置,所以我们只能看到一个物体,于是我们在顶点着色器中嵌入了另一个内建变量,gl_InstanceID
在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1。比如说,我们正在渲染第43个实例,那么顶点着色器中它的gl_InstanceID将会是42。因为每个实例都有唯一的ID,我们可以建立一个数组,将ID与位置值对应起来,将每个实例放置在世界的不同位置。

如下:我们在标准化设备坐标系中使用一个渲染调用,绘制100个2D四边形。我们会索引一个包含100个偏移向量的uniform数组,将偏移值加到每个实例化的四边形上。最终的结果是一个排列整齐的四边形网格:
在这里插入图片描述

  • 每个四边形由2个三角形所组成,一共有6个顶点。每个顶点包含一个2D的标准化设备坐标位置向量和一个颜色向量。
float quadVertices[] = {
    // 位置          // 颜色
    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,
    -0.05f, -0.05f,  0.0f, 0.0f, 1.0f,

    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,   
     0.05f,  0.05f,  0.0f, 1.0f, 1.0f                   
};  
  • 片段着色器从顶点着色器中接受颜色变量,设置为颜色输出,实现四边形的颜色。
#version 330 core
out vec4 FragColor;

in vec3 fColor;

void main()
{
    FragColor = vec4(fColor, 1.0);
}
  • 在顶点着色器中,我们利用gl_InstanceID来索引包含偏移量的offset数组,获取每个实例的偏移变量。
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fColor;

uniform vec2 offsets[100];

void main()
{
    vec2 offset = offsets[gl_InstanceID];
    gl_Position = vec4(aPos + offset, 0.0, 1.0);
    fColor = aColor;
}
  • offset数组值由我们预先计算得出
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;
    }
}
  • 然后我们再将预先计算的偏移量赋给uniform数组offset
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]);
}
  • 实例化渲染
glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);

二、实例化数组

当我们的实例化对象很多时,可能会超过最大能够发送至着色器的uniform数据大小上限,于是,我们可以采用实例化数组,被定义为一个顶点属性中,在顶点着色器渲染一个新的实例的时候更新。

于是,我们将顶点属性定义为一个实例化数组后,顶点着色器只需要对每一个实例进行更新,而不是对顶点进行属性的更新。

  • 将偏移量uniform数组设置为一个实例化数组,并添加顶点属性。
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;

out vec3 fColor;

void main()
{
    gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
    fColor = aColor;
}
  • 由于此时实例化数组定义为了顶点属性,所以我们需要将它的内容存在顶点缓冲对象中,并配置属性指针,然后存在一个新的缓冲对象中:
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);
  • 设置顶点属性指针,并启用顶点属性
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);   
glVertexAttribDivisor(2, 1);

其中,glVertexAttribDivisor定义了什么时候更新顶点属性到新一组数据,第一个参数定义了所需要的顶点属性,第二个参数是属性除数(默认是0):在顶点着色器每次迭代时更新;为1时在渲染一个新实例的时候更新顶点属性;为2时表示每2个实例更新一次属性。于是,我们再次得到我们的渲染结果:
在这里插入图片描述

  • 我们还可以修改每个实例的大小
void main()
{
    vec2 pos = aPos * (gl_InstanceID / 100.0);
    gl_Position = vec4(pos + aOffset, 0.0, 1.0);
    fColor = aColor;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值