基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化

 

Vries的原教程地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/10%20Instancing/ 关于实例化的详细设置介绍与设置与参数设置请查看这个教程,本篇旨在对Vires基于visual studio平台的编程思想与代码做Qt平台的移植,重在记录自身学习之用)

在Vires的教程中,实例化的顺序比较靠后,但这章有一部分的内容与obj模型相关且有趣,故放在“十六”。

 

一,实例化

  举一个小栗子,最近在玩荒野行动时,场景建模中会有很多草,这些草只是普通的2D纹理贴图,成千上万地分布在场景中。

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第1张图片基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第2张图片

   

  如果使用伪代码描述,大多数人会这样写,修改草的位置,绘图一万遍:

for(int i = 0; i != 10000; ++i){
  grassShader.setQMatrix4x4(model);//在着色器中修改草的model矩阵位置
  grass.draw();//绘图;
}

  但整个draw()的过程,是CPU与GPU数据交互的过程,CPU指定数据缓存buffer,GPU从缓存里找到指定顶点位置,读取数据,这是一个很费时的动作,几十次还可以,如果是数十万级数的操作,无疑很消耗计算机资源。至此实例化应运而生,一次将成千上万的参数数据打包从CPU传往GPU,大大节省了计算时间。

二,实例化示例

  这一张2D纹理放在100个不同的位置上,使用实例化思想,在上节“十五”代码的基础上进行修改,源代码连接在上节教程中。

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第3张图片基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第4张图片

顶点着色器:gl_InstanceID是一个有趣的内置变量,当决定使用实例化glDrawArraysInstanced()进行绘图时,gl_Instanced从0开始,每绘制一个实例,就+1递增,比如,在绘制第25个实例时,顶点着色器中,gl_Instanced的值为24。

#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.0f, 1.0f);
  fColor = aColor;
}

片段着色器:


#version 330 core
out vec4 FragColor;

in vec3 fColor;

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

矩形类.cpp

#include "light.h"

Light::Light(){
  core = QOpenGLContext::currentContext()->versionFunctions();
}

Light::~Light(){
  core->glDeleteBuffers(1, &lightVBO);
}

void Light::init(){

  float lightVertices[] = {
    // 位置          // 颜色
    -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
  };

  core->glGenBuffers(1, &lightVBO);

  core->glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
  core->glBufferData(GL_ARRAY_BUFFER, sizeof(lightVertices), lightVertices, GL_STATIC_DRAW);
}

void Light::drawLight(){
  core->glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
  core->glEnableVertexAttribArray(0);
  core->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
  core->glEnableVertexAttribArray(1);
  core->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));

  core->glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100); //100表示实例化100个 矩形
}

在oglmanager类中,设置着色器中矩形要偏移的位置参数

 ...............

  ResourceManager::loadShader("light", ":/shaders/res/shaders/light.vert", ":/shaders/res/shaders/light.frag");

  QVector2D translations[100]; //vries的数据,直接拿来用
  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;
    }
  }

  for(GLuint i = 0; i < 100; i++){
    QString index;
    ResourceManager::getShader("light").use().setVector2f("offsets["+index.setNum(i)+"]", translations[i]);
  }

..................

三,实例化数组

  gl_Instanced很好用,但如果继续在顶点着色器中使用uniform作为矩形位置的参数类型,会出现uniform数值大小的限制问题,这个链接,数值限制解释了uniform类型的变量数组的最大值,我英语是一个二把刀,依稀看懂是说OpenGL3.0以上的版本,最大值至少是1024。因为实例化的数量经常成千上万,所以出现了实例化数组,即将偏移量设置为顶点属性,以解除最大值的限制。

  修改顶点着色器,去除uniform offsets[100]变量,改为顶点属性:


#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.0f, 1.0f);
  fColor = aColor;
}

 有了顶点属性,就要绑定buffer,将偏移量赋值,还是修改light.cpp:

#include "light.h"
#include 

Light::Light(){
  core = QOpenGLContext::currentContext()->versionFunctions();
}

Light::~Light(){
  core->glDeleteBuffers(1, &lightVBO);
}

void Light::init(){
  /************* 矩形 buffer ***************/
  float lightVertices[] = {
    // 位置          // 颜色
    -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
  };

  core->glGenBuffers(1, &lightVBO);

  core->glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
  core->glBufferData(GL_ARRAY_BUFFER, sizeof(lightVertices), lightVertices, GL_STATIC_DRAW);

  /************* 偏移量offset buffer ***************/
  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;
    }
  }
  core->glGenBuffers(1, &instanceVBO);

  core->glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
  core->glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*100, &translations[0], GL_STATIC_DRAW);
}

