OpenGL with QtWidgets:纹理

(本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/

0.前言

上一篇笔记记录了OpenGL在QtWidgets中的使用:https://blog.csdn.net/gongjianbo1992/article/details/104113006 ,本文将学习纹理(Texture)的使用。

1.知识点

根据教程所述,OpenGL中我们可以使用纹理(Texture)来展现细节,纹理是一个2D图片(甚至也有1D和3D的纹理)。

要把纹理映射到绘制区域,如三角形,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation),插值方式我们可以设置(即纹理过滤设置,可以根据临近值,或是线性插值)。

纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。下面的图片展示了我们是如何把纹理坐标映射到三角形上的。

纹理坐标的范围通常是从(0, 0)到(1, 1),如果我们把纹理坐标设置在范围之外(如小于0,或者大于1),OpenGL默认的行为是重复这个纹理图像,这也是可以设置的(即纹理环绕设置)。

此外,教程还提到了多级渐远纹理和纹理单元。

假设我们有一个包含着上千物体的大房间,每个物体上都有纹理。有些物体会很远,但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。OpenGL有一个glGenerateMipmaps函数,在创建完一个纹理后调用它OpenGL就会承担接下来的所有工作了。

使用glUniform1i,我们可以给纹理采样器分配一个位置值,这样的话我们能够在一个片段着色器中设置多个纹理。一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元。纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:

扫描二维码关注公众号,回复: 9275594 查看本文章
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);

激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元。OpenGL至少保证有16个纹理单元供你使用。

2.如何实现

使用OpenGL先继承QOpenGLWidget类并实现对应接口,我写了两个Demo:

第一个Demo(GLTexture 单个纹理贴图),我把教程的着色器程序类替换为了Qt封装的QOpenGLShaderProgram,剩下的直接复制粘贴教程代码改动很小。在图片加载上,我也使用了Qt的QImage类。有三点要注意,

一是因为OpenGL要求纹理坐标原点在图片下方,所以默认加载的图片是上下颠倒的,我在顶点着色器里把纹理顶点反了一下(即1-xx);

二是设置纹理的时候,记得根据图片格式来修改glTexImage2D对应的参数,参数枚举可以查OpenGL API:https://www.khronos.org/opengl/wiki/GLAPI/glTexImage2D ;

三是如果图片是半透明的,需要进行设置,不然没效果:

    //当我们需要绘制透明图片时,就需要关闭GL_DEPTH_TEST并且打开混合glEnable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    //基于源像素Alpha通道值的半透明混合函数
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

第二个Demo(GLTextureUnit 使用纹理单元多个纹理贴图),我把大部分操作都替换为了Qt提供的封装类。相较于直接使用OpenGL的函数,Qt提供的类操作更简洁,比如纹理图片数据的加载,直接构造函数里填上路径就可以加载了。一些对象的创建有create()函数,绑定有bind()函数,释放有destroy()函数,等等。

在析构函数中,因为要释放VAO、VBO等,Qt示例里是把释放操作放在了makeCurrent();和doneCurrent();两句代码之间。makecurrent()通过使相应的上下文成为当前上下文并在该上下文中绑定framebuffer对象,为渲染此小部件的OpenGL内容做准备。在大多数情况下,不必调用此函数,因为在调用paintGL()之前会自动调用该函数。doneCurrent()释放上下文。(为什么Qt没有做一个析构时自动释放的封装)

3.实现代码

(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git

我的GLTexture类和GLTextureUnit类效果图:

GLTexture类代码:

#ifndef GLTEXTURE_H
#define GLTEXTURE_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>

//纹理贴图
class GLTexture : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit GLTexture(QWidget *parent = nullptr);
    ~GLTexture();

protected:
    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

private:
    QOpenGLShaderProgram _shaderProgram;
    GLuint _VAO;
    GLuint _VBO;
    GLuint _EBO;
    GLuint _Texture;
};

#endif // GLTEXTURE_H
#include "GLTexture.h"

GLTexture::GLTexture(QWidget *parent)
    : QOpenGLWidget(parent)
{

}

GLTexture::~GLTexture()
{
    glDeleteVertexArrays(1, &_VAO);
    glDeleteBuffers(1, &_VBO);
    glDeleteBuffers(1, &_EBO);
    glDeleteTextures(1,&_Texture);
}

