代码之家  ›  专栏  ›  技术社区  ›  Pavel Melnikov

使用QOpenGLWidget中的着色器更新QImage

  •  3
  • Pavel Melnikov  · 技术社区  · 7 年前

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

    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 回复  |  直到 7 年前
        1
  •  2
  •   Rabbid76    7 年前

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

    活动纹理单元可以通过以下方式选择: glActiveTexture . 默认纹理单位为 GL_TEXTURE0 .

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

    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
    


    答案的扩展:

    以下代码将通过着色器处理将a图像绘制到整个小部件。最后,渲染图像从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 .