void Light::drawLight(){
  /************* 矩形 指定顶点属性 ***************/
  core->glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
  core->glEnableVertexAttribArray(0);
  core->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
  core->glEnableVertexAttribArray(1);
  core->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));

  /************* 偏移量offset 指定顶点属性 ***************/
  core->glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
  core->glEnableVertexAttribArray(2);
  core->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
  core->glVertexAttribDivisor(2, 1);

  core->glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);
}

  这里有一个新的函数glVertexAttribDivisor(GLuint index, GLuint divisor),index表示顶点着色器中要实例化的顶点属性所在的索引,divisor为1表示每实例化一个矩形就更新顶点属性,如果是3表示每实例化3个矩形才更新,再次渲染,得到的结果还是这个画面:

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第5张图片

  当然,虽然使用了实例化数组,gl_InstanceID这个好用的变量还是可以用的,比如以下这个效果,只要稍微修改顶点着色器。

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第6张图片

顶点着色器:


#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(){
  vec2 pos  = aPos * (gl_InstanceID / 100.0);
  gl_Position = vec4(pos + aOffset, 0.0f, 1.0f);
  fColor = aColor;
}

四,小行星带

  4.1 未采用实例化

   一个小行星加三百个陨石,均是obj模型,顺便吐槽依据一句,五年前的Lenovo Y410P真的老了,1000个陨石跑起来有些吃力。言归正传,这是未使用“实例化”处理的300个陨石模型,使用的同一陨石模型,分布在不同的地方,绘图时,提前计算陨石的位置,for循环300次,绘出图形。

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第7张图片

提前处理小行星的一个与陨石带的300个model矩阵,如下图所示,数据均采用Vries的原数据:

  /*************  小行星 shader参数 *************/ 
  planetModelMatrix.translate(0.0f, -3.0f, 0.0f);
  planetModelMatrix.scale(4.0f);

  /*************  陨石带 shader参数 *************/
  rockModelsMatrix = new QMatrix4x4[ROCK_NUMBER]; //ROCK_NUM=300
  GLfloat radius = 50.0;
  GLfloat offset = 2.5f;
  for(GLuint i = 0; i < ROCK_NUMBER; i++){
    QMatrix4x4 model;
    // 1. 位移:分布在半径为 'radius' 的圆形上,偏移的范围是 [-offset, offset]
    GLfloat angle = (GLfloat)i / (GLfloat)ROCK_NUMBER * 360.0f;
    GLfloat displacement = (qrand() % (GLint)(2 * offset * 100)) / 100.0f - offset;
    GLfloat x = sin(angle) * radius + displacement;
    displacement = (qrand() % (GLint)(2 * offset * 100)) / 100.0f - offset;
    GLfloat y = displacement * 0.4f; // 让行星带的高度比x和z的宽度要小
    displacement = (qrand() % (GLint)(2 * offset * 100)) / 100.0f - offset;
    GLfloat z = cos(angle) * radius + displacement;
    model.translate(x, y, z);

    // 2. 缩放:在 0.05 和 0.25f 之间缩放
    GLfloat scale = (qrand() % 20) / 100.0f + 0.05;
    model.scale(scale);

    // 3. 旋转:绕着一个(半)随机选择的旋转轴向量进行随机的旋转
    GLfloat rotAngle = (qrand() % 360);
    model.rotate(rotAngle, QVector3D(0.4f, 0.6f, 0.8f));

    // 4. 添加到矩阵的数组中
    rockModelsMatrix[i] = model;
  }

在绘图函数paintGL()中,修改着色器的model矩阵,绘出图形。

  /*********  绘制小行星 ************/
  ResourceManager::getShader("model").use().setMatrix4f("model", planetModelMatrix);
  planetModel->draw(this->isOpenLighting);

  /*********  绘制陨石带 ************/
  for(GLuint i = 0; i < ROCK_NUMBER; i++){
    ResourceManager::getShader("model").use().setMatrix4f("model", rockModelsMatrix[i]);
    rockModel->draw(this->isOpenLighting);
  }

4.2 采用实例化处理模型

  下图是采用“实例化”处理后的50000个陨石,总共近2450万个顶点,运行起来一点也不卡!摄像机的移动和视角的拖拽非常流畅,这就是“实例化”的妙用。

代码链接如下:

百度网盘链接:https://pan.baidu.com/s/1fzIZudhm3YmvW56YupJtSw 密码:assf

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十六)实例化_第8张图片

修改陨石带的顶点着色器,替换model矩阵。


#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTex;
layout (location = 2) in vec3 aNormal;
layout (location = 3) in mat4 aInstanceModelMatrix;

//uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec2 TexCoords;
out vec3 FragPos;
out vec3 Normal;


void main(){
  gl_Position = projection * view * aInstanceModelMatrix * vec4(aPos, 1.0f);
  FragPos = vec3(aInstanceModelMatrix * vec4(aPos, 1.0f));
  Normal = mat3(transpose(inverse(aInstanceModelMatrix))) * aNormal;
  TexCoords = aTex;
}

 

你可能感兴趣的:(现代OpenGL学习教程)