OpenGL 实例化

1.简介

假设你有一个绘制了很多模型的场景,而大部分的模型包含的是同一组顶点数据,只不过进行的是不同的世界空间变换。想象一个充满草的场景:每根草都是一个包含几个三角形的小模型。你可能会需要绘制很多根草,最终在每帧中你可能会需要渲染上千或者上万根草。因为每一根草仅仅是由几个三角形构成,渲染几乎是瞬间完成的,但上千个渲染函数调用却会极大地影响性能。

如果像这样绘制模型的大量实例(Instance),很快就会因为绘制调用过多而达到性能瓶颈。

如果我们能够将数据一次性发送给GPU,然后使用一个绘制函数让OpenGL利用这些数据绘制多个物体,就会更方便了。这就是实例化(Instancing)。

实例化这项技术能够让我们使用一个渲染调用来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信,它只需要一次即可。如果想使用实例化渲染,我们只需要将glDrawArrays和glDrawElements的渲染调用分别改为glDrawArraysInstanced和glDrawElementsInstanced就可以了。

GLSL在顶点着色器中嵌入了另一个内建变量,gl_InstanceID。

在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1。比如说,我们正在渲染第43个实例,那么顶点着色器中它的gl_InstanceID将会是42。因为每个实例都有唯一的ID,我们可以建立一个数组,将ID与位置值对应起来,将每个实例放置在世界的不同位置。

2.示例

 OpenGL 实例化_第1张图片

//顶点着色器
#version 330 core 
layout (location = 0) in vec3 aPos; 
layout (location = 1) in vec3 aNormal;
uniform vec2 offsets[100];

out vec3 Color;
void main() { 
    Color=aNormal;
    vec2 offset = offsets[gl_InstanceID];
    gl_Position = vec4(aPos, 1.0)+vec4(offset,0.0,0.0);
} 


//片段着色器
#version 330 core

out vec4 FragColor;
in vec3 Color;

void main() {
    FragColor = vec4(Color, 1.0);
}

赋值数据:位置偏移量

 QVector2D 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) {
            QVector2D translation;
            translation.setX((float)x / 10.0f + offset);
            translation.setY((float)y / 10.0f + offset);
            translations[index++] = translation;
        }
    }
    m_ShaderProgram.bind();
    for(unsigned int i = 0; i < 100; i++) {
        std::string str="offsets[" + std::to_string(i) + "]";
        m_ShaderProgram.setUniformValue(str.c_str(), translations[i]);
    }

渲染:

    m_glFuns->glBindVertexArray(VAO);
    m_glFuns->glDrawArraysInstanced(GL_TRIANGLES, 0, vertices.size(),100);

3.实例化数组

虽然之前的实现在目前的情况下能够正常工作,但是如果我们要渲染远超过100个实例的时候(这其实非常普遍),我们最终会超过最大能够发送至着色器的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;
}

我们不再使用gl_InstanceID,现在不需要索引一个uniform数组就能够直接使用offset属性了。

因为实例化数组和position与color变量一样,都是顶点属性,我们还需要将它的内容存在顶点缓冲对象中,并且配置它的属性指针。

    unsigned int instanceVBO;
    m_glFuns->glGenBuffers(1, &instanceVBO);
    m_glFuns->glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    m_glFuns->glBufferData(GL_ARRAY_BUFFER, sizeof(QVector2D) * 100, &translations[0], GL_STATIC_DRAW);
    m_glFuns->glBindBuffer(GL_ARRAY_BUFFER, 0);

之后我们还需要设置它的顶点属性指针,并启用顶点属性:

    m_glFuns->glEnableVertexAttribArray(2);
    m_glFuns->glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    m_glFuns->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
    m_glFuns->glBindBuffer(GL_ARRAY_BUFFER, 0);
    m_glFuns->glVertexAttribDivisor(2, 1);

我们调用了glVertexAttribDivisor。这个函数告诉了OpenGL该什么时候更新顶点属性的内容至新一组数据。它的第一个参数是需要的顶点属性;第二个参数是属性除数(Attribute Divisor),设置为1时,我们告诉OpenGL我们希望在渲染一个新实例的时候更新顶点属性。

现在使用glDrawArraysInstanced,再次渲染四边形,

m_glFuns->glDrawArraysInstanced(GL_TRIANGLES, 0, vertices.size(),100);

你可能感兴趣的:(OpenGL,开发语言,opengl,qt,实例化)