如何在 Qt 5 中使用带有实例化的 VAO

Posted

技术标签:

【中文标题】如何在 Qt 5 中使用带有实例化的 VAO【英文标题】:How to use VAOs with instancing in Qt 5 【发布时间】:2014-05-19 07:27:15 【问题描述】:

我正试图围绕如何正确使用 VAO 进行实例化渲染(特别是在 Qt 5.2 中,使用 OpenGL 3.3)。我的理解是,VAO 保存了 VBO 的状态和相关属性,这样您就不必担心在绘图时绑定和启用所有内容,您只需绑定 VAO。但是通过实例化,您通常会有多个 VBO。你如何绕过需要绑定它们?还是我只需要为每个顶点数据和每个实例数据都使用一个 VBO?

我一直在看一些教程,例如:http://ogldev.atspace.co.uk/www/tutorial33/tutorial33.html

在我看来,他所做的是对每个顶点数据使用 VAO,而不是对每个实例数据使用 VAO。我试过用我的基于 Qt 的代码做同样的事情,但它对我不起作用(可能是因为我不完全理解它是如何工作的......当绘制发生时他的实例数据是否仍然需要绑定? ?)

一些虚拟代码...这有点傻,我只是在绘制两个三角形的单个实例,并以透视矩阵作为每个实例的属性。

glwindow.cpp:

#include "glwindow.h"

#include <QColor>
#include <QMatrix4x4>
#include <QVector>
#include <QVector3D>
#include <QVector4D>

#include <QDebug>


GLWindow::GLWindow(QWindow *parent) 
  : QWindow(parent)
  , _vbo(QOpenGLBuffer::VertexBuffer)
  , _matbo(QOpenGLBuffer::VertexBuffer)
  , _context(0)

  setSurfaceType(QWindow::OpenGLSurface);


GLWindow::~GLWindow()


void GLWindow::initGL()
  
  setupShaders();
  _program->bind();
  _positionAttr = _program->attributeLocation("position");
  _colourAttr = _program->attributeLocation("colour");
  _matrixAttr = _program->attributeLocation("matrix");

  QVector<QVector3D> triangles;
  triangles << QVector3D(-0.5, 0.5, 1) << QVector3D(-0.5, -0.5, 1) << QVector3D(0.5, -0.5, 1);
  triangles << QVector3D(0.5, 0.5, 0.5) << QVector3D(-0.5, -0.5, 0.5) << QVector3D(0.5, -0.5, 0.5);

  QVector<QVector3D> colours;
  colours << QVector3D(1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, 1);
  colours << QVector3D(1, 1, 1) << QVector3D(1, 1, 1) << QVector3D(1, 1, 1);

  _vao.create();
  _vao.bind();

  _vbo.create();
  _vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
  _vbo.bind();

  size_t positionSize = triangles.size() * sizeof(QVector3D);
  size_t colourSize = colours.size() * sizeof(QVector3D);
  _vbo.allocate(positionSize + colourSize);
  _vbo.bind();
  _vbo.write(0, triangles.constData(), positionSize);
  _vbo.write(positionSize, colours.constData(), colourSize);
  _colourOffset = positionSize;

  _program->setAttributeBuffer(_positionAttr, GL_FLOAT, 0, 3, 0);
  _program->setAttributeBuffer(_colourAttr, GL_FLOAT, _colourOffset, 3, 0);

  _program->enableAttributeArray(_positionAttr);  
  _program->enableAttributeArray(_colourAttr);

  _vao.release();

  _matbo.create();
  _matbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
  _matbo.bind();

  _matbo.allocate(4 * sizeof(QVector4D));
  _program->setAttributeBuffer(_matrixAttr, GL_FLOAT, 0, 4, 4 * sizeof(QVector4D));
  _program->enableAttributeArray(_matrixAttr);

  _func330->glVertexAttribDivisor(_matrixAttr, 1);
  _matbo.release();

  _program->release();
  resizeGL(width(), height());


void GLWindow::resizeGL(int w, int h)

  glViewport(0, 0, w, h);