void GLTexture::initializeGL()
{
    //为当前上下文初始化OpenGL函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    //因为OpenGL纹理颠倒过来的,所以取反vec2(aTexCoord.x, 1-aTexCoord.y);
    const char *vertex_str=R"(#version 330 core
                           layout (location = 0) in vec3 aPos;
                           layout (location = 1) in vec3 aColor;
                           layout (location = 2) in vec2 aTexCoord;
                           out vec3 ourColor;
                           out vec2 TexCoord;

                           void main()
                           {
                           gl_Position = vec4(aPos, 1.0);
                           ourColor = aColor;
                           TexCoord = vec2(aTexCoord.x, 1-aTexCoord.y);
                           })";
    const char *fragment_str=R"(#version 330 core
                             in vec3 ourColor;
                             in vec2 TexCoord;
                             uniform sampler2D ourTexture;

                             void main()
                             {
                             gl_FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
                             })";

    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!_shaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()<<"compiler vertex error"<<_shaderProgram.log();
    }
    if(!_shaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Fragment,fragment_str)){
        qDebug()<<"compiler fragment error"<<_shaderProgram.log();
    }
    //使用addShader()将添加到该程序的着色器链接在一起。
    if(!_shaderProgram.link()){
        qDebug()<<"link shaderprogram error"<<_shaderProgram.log();
    }

    //从LearnOpenGL移植过来
    //纹理坐标x范围增大了,使它看起来像正方形
    float vertices[] = {
        // positions          // colors           // texture coords
        0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
        0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // bottom right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,  0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,  0.0f, 1.0f  // top left
    };
    unsigned int indices[] = {
        0, 1, 3, // first triangle
        1, 2, 3  // second triangle
    };
    glGenVertexArrays(1, &_VAO);
    glGenBuffers(1, &_VBO);
    glGenBuffers(1, &_EBO);

    glBindVertexArray(_VAO);

    glBindBuffer(GL_ARRAY_BUFFER, _VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // texture coord attribute
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);


    // load and create a texture
    glGenTextures(1, &_Texture);
    glBindTexture(GL_TEXTURE_2D, _Texture);
    // set the texture wrapping parameters
    //GL_REPEAT重复,GL_MIRRORED_REPEAT镜像重复,GL_CLAMP_TO_EDGE拉伸,GL_CLAMP_TO_BORDER无重复
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    // set texture filtering parameters
    //GL_NEAREST临近值,GL_LINEAR线性插值
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load image, create texture and generate mipmaps
    //参照博客 https://www.jianshu.com/p/273b7f960f3d
    QImage img1 = QImage(":/face.png").convertToFormat(QImage::Format_RGBA8888);
    if (!img1.isNull()) {
        // void glTexImage2D(GLenum target​, GLint level​, GLint internalFormat​, GLsizei width​, GLsizei height​, GLint border​, GLenum format​, GLenum type​, const GLvoid * data​);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img1.width(), img1.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img1.bits());
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    // don't forget to activate/use the shader before setting uniforms!
    //只有一个纹理贴图可以不用,默认就是0
    //_shaderProgram.bind();
    //glUniform1i(_shaderProgram.uniformLocation("texture1"), 0);
    //_shaderProgram.release();
}

void GLTexture::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //当我们需要绘制透明图片时,就需要关闭GL_DEPTH_TEST并且打开混合glEnable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    //基于源像素Alpha通道值的半透明混合函数
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _Texture);

    _shaderProgram.bind();
    //绘制
    glBindVertexArray(_VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    _shaderProgram.release();
}

void GLTexture::resizeGL(int width, int height)
{
    glViewport(0,0,width,height);
}

 GLTextureUnit类代码:

#ifndef GLTEXTUREUNIT_H
#define GLTEXTUREUNIT_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>

//纹理单元,本类尝试使用Qt封装得OpenGL相关类
class GLTextureUnit : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
public:
    explicit GLTextureUnit(QWidget *parent = nullptr);
    ~GLTextureUnit();

protected:
    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

private:
    //着色器程序
    QOpenGLShaderProgram _shaderProgram;
    //顶点数组对象
    QOpenGLVertexArrayObject _vao;
    //顶点缓冲
    QOpenGLBuffer _vbo;
    //索引缓冲
    QOpenGLBuffer _ebo;
    //纹理(因为不能赋值,所以只能声明为指针)
    QOpenGLTexture *_texture1 = nullptr;
    QOpenGLTexture *_texture2 = nullptr;
};

#endif // GLTEXTUREUNIT_H
#include "GLTextureUnit.h"

GLTextureUnit::GLTextureUnit(QWidget *parent)
    : QOpenGLWidget(parent)
{

}

GLTextureUnit::~GLTextureUnit()
{
    makeCurrent();
    _vbo.destroy();
    _ebo.destroy();
    _vao.destroy();
    delete _texture1;
    delete _texture2;
    doneCurrent();
}

