QGraphicsScene 和 OpenGL 片段着色器不工作

QGraphicsScene & OpenGL Fragment Shader Not Working

我有一个非常大的 QGraphicsScene,可以包含非常多的图形。我使用 QGLWidget 作为视口,这样我就可以利用 OpenGL 来尝试改进某些东西的渲染方式。我已经创建了一个自定义 QGraphicsItem,我可以使用它在一次渲染调用中绘制具有相同纹理的多个四边形,而不是在场景中有成百上千个不同的 QGraphicsItem,它们实际上都是以相同的方式绘制的,只是在不同的位置。在我自定义的 QGraphicsItem 的 paint() 方法中,我调用了 beginNativePainting() 和 endNativePainting() 并在它们之间执行我所有的 OpenGL 调用。

我想使用着色器程序,以便我可以在顶点着色器中稍微操纵顶点,所以我复制了 Qt 的 OpenGL 纹理示例,它使用着色器程序绘制 6 个带纹理的四边形。该示例按原样工作得很好,但是当我尝试在 QGraphicsItem 的 paint() 方法中使用相同的方法时,我所有的四边形都被绘制成白色。我最好的猜测是我的片段着色器没有被使用。我什至尝试在片段着色器中对颜色进行硬编码,但没有任何变化。

这是我的自定义 QGraphicsItem 的源代码 class。

class BatchGraphics : public QGraphicsPixmapItem
{
  enum {PROGRAM_VERTEX_ATTRIBUTE = 0,
        PROGRAM_TEXCOORD_ATTRIBUTE = 1};

public:
  BatchGraphics()
    : _program(0),
      _texture(0),
      _dirty(false)
  {
  }

  // Returns the custom bounding rect for this item which encompasses all quads
  QRectF boundingRect() const
  {
    return _boundingRect;
  }

  // Add a quad to the batch.  Only the center point is necessary
  void addQuad(int id, float x, float y)
  {
    _quads.insert(id, QPointF(x, y));
    updateBoundingRect();
    _dirty = true;
  }

  // Remove a quad from the batch.
  void removeQuad(int id)
  {
    if (_quads.contains(id))
    {
      _quads.remove(id);
      updateBoundingRect();
      _dirty = true;
    }
  }

  // Return the number of quads in the batch
  int count() {return _quads.count();}

  // Paint the batch using a custom implementation.
  void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  {
    // If the item is dirty (has been modified, update the geometry)
    if (_dirty) {
      updateGeometry();
    }

    if (_program)
    {
      painter->beginNativePainting();

      // Enable GL states
      //glEnable(GL_TEXTURE_2D);

      // Set the MVP matrix
      _program->setUniformValue("matrix", painter->transform());

      // Enable and set the vertex and texture attributes
      _program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
      _program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
      _program->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE,   GL_FLOAT, _vertices.constData(),   3, 5*sizeof(GLfloat));
      _program->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, _vertices.constData()+2, 2, 5*sizeof(GLfloat));

      // Bind the texture
      _texture->bind();

      // Draw the arrays
      glDrawArrays(GL_TRIANGLES, 0, _quads.count()*6); // 6 vertices per quad

      painter->endNativePainting();
    }
  }

