使用 QOpenGLWidget 中的着色器更新 QImage

Posted

技术标签:

【中文标题】使用 QOpenGLWidget 中的着色器更新 QImage【英文标题】:Update a QImage using a shader in QOpenGLWidget 【发布时间】:2018-02-05 13:15:48 【问题描述】:

我有一个简单的顶点着色器

static const char *vertexShader=
        "attribute vec4 vPosition;  \n"

        "void main()\n"
            "gl_Position = vPosition;\n"
        "";

我还有一个着色器,可以在图像上创建“广告牌”效果。

static const char *fragmentShader=
            "uniform float grid;\n"
            "uniform float dividerValue;\n"
            "uniform float step_x;\n"
            "uniform float step_y;\n"
            "uniform sampler2D source;\n"
            "uniform lowp float qt_Opacity;\n"
            "uniform vec2 qt_TexCoord0;\n"

            "void main()\n"
                "vec2 uv = qt_TexCoord0.xy;\n"
                "float offx = floor(uv.x  / (grid * step_x));\n"
                "float offy = floor(uv.y  / (grid * step_y));\n"
                "vec3 res = texture2D(source, vec2(offx * grid * step_x , offy * grid * step_y)).rgb;\n"
                "vec2 prc = fract(uv / vec2(grid * step_x, grid * step_y));\n"
                "vec2 pw = pow(abs(prc - 0.5), vec2(2.0));\n"
                "float  rs = pow(0.45, 2.0);\n"
                "float gr = smoothstep(rs - 0.1, rs + 0.1, pw.x + pw.y);\n"
                "float y = (res.r + res.g + res.b) / 3.0;\n"
                "vec3 ra = res / y;\n"
                "float ls = 0.3;\n"
                "float lb = ceil(y / ls);\n"
                "float lf = ls * lb + 0.3;\n"
                "res = lf * res;\n"
                "vec3 col = mix(res, vec3(0.1, 0.1, 0.1), gr);\n"
                "if (uv.x < dividerValue)\n"
                    "gl_FragColor = qt_Opacity * vec4(col, 1.0);\n"
                "else\n"
                    "gl_FragColor = qt_Opacity * texture2D(source, uv);\n"
            "";

我想做的是使用此着色器将此效果应用于 QtOpenGlWidget 中的图像。但是我不知道如何将我的图像设置为纹理并将其传递给着色器,然后将其返回并使用着色器效果进行修改。我想要实现的是:https://imgur.com/a/NSY0u 但我的着色器不会影响图像 https://imgur.com/a/dgSfq 。我的 GLWidget 类:

GLWidget::GLWidget(Helper *helper, QWidget *parent)
: QOpenGLWidget(parent), helper(helper)

    QImage img("E:\\pictures\\0151.jpg");
    image = img;
    image = image.convertToFormat(QImage::Format_RGBA8888);

    setFixedSize(512, 512);
    setAutoFillBackground(false);

    targetWidth = width();
    targetHeight = height();

    qDebug() << "targetWidth="<<targetWidth;
    qDebug() << "targetHeight ="<<targetHeight ;
//this values i am trying to pass to my fragment shader
    grid = 5.0;//grid on image

    dividerValue = 0.5;

    step_x = 0.0015625;
    step_y = height() ? (2.5 * step_x * targetWidth / targetHeight) : 0.0;


void GLWidget::initializeGL()

    initializeOpenGLFunctions();

    m_program = new QOpenGLShaderProgram;
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShader);//?

    m_program->link();
    m_program->bind();
    m_program->release();