void GLTextureUnit::initializeGL()
{
    //为当前上下文初始化OpenGL函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    //因为OpenGL纹理颠倒过来的,所以取反vec2(aTexCoord.x, 1-aTexCoord.y);
    const char *vertex_str=R"(#version 330 core
                           layout (location = 0) in vec3 aPos;
                           layout (location = 1) in vec3 aColor;
                           layout (location = 2) in vec2 aTexCoord;
                           out vec3 ourColor;
                           out vec2 TexCoord;

                           void main()
                           {
                           gl_Position = vec4(aPos, 1.0);
                           ourColor = aColor;
                           TexCoord = vec2(aTexCoord.x, 1-aTexCoord.y);
                           })";
    const char *fragment_str=R"(#version 330 core
                             in vec3 ourColor;
                             in vec2 TexCoord;
                             uniform sampler2D texture1;
                             uniform sampler2D texture2;

                             void main()
                             {
                             gl_FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
                             })";

    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!_shaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()<<"compiler vertex error"<<_shaderProgram.log();
    }
    if(!_shaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Fragment,fragment_str)){
        qDebug()<<"compiler fragment error"<<_shaderProgram.log();
    }
    //使用addShader()将添加到该程序的着色器链接在一起。
    if(!_shaderProgram.link()){
        qDebug()<<"link shaderprogram error"<<_shaderProgram.log();
    }

    //VAO,VBO
    float vertices[] = {
        // positions          // colors           // texture coords
        0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
        0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // bottom right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left
    };
    //EBO
    unsigned int indices[] = {
        0, 1, 3,  // first Triangle
        1, 2, 3   // second Triangle
    };
    _vao.create();
    _vao.bind();
    //QOpenGLVertexArrayObject::Binder vaoBind(&_vao);
    _vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    _vbo.create();
    _vbo.bind();
    _vbo.allocate(vertices,sizeof(vertices));

    _ebo=QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    _ebo.create();
    _ebo.bind();
    _ebo.allocate(indices,sizeof(indices));

    // position attribute
    int attr = -1;
    attr = _shaderProgram.attributeLocation("aPos");
    _shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
    _shaderProgram.enableAttributeArray(attr);
    // color attribute
    attr = _shaderProgram.attributeLocation("aColor");
    _shaderProgram.setAttributeBuffer(attr, GL_FLOAT, sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 8);
    _shaderProgram.enableAttributeArray(attr);
    // texture coord attribute
    attr = _shaderProgram.attributeLocation("aTexCoord");
    _shaderProgram.setAttributeBuffer(attr, GL_FLOAT, sizeof(GLfloat) * 6, 2, sizeof(GLfloat) * 8);
    _shaderProgram.enableAttributeArray(attr);

    // texture 1
    //直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
    _texture1 = new QOpenGLTexture(QImage(":/box.jpg"), QOpenGLTexture::GenerateMipMaps);
    if(!_texture1->isCreated()){
        qDebug() << "Failed to load texture";
    }
    // set the texture wrapping parameters
    // 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    _texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    _texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
    // set texture filtering parameters
    //等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    _texture1->setMinificationFilter(QOpenGLTexture::Linear);
    _texture1->setMagnificationFilter(QOpenGLTexture::Linear);

    // texture 2
    //直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
    _texture2 = new QOpenGLTexture(QImage(":/face.png"), QOpenGLTexture::GenerateMipMaps);
    if(!_texture2->isCreated()){
        qDebug() << "Failed to load texture";
    }
    // set the texture wrapping parameters
    // 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    _texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    _texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);//
    // set texture filtering parameters
    //等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    _texture2->setMinificationFilter(QOpenGLTexture::Linear);
    _texture2->setMagnificationFilter(QOpenGLTexture::Linear);

    _shaderProgram.bind();
    _shaderProgram.setUniformValue("texture1", 0);
    _shaderProgram.setUniformValue("texture2", 1);
    _shaderProgram.release();
}

void GLTextureUnit::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //当我们需要绘制透明图片时,就需要关闭GL_DEPTH_TEST并且打开混合glEnable(GL_BLEND);
    //glDisable(GL_DEPTH_TEST);
    //glEnable(GL_BLEND);
    //基于源像素Alpha通道值的半透明混合函数
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    //纹理单元的应用
    glActiveTexture(GL_TEXTURE0);
    _texture1->bind();
    glActiveTexture(GL_TEXTURE1);
    _texture2->bind();

    _shaderProgram.bind();
    //绘制
    //QOpenGLVertexArrayObject::Binder vaoBind(&_vao);
    _vao.bind();
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    _shaderProgram.release();
}

void GLTextureUnit::resizeGL(int width, int height)
{
    glViewport(0, 0, width, height);
}

 4.参考

教程:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/

博客(Qt+OpenGL):https://www.jianshu.com/p/273b7f960f3d

博客(纹理颠倒):https://blog.csdn.net/narutojzm1/article/details/51940817

发布了106 篇原创文章 · 获赞 31 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/104119033