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));
我有一个非常大的 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));