//we can use paintEvent to display our image with opengl
void GLWidget::paintEvent(QPaintEvent *event)

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    m_program->bind();

    QPainter painter;
    painter.begin(this);
    painter.drawImage(0,0,image);

    QOpenGLTexture texture(image); //I dont know how to setUniformValue(m_program->uniformLocation("source"),texture) to my shader


    GLuint m_texture;
    glGenTextures(1, &m_texture);
    glBindTexture(GL_TEXTURE_2D, m_texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width(), image.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
    glGenerateMipmap(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE_2D);
  //open an image
   m_program->setUniformValue("grid", grid);
   m_program->setUniformValue("dividerValue",dividerValue);
   m_program->setUniformValue("step_x", step_x);
   m_program->setUniformValue("step_y", step_y);
   m_program->setUniformValue(m_program->uniformLocation("source"),m_texture);      
   painter.end();
   m_program->release();

【问题讨论】:

【参考方案1】:

当您绑定纹理时,它会绑定到当前活动的纹理图像单元(参见Binding textures to samplers)。

可以通过glActiveTexture 选择活动纹理单元。默认纹理单元为GL_TEXTURE0

您必须提供给纹理采样器 uniform 的值不是纹理的名称,而是纹理绑定到的纹理单元(编号):

int texture_unit = 0;                                 // <-----  e.g. texture unit 0
glActiveTexture( GL_TEXTURE0 + texture_unit );
glBindTexture( GL_TEXTURE_2D, m_texture );

.....

m_program->bind(); 
m_program->setUniformValue( "source", texture_unit ); // <----- texture unit

对于QOpenGLTexture 对象,可以通过QOpenGLTexture::bind 选择纹理单元:

int texture_unit = 1;                                 // <-----  e.g. texture unit 1

QOpenGLTexture texture(image);
texture.bind( texture_unit );

m_program->bind(); 
m_program->setUniformValue( "source", texture_unit ); // <----- texture unit

请注意,从 OpenGL 4.2 开始,纹理单元可以在着色器中通过 Binding point 进行初始化:

layout(binding = 0) uniform sampler2D source; // binding = 0 -> texture unit 0

答案的扩展:

以下代码会将图像绘制到整个小部件,并由您的着色器进行处理。最后从 GPU 读回渲染图像:

class GLWidget : public QOpenGLWidget

    .....

    QOpenGLShaderProgram * m_program = nullptr;
    QOpenGLTexture       * m_texture = nullptr;
;

void GLWidget::initializeGL()

    initializeOpenGLFunctions();

    QImage img("E:\\pictures\\0151.jpg");
    m_texture = new QOpenGLTexture( img );

    m_program = new QOpenGLShaderProgram;
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader);
    m_program->bindAttributeLocation("vPosition", 0);
    m_program->link();

void GLWidget::paintEvent(QPaintEvent *event)

    // celar the framebuffer
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // bind the texture
    uint texture_unit = 1;
    m_texture->bind( texture_unit );

    // activate the shader
    m_program->bind();
    m_program->setUniformValue( "source", texture_unit );
    m_program->setUniformValue( "grid", grid );
    m_program->setUniformValue( "dividerValue", dividerValue );
    m_program->setUniformValue( "step_x", step_x );
    m_program->setUniformValue( "step_y", step_y );

    // draw a quad over the entire widget
    GLfloat vertices[] -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,  1.0f, 1.0f, 1.0f ;
    m_program->enableAttributeArray(0);
    m_program->setAttributeArray(0, GL_FLOAT, vertices, 2);
    glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
    m_program->disableAttributeArray(0);

    // release the shader
    m_program->release();

    // read the rendered image
    int width  = ....;
    int height = ....;
    unsigned char *pixels = new unsigned char[width * height * 4];
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    QImage *img = new QImage( pixels, width, height, QImage::Format_RGBA8888 );

    .....

您还必须对顶点着色器和片段着色器进行一些更改。在顶点着色器中,您必须将顶点位置传递给片段着色器:

attribute vec2 vPosition;
varying vec2 v_pos;

void main()

    v_pos = vPosition.xy;
    gl_Position = vec4(vPosition.xy, 0.0, 1.0);

在片段着色器中,您必须计算顶点位置的纹理坐标:

varying vec2 v_pos;

void main()

    vec2 uv = v_pos.xy * 0.5 + 0.5;

    ....

另见glwidget.cpp Example File。

【讨论】:

那么,OpenGL 将 GL_TEXTURE0 + texture_unit 编号设置为我的 QImage ? 最后一个问题。更多与 Qt 相关。如果我对图像进行了效果,我可以从着色器返回修改后的纹理作为 QImage 并在场景上绘画吗? 让我们continue this discussion in chat。 这真的很有帮助。谢谢。剩下的唯一问题是关于顶点着色器。我还需要使用 gl_Position 来获取有关场景的数据吗? 我在聊天中写道

以上是关于使用 QOpenGLWidget 中的着色器更新 QImage的主要内容,如果未能解决你的问题,请参考以下文章

用于渲染 2D 文本的 paintGL() 中的着色器和 QPainter 之间的冲突

QOpenGLWidget的resizeGL不是调用glViewport的地方吗?

QOpenGLWidget不绘制数组

如何将 QPainter 和 OpenGL 与着色器与 Qt5.9 并行使用

qt glsl渲染rgb

在QOpenGLWidget上使用Qt5小部件而不重写?