private:
  // Initialize the shader and texture
  void initialize()
  {
    // Create the OpenGL texture
    _texture = new QOpenGLTexture(pixmap().toImage());

    // Vertex Shader
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex);
    const char *vsrc =
        "attribute  highp    vec4  vertex;\n"
        "attribute  mediump  vec4  texCoord;\n"
        "varying    mediump  vec4  texc;\n"
        "uniform    mediump  mat4  matrix;\n"
        "void main(void)\n"
        "{\n"
        "    gl_Position = matrix * vertex;\n"
        "    texc = texCoord;\n"
        "}\n";
    vshader->compileSourceCode(vsrc);

    // Fragment Shader
    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment);
    const char *fsrc =
        "uniform           sampler2D  texture;\n"
        "varying  mediump  vec4       texc;\n"
        "void main(void)\n"
        "{\n"
        "    gl_FragColor = texture2D(texture, texc.st);\n"
        "}\n";
    fshader->compileSourceCode(fsrc);

    // Program
    _program = new QOpenGLShaderProgram;
    _program->addShader(vshader);
    _program->addShader(fshader);
    _program->bindAttributeLocation("vertex",   PROGRAM_VERTEX_ATTRIBUTE);
    _program->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);
    _program->link();

    _program->bind();
    _program->setUniformValue("texture", 0);
  }

  // Update the vertex array.  Calls initialize the first time.
  void updateGeometry()
  {
    if (_program == 0) {
      initialize();
    }

    _vertices.clear();

    // Half pixmap size
    QPointF s = QPointF(pixmap().width()/2, pixmap().height()/2);

    // Build vertex data for each quad
    foreach (const QPointF& point, _quads)
    {
      // Top Left
      _vertices << point.x()-s.x(); // x
      _vertices << point.y()-s.y(); // y
      _vertices << 1;               // z
      _vertices << 0;               // tu
      _vertices << 1;               // tv

      // Top Right
      _vertices << point.x()+s.x(); // x
      _vertices << point.y()-s.y(); // y
      _vertices << 1;               // z
      _vertices << 1;               // tu
      _vertices << 1;               // tv

      // Bottom Left
      _vertices << point.x()-s.x(); // x
      _vertices << point.y()+s.y(); // y
      _vertices << 1;               // z
      _vertices << 0;               // tu
      _vertices << 0;               // tv

      // Top Right
      _vertices << point.x()+s.x(); // x
      _vertices << point.y()-s.y(); // y
      _vertices << 1;               // z
      _vertices << 1;               // tu
      _vertices << 1;               // tv

      // Bottom Left
      _vertices << point.x()-s.x(); // x
      _vertices << point.y()+s.y(); // y
      _vertices << 1;               // z
      _vertices << 0;               // tu
      _vertices << 0;               // tv

      // Bottom Right
      _vertices << point.x()+s.x(); // x
      _vertices << point.y()+s.y(); // y
      _vertices << 1;               // z
      _vertices << 1;               // tu
      _vertices << 0;               // tv
    }

    _dirty = false;
  }

private:
  // Updates the bounding rect based on the quads in the batch.
  void updateBoundingRect()
  {
    prepareGeometryChange();

    double left = 9999;
    double right = -9999;
    double top = 9999;
    double bottom = -9999;

    double w = pixmap().width()/2;
    double h = pixmap().width()/2;

    foreach (const QPointF& p, _quads)
    {
      left   = qMin(left,   p.x()-w);
      right  = qMax(right,  p.x()+w);
      top    = qMin(top,    p.y()-h);
      bottom = qMax(bottom, p.y()+h);
    }

    _boundingRect = QRectF(left, top, (right-left), (bottom-top));
  }

private:
  QOpenGLShaderProgram* _program;
  QOpenGLTexture* _texture;
  QRectF _boundingRect;
  QMap<int, QPointF> _quads;
  QVector<GLfloat> _vertices;
  bool _dirty;
};

我了解渲染管线的基础知识以及如何使用着色器,但就事物与使用某些功能时必须调用的其他 OpenGL 方法之间的依赖关系而言,我一无所知。我可以使用固定函数管道方法使四边形与纹理一起渲染,但这是老派了,就像我说的那样,一旦我开始工作,我希望能够在顶点着色器中操纵顶点。

我在创建 QGLWidget 时没有做任何特别的事情,它的 QGLFormat 最终变成了 2.0。我也试过调用 glEnable(GL_TEXTURE_2D),但这只会使四边形呈现黑色而不是白色。我也尝试过在每次调用 paint() 时绑定程序,我想也许 Qt 在幕后的其他地方绑定了一个不同的着色器,但这只会导致什么都不会出现。

有人可以提供帮助吗?我不明白为什么这种方法在 Qt 的纹理示例中工作正常,但当我尝试在 QGraphicsItem 中执行时却不行。

看了Qt的源码和beginNativePainting()时发生了什么,我终于明白了。首先,我确实必须在每次调用 paint() 时绑定我的着色器,其次我必须获得正确的 MVP 矩阵。

我试图将 QPainter 的变换传递到我的着色器以充当模型视图投影矩阵,但变换只是模型视图矩阵。我还需要获取投影矩阵,Qt 在调用 beginNativePainting() 时设置它。

我直接从 OpenGL 获得了项目和模型视图矩阵,并在绑定我的纹理和 presto 后将它们组合起来传递给我的着色器!成功了!

以下是我必须进行的相关更改:

painter->beginNativePainting();

// Enable GL states
//glEnable(GL_TEXTURE_2D);

// === Begin New Code ======
// Bind my program
_program->bind();

QMatrix4x4 proj;
glGetFloatv(GL_PROJECTION_MATRIX, proj.data());

QMatrix4x4 model;
glGetFloatv(GL_MODELVIEW_MATRIX, model.data());

// Set the MVP matrix
_program->setUniformValue("matrix", proj * model);
// === End New Code ======

// Enable and set the vertex and texture attributes
_program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
_program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
_program->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE,   GL_FLOAT, _vertices.constData(),   3, 5*sizeof(GLfloat));
_program->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, _vertices.constData()+2, 2, 5*sizeof(GLfloat));