void GLWindow::paintGL()

  if (! _context) // not yet initialized
    return;

  _context->makeCurrent(this);
  QColor background(Qt::black);

  glClearColor(background.redF(), background.greenF(), background.blueF(), 1.0f);
  glClearDepth(1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  QMatrix4x4 matrix;
  matrix.perspective(60, 4.0/3.0, 0.1, 100.0);
  matrix.translate(0, 0, -2);

  _program->bind();

  _matbo.bind();
  _matbo.write(0, matrix.constData(), 4 * sizeof(QVector4D));

  _vao.bind();

  glEnable(GL_DEPTH_TEST);
  _func330->glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);

  _vao.release();

  _program->release();

  _context->swapBuffers(this);
  _context->doneCurrent();



void GLWindow::setupShaders()


  QString vShaderSrc("#version 330\n"
    "layout(location = 0) in vec4 position;\n"
    "layout(location = 1) in vec4 colour;\n"
    "layout(location = 2) in mat4 matrix;\n"
    "smooth out vec4 col;\n"
    "void main() \n"
    "   col = colour;\n"
    "   gl_Position = matrix * position;\n"
    "\n");

  QString fShaderSrc("#version 330\n"
    "smooth in vec4 col;\n"
    "void main() \n"
    "   gl_FragColor = col;\n"
    "\n");


  _program = new QOpenGLShaderProgram(this);
  _program->addShaderFromSourceCode(QOpenGLShader::Vertex, vShaderSrc);
  _program->addShaderFromSourceCode(QOpenGLShader::Fragment, fShaderSrc);
  _program->link();




void GLWindow::exposeEvent(QExposeEvent *event)

  Q_UNUSED(event);

  if (isExposed())
  
    if (! _context)
    
      _context = new QOpenGLContext(this);
      QSurfaceFormat format(requestedFormat());
      format.setVersion(3,3);
      format.setDepthBufferSize(24);

      _context->setFormat(format);
      _context->create();

      _context->makeCurrent(this);
      initializeOpenGLFunctions();

      _func330 = _context->versionFunctions<QOpenGLFunctions_3_3_Core>();
      if (_func330)
        _func330->initializeOpenGLFunctions();
      else
      
        qWarning() << "Could not obtain required OpenGL context version";
        exit(1);
      

      initGL();
    

    paintGL();
  

glwindow.h:

#ifndef GL_WINDOW_H
#define GL_WINDOW_H

#include <QExposeEvent>
#include <QSurfaceFormat>
#include <QWindow>

#include <QOpenGLBuffer>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>


class GLWindow : public QWindow, protected QOpenGLFunctions

  Q_OBJECT
public:
  GLWindow(QWindow * = 0);
  virtual ~GLWindow();

  void initGL();
  void paintGL();
  void resizeGL(int, int);

protected:
  virtual void exposeEvent(QExposeEvent *);


private:

  void setupShaders();

  QOpenGLBuffer _vbo;
  QOpenGLBuffer _matbo;
  QOpenGLContext *_context;
  QOpenGLShaderProgram *_program;
  QOpenGLVertexArrayObject _vao;
  QOpenGLFunctions_3_3_Core *_func330;

  GLuint _positionAttr;
  GLuint _colourAttr;
  GLuint _matrixAttr;

  size_t _colourOffset;

 ; 

#endif

glbuffertest.cpp:

#include <QGuiApplication>
#include <QSurfaceFormat>

#include "glwindow.h"



int main(int argc, char **argv)


  QGuiApplication app(argc, argv);

  GLWindow window;  
  window.resize(400, 400);
  window.show();

  return app.exec();


glbuffertest.pro:

######################################################################
# Automatically generated by qmake (3.0) Fri May 16 09:49:41 2014
######################################################################

TEMPLATE = app
TARGET = glbuffertest
INCLUDEPATH += .

CONFIG += qt debug

# Input
SOURCES += glbuffertest.cpp glwindow.cpp
HEADERS += glwindow.h

更新: 我已经尝试摆脱我的_matbo 缓冲区,而是将矩阵数据放入与位置和颜色属性相同的 VBO 中,但这对我不起作用。我的initGL 函数现在看起来像:

void GLWindow::initGL()
  
  setupShaders();
  _program->bind();
  _positionAttr = _program->attributeLocation("position");
  _colourAttr = _program->attributeLocation("colour");
  _matrixAttr = _program->attributeLocation("matrix");

  QVector<QVector3D> triangles;
  triangles << QVector3D(-0.5, 0.5, 1) << QVector3D(-0.5, -0.5, 1) << QVector3D(0.5, -0.5, 1);
  triangles << QVector3D(0.5, 0.5, 0.5) << QVector3D(-0.5, -0.5, 0.5) << QVector3D(0.5, -0.5, 0.5);

  QVector<QVector3D> colours;
  colours << QVector3D(1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, 1);
  colours << QVector3D(1, 1, 1) << QVector3D(1, 1, 1) << QVector3D(1, 1, 1);

  _vao.create();
  _vao.bind();

  _vbo.create();
  _vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
  _vbo.bind();

  size_t positionSize = triangles.size() * sizeof(QVector3D);
  size_t colourSize = colours.size() * sizeof(QVector3D);
  size_t matrixSize = 4 * sizeof(QVector4D);
  _vbo.allocate(positionSize + colourSize + matrixSize);
  _vbo.bind();
  _vbo.write(0, triangles.constData(), positionSize);
  _vbo.write(positionSize, colours.constData(), colourSize);


  _colourOffset = positionSize;
  _matrixOffset = positionSize + colourSize;

  _program->setAttributeBuffer(_positionAttr, GL_FLOAT, 0, 3, 0);
  _program->setAttributeBuffer(_colourAttr, GL_FLOAT, _colourOffset, 3, 0);
  _program->setAttributeBuffer(_matrixAttr, GL_FLOAT, _matrixOffset, 4, 4 * sizeof(QVector4D));

  _program->enableAttributeArray(_positionAttr);  
  _program->enableAttributeArray(_colourAttr);
  _program->enableAttributeArray(_matrixAttr);
  _func330->glVertexAttribDivisor(_matrixAttr, 1);

  _vao.release();

  _program->release();
  resizeGL(width(), height());

paintGL:

void GLWindow::paintGL()

  if (! _context) // not yet initialized
    return;

  _context->makeCurrent(this);
  QColor background(Qt::black);

  glClearColor(background.redF(), background.greenF(), background.blueF(), 1.0f);
  glClearDepth(1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  QMatrix4x4 matrix;
  matrix.perspective(60, 4.0/3.0, 0.1, 100.0);
  matrix.translate(0, 0, -2);


  _program->bind();

  _vao.bind();
  _vbo.write(_matrixOffset, matrix.constData(), 4 * sizeof(QVector4D));

  /* I tried replacing the three preceding lines with the following, without success: */

  /*
  _vao.bind();
  _vbo.bind();
  _vbo.write(_matrixOffset, matrix.constData(), 4 * sizeof(QVector4D));


  _program->bind();
  _program->enableAttributeArray(_matrixAttr);
  _func330->glVertexAttribDivisor(_matrixAttr, 1); */

  glEnable(GL_DEPTH_TEST);
  _func330->glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);

  _vao.release();

  _program->release();

  _context->swapBuffers(this);
  _context->doneCurrent();


所以看来我的实例化问题不仅仅是在错误的时间绑定了错误的缓冲区。我还做错了什么?

【问题讨论】:

【参考方案1】:

我认为您必须为位置创建一个 VBO,为颜色创建一个 VBO(或使用跨步的交错数据)。 VAO 允许您使用多个 VBO,每个属性一个。

vao.create();
vao.bind();
// prepare your shader program
// ...
// prepare your VBOs : one VBO for pos, one VBO for colors, one for normals,...
// example for position
vertexPositionBuffer.create();
vertexPositionBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw); 
vertexPositionBuffer.bind();
// if your store the points using QVector<QVector3D>
vertexPositionBuffer.allocate(vertices.constData(), vertices.size() * sizeof(QVector3D));
vertexPositionBuffer.release();
// do the same for colors or other attributes
// ...
// after all buffers are created
shaderProgram.bind();
// Bind the position buffer
vertexPositionBuffer.bind();
shaderProgram.enableAttributeArray("vertexPosition");
shaderProgram.setAttributeBuffer("vertexPosition", GL_FLOAT, 0, 3);
vertexPositionBuffer.release();
// do the same for all other buffers
// ...
shaderProgram.release();
// release vao 
vao.release();

and in your paintGL function:

// update your matrices
// bind your shader program
// set you uniform variables
// then
vao.bind();
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
vao.release();
// release your shader program

【讨论】:

因为我用的是instancing,所以有些属性也需要在draw时更新,所以我需要绑定相应的buffer。使用我的颜色和位置属性使用的偏移方法共享多个属性的缓冲区不是问题,但是当我再次绑定我的 VAO 时,它们所在的 VBO 似乎没有被绑定(在绘制时,我意思)。 1) 在写入 int 之前必须绑定 VBO。 2)我认为绑定程序后在paintGL函数内的VBO中编写是不安全的 3)在编写VBO之前不需要绑定VAO。 4) 你应该使用 DynamicDraw 2) 在paintGL函数中写入VBO应该没问题...这就是我链接到的实例化教程所做的。事实上,有必要使用实例化。但是只能写入当前绑定的 VBO(我认为......我可以详细说明如果您尝试写入另一个 VBO 会发生什么)。 3) 事实上,在我发布的第一个代码中,我并没有在写信给_matbo 之前绑定 VAO。 4) 我不确定 DynamicDraw 会如何帮助:( 在paintGL _vbo.write 中没有绑定吗?阅读 QT 文档:您必须在写入之前 bin _vbo。我不明白您真正想要做什么:您的矩阵似乎是恒定的,那么为什么不统一呢?如果要使用实例,则必须为对象的每个实例提供属性(例如:每个实例一个转换矩阵):vbos 用于存储由 instanceId 索引的属性数组。 这是一个虚拟示例。我在这里只使用一个矩阵来查看是否可以使用矩阵作为glDrawArraysInstanced 函数的属性来绘制任何东西。在我正在使用的实际软件中,我需要用不同的颜色绘制数百次不同大小和不同位置的球体。至于我的虚拟代码中的_vbo,我没有在我的paintGL() 函数中写入它,而是在initGL() 函数中写入它之前绑定它。【参考方案2】:

我明白了。主要问题是:

    我必须遍历我的 mat4 属性的所有四列,设置并启用每一列,并在每一列上调用 glVertexAttribDivisor()。 对于我的 mat4 属性,我完全搞砸了对 QOpenGLShaderProgram::setAttributeBuffer() 的调用。

基本上,您必须将 mat4 视为四个独立的 vec4 属性(每列一个)。这丝毫不会影响您将 QMatrix4x4 数据复制到 QOpenGLBuffer 对象的方式,只是告诉着色器程序处理数据的方式。这在我在原始问题和The OpenGL Programming Guide's instancing tutorial 中链接的教程中都有很好的描述,我只是没有明白。所以,回到上面glwindow.cpp 的第一次尝试,我几乎没有改变,现在一切正常:

#include "glwindow.h"

#include <QColor>
#include <QMatrix4x4>
#include <QVector>
#include <QVector3D>
#include <QVector4D>

#include <QDebug>


GLWindow::GLWindow(QWindow *parent) 
  : QWindow(parent)
  , _vbo(QOpenGLBuffer::VertexBuffer)
  , _matbo(QOpenGLBuffer::VertexBuffer)
  , _context(0)

  setSurfaceType(QWindow::OpenGLSurface);


GLWindow::~GLWindow()


void GLWindow::initGL()
  
  setupShaders();
  _program->bind();
  _positionAttr = _program->attributeLocation("position");
  _colourAttr = _program->attributeLocation("colour");
  _matrixAttr = _program->attributeLocation("matrix");

  QVector<QVector3D> triangles;
  triangles << QVector3D(-0.5, 0.5, 1) << QVector3D(-0.5, -0.5, 1) << QVector3D(0.5, -0.5, 1);
  triangles << QVector3D(0.5, 0.5, 0.5) << QVector3D(-0.5, -0.5, 0.5) << QVector3D(0.5, -0.5, 0.5);

  QVector<QVector3D> colours;
  colours << QVector3D(1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, 1);
  colours << QVector3D(1, 1, 1) << QVector3D(1, 1, 1) << QVector3D(1, 1, 1);

  _vao.create();
  _vao.bind();

  _vbo.create();
  _vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
  _vbo.bind();

  size_t positionSize = triangles.size() * sizeof(QVector3D);
  size_t colourSize = colours.size() * sizeof(QVector3D);
  _vbo.allocate(positionSize + colourSize);
  _vbo.bind();
  _vbo.write(0, triangles.constData(), positionSize);
  _vbo.write(positionSize, colours.constData(), colourSize);
  _colourOffset = positionSize;

  _program->setAttributeBuffer(_positionAttr, GL_FLOAT, 0, 3, 0);
  _program->setAttributeBuffer(_colourAttr, GL_FLOAT, _colourOffset, 3, 0);

  _program->enableAttributeArray(_positionAttr);  
  _program->enableAttributeArray(_colourAttr);


  _matbo.create();
  _matbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
  _matbo.bind();

  _matbo.allocate(4 * sizeof(QVector4D));

  // This is completely wrong
  /*_program->setAttributeBuffer(_matrixAttr, GL_FLOAT, 0, 4, 4 * sizeof(QVector4D));
  _program->enableAttributeArray(_matrixAttr);

  _func330->glVertexAttribDivisor(_matrixAttr, 1);
  */      

  // The right way to set up a mat4 attribute for instancing
  for (unsigned i = 0; i < 4; i++)
  
    _program->setAttributeBuffer(_matrixAttr + i, GL_FLOAT, i * sizeof(QVector4D), 4, 4 * sizeof(QVector4D));
    _program->enableAttributeArray(_matrixAttr + i);
    _func330->glVertexAttribDivisor(_matrixAttr + i, 1);
  

  _matbo.release();
  _vao.release();

  _program->release();
  resizeGL(width(), height());


void GLWindow::resizeGL(int w, int h)

  glViewport(0, 0, w, h);


void GLWindow::paintGL()

  if (! _context) // not yet initialized
    return;

  _context->makeCurrent(this);
  QColor background(Qt::black);

  glClearColor(background.redF(), background.greenF(), background.blueF(), 1.0f);
  glClearDepth(1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  QMatrix4x4 matrix;
  matrix.perspective(60, 4.0/3.0, 0.1, 100.0);
  matrix.translate(0, 0, -2);

  _program->bind();
  _vao.bind();

  _matbo.bind();
  _matbo.write(0, matrix.constData(), 4 * sizeof(QVector4D));


  glEnable(GL_DEPTH_TEST);
  _func330->glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);

  _vao.release();

  _program->release();

  _context->swapBuffers(this);
  _context->doneCurrent();



void GLWindow::setupShaders()


  QString vShaderSrc("#version 330\n"
    "layout(location = 0) in vec4 position;\n"
    "layout(location = 1) in vec4 colour;\n"
    "layout(location = 2) in mat4 matrix;\n"
    "smooth out vec4 col;\n"
    "void main() \n"
    "   col = colour;\n"
    "   gl_Position = matrix * position;\n"
    "\n");

  QString fShaderSrc("#version 330\n"
    "smooth in vec4 col;\n"
    "void main() \n"
    "   gl_FragColor = col;\n"
    "\n");


  _program = new QOpenGLShaderProgram(this);
  _program->addShaderFromSourceCode(QOpenGLShader::Vertex, vShaderSrc);
  _program->addShaderFromSourceCode(QOpenGLShader::Fragment, fShaderSrc);
  _program->link();




void GLWindow::exposeEvent(QExposeEvent *event)

  Q_UNUSED(event);

  if (isExposed())
  
    if (! _context)
    
      _context = new QOpenGLContext(this);
      QSurfaceFormat format(requestedFormat());
      format.setVersion(3,3);
      format.setDepthBufferSize(24);

      _context->setFormat(format);
      _context->create();

      _context->makeCurrent(this);
      initializeOpenGLFunctions();

      _func330 = _context->versionFunctions<QOpenGLFunctions_3_3_Core>();
      if (_func330)
        _func330->initializeOpenGLFunctions();
      else
      
        qWarning() << "Could not obtain required OpenGL context version";
        exit(1);
      

      initGL();
    

    paintGL();
  

请注意,我还移动了 _matbo 的绑定和 mat4 属性的设置,以便在释放 VAO 之前完成所有操作。我最初对允许多少 VBO 以及何时需要绑定感到非常困惑。在单个VAO中拥有多个VBO没有问题,只是需要绑定正确的一个,并且在调用QOpenGLShaderProgram::setAttributeBuffer()之前需要绑定正确的一个。调用 glDraw*() 时绑定哪个缓冲区并不重要(我相信如果我错了,有人会发表评论)。

【讨论】:

以上是关于如何在 Qt 5 中使用带有实例化的 VAO的主要内容,如果未能解决你的问题,请参考以下文章

PyQt4 不可见 C++ 实例化的 QApplication

重用 VBO 进行硬件实例化

带有 lambda 作为每个实例化的唯一默认参数的模板

带有 Clang 10 显式模板实例化的 ~queue 的未定义引用

c#部分---需要实例化的内容;

如何在运行时控制实例化的预制件作为孩子