zoukankan      html  css  js  c++  java
  • 4.QOpenGLWidget-对三角形进行纹理贴图、纹理叠加

    在上章3.QOpenGLWidget-通过着色器来渲染渐变三角形,我们为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销。

    所以使用纹理(Texture)。纹理是一个2D图片(甚至也有1D和3D的纹理),你可以想象纹理是一张绘有砖块的纸,无缝折叠贴合到你的3D的房子上,这样你的房子看起来就像有砖墙外表了.
    下面你会看到之前教程的那个三角形贴上了一张砖墙图片:
    • 除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上,比如传输大量RGB数据显示一幅画面

      为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(译注:采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。

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

    纹理坐标看起来就像这样:
    float texCoords[] = { 
     0.0f, 0.0f, // 左下角 
     1.0f, 0.0f, // 右下角
     0.5f, 1.0f // 上中 
    };
    对纹理采样的解释非常宽松,它可以采用几种不同的插值方式。所以我们需要自己告诉OpenGL该怎样对纹理采样。
     
    1.QOpenGLTexture纹理对象介绍
     在QT中,通过QOpenGLTexture类封装了一个OpenGL纹理对象,QOpenGLTexture可以很容易地使用OpenGL纹理和它们提供的无数特性和目标,这取决于你的OpenGL实现的能力。
     
    QOpenGLTexture纹理的范围是从(0, 0)到(1, 1),如果超过范围后,opengl默认是重复纹理图像,当然也可以通过setWrapMode(CoordinateDirection direction, WrapMode mode)函数来重新设置,setWrapMode函数参数定义如下:
    void QOpenGLTexture::setWrapMode(CoordinateDirection direction, WrapMode mode);
    //direction:坐标方向,纹理的坐标系统和xyz坐标系统一样,s对应x,t对应y,r对应z(3D纹理时才设置z)
    //mode:纹理模式,Repeat(超出部分重复纹理)MirroredRepeat(超出部分镜像重复纹理)ClampToEdge(超出部分显示纹理临近的边缘颜色值)、

     QOpenGLTexture放大缩小的过滤方式是通过 setMinMagFilters(Filter minificationFilter, Filter magnificationFilter)函数实现,比如:

     m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest);
    //参数1:设置缩小方式 ,参数2:设置放大方式
    //设置缩小和放大的方式,缩小图片采用LinearMipMapNearest线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤

     

    具体可以设置的参数有:
    • Nearest :  邻近过滤,速度快,可能有锯齿,等同于opengl中的GL_NEAREST
    • Linear :  线性过滤,将最接近的2*2个颜色,计算出一个插值,速度慢,画面好,等同于opengl中的GL_LINEAR
    • //下面4个多级渐远纹理参数只能用在缩小方式参数1上面 
    • NearestMipMapNearest :  使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样,等同于GL_NEAREST_MIPMAP_NEAREST
    • NearestMipMapLinear :  在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样,等同于GL_NEAREST_MIPMAP_LINEAR
    • LinearMipMapNearest :  使用最邻近的多级渐远纹理级别,并使用线性插值进行采样,等同于GL_LINEAR_MIPMAP_NEAREST
    • LinearMipMapLinear :  在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样,GL_LINEAR_MIPMAP_LINEAR
    缩小之多级渐远纹理
    当纹理大于渲染屏幕时,使用纹理缩小算法(minifying)来渲染屏幕,就可以设置NearestMipMapNearest 等4个参数,比如在一个场景中,由于远处的物体只占有很少的片段(近大远小,非常远的物体看起来就像一个点),OpenGL使用高分辨率纹理为这些片段后去正确的颜色值是很困难的,它需要对一个跨过纹理很大部分的片段只拾取一个颜色,比如一个物体太远,只占有1个像素值,而该物体对应的纹理是个高分辨率图片,那么到底选用图片中哪个像素值?
    OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是将一个图像生成一系列的纹理图像,后一个纹理图像是前一个的二分之一,直到生成只有1个像素大小的图片为止,如下图所示:
    然后绘制物体时,把摄像机到物体的距离与阙值作比较,在不同的距离空间内选用不同的纹理图像。由于距离远,解析度不高也不会被用户注意到。
    所以多级渐远纹理只应用于纹理被缩小的情况下。
     
    2.源码实现
    具体代码如下所示:
    #include "myglwidget.h"
    #include <QtDebug>
    
    //GLSL3.0版本后,废弃了attribute关键字(以及varying关键字),属性变量统一用in/out作为前置关键字
    #define GL_VERSION  "#version 330 core
    "
    
    #define GLCHA(x)    #@x         //加单引号
    #define GLSTR(x)     #x            //加双引号
    #define GET_GLSTR(x) GL_VERSION#x
    
    const char *vsrc = GET_GLSTR(
    
        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 = aTexCoord;
        }
    
    );
    
    const char *fsrc =GET_GLSTR(
    
        out vec4 FragColor;
    
        in vec3 ourColor;
        in vec2 TexCoord;
    
        uniform sampler2D ourTexture;
    
        void main()
        {
            FragColor = texture(ourTexture, TexCoord);
        }
    );
    
    
    myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent)
    {
    
    }
    
    
    void myGlWidget::paintGL()
    {
       // 绘制
       // glViewport(0, 0, width(), height());
       glClear(GL_COLOR_BUFFER_BIT);
    
    
       // 渲染Shader
       vao.bind();       //绑定激活vao
       m_texture->bind();
       glDrawArrays(GL_TRIANGLES, 0, 3);    //绘制3个定点,样式为三角形
    
       m_texture->release();
       vao.release();       //解绑
    }
    
    void myGlWidget::initializeGL()
    {
    
       // 为当前环境初始化OpenGL函数
       initializeOpenGLFunctions();
    
       glClearColor(1.0f, 1.0f, 1.0f, 1.0f);    //设置背景色为白色
    
    
       //初始化纹理对象
       m_texture  = new QOpenGLTexture(QOpenGLTexture::Target2D);
       m_texture->setData(QImage(":wall1"));
       m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest);
       //设置缩小和放大的方式,缩小图片采用LinearMipMapLinear线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤
    m_texture->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat); m_texture->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat); //创建着色器程序 program = new QOpenGLShaderProgram; program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc); program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc); program->link(); program->bind();//激活Program对象 //初始化VBO,将顶点数据存储到buffer中,等待VAO激活后才能释放 float vertices[] = { // 位置 // 颜色 //纹理坐标 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 0.0f,// 右下 -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 2.0f // 顶部 }; vbo.create(); vbo.bind(); //绑定到当前的OpenGL上下文, vbo.allocate(vertices, sizeof(vertices)); vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); //设置为一次修改,多次使用 //初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等) vao.create(); vao.bind(); // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0); program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 8 * sizeof(float)); //设置aPos顶点属性 program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 8 * sizeof(float)); //设置aColor顶点颜色 program->setAttributeBuffer(2, GL_FLOAT, 6 * sizeof(float), 2, 8 * sizeof(float)); //设置纹理坐标 //offset:第一个数据的偏移量 //tupleSize:一个数据有多少个元素,比如位置为xyz,颜色为rgb,所以是3 //stride:步长,下个数据距离当前数据的之间距离,比如右下位置和左下位置之间间隔了:3个xyz值+3个rgb值,所以填入 6 * sizeof(float) program->enableAttributeArray(0); //使能aPos顶点属性 program->enableAttributeArray(1); //使能aColor顶点颜色 program->enableAttributeArray(2); //使能纹理坐标 //解绑所有对象 vao.release(); vbo.release(); } void myGlWidget::resizeEvent(QResizeEvent *e) { }
     由于我们设置的三角形纹理坐标是
      //纹理坐标
    2.0f, 0.0f,//   右下
    0.0f, 0.0f,  // 左下
    1.0f, 2.0f  //  顶部

     所以是超过了范围(0, 0)到(1, 1),假如我们绘制mode改为QOpenGLTexture::ClampToEdge,就可以看出其实三角形是大于图片的,修改代码如下:

     m_texture->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::ClampToEdge);
     m_texture->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::ClampToEdge);

    显示界面如下所示:

     在代码中,我们还保存了上章着色器颜色渲染相关代码,所以我们可以把得到的纹理颜色与顶点颜色混合,来获得更有趣的混合效果,修改fragment源码:

    FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

    编译并运行,如下图所示:

    texture()函数意义

    其中的texture函数主要是来采样不同坐标的纹理颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。

    texture函数会根据当前所在纹理坐标去获取对应的颜色。然后输出到FragColor 显示颜色.

    3.纹理叠加

    在上个源码实现中,我们在fragment源码中定义了一个uniform类型的ourTexture变量,但是我们却没有给它赋值就已经实现了纹理,这是因为默认激活的是GL_TEXTURE0,所以我们之前的操作都是针对GL_TEXTURE0(如果有一个纹理的话).
    假如有多个纹理的话,我们就需要设置其纹理位置值(也称为一个纹理单元(Texture Unit))。然后再将对应的QOpenGLTexture绑定上.

    设置如下所示:

    program->setUniformValue("texture1", 0);  //设置texture1为GL_TEXTURE0
    m_texture->bind();    //将m_texture绑定在GL_TEXTURE0上
    program->setUniformValue("texture2", 1);  //设置texture2为GL_TEXTURE1
    m_texture2->bind(1);//将m_texture2绑定在GL_TEXTURE1上
    ....

    OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

    修改fragment源码:

    #version 330 core
    ...
    
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    
    void main()
    {
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.7);
    }

    mix函数作用是将前两个纹理参数进行融合,根据第三个参数值来进行线性插值,如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.7表示返回30%的第一个输入颜色和70%的第二个输入颜色。

    然后再加入一个我的大学图片:

    最终和砖墙叠加后的效果如下所示:

     具体源码如下所示:

    #include "myglwidget.h"
    #include <QtDebug>
    
    
    //GLSL3.0版本后,废弃了attribute关键字(以及varying关键字),属性变量统一用in/out作为前置关键字
    #define GL_VERSION  "#version 330 core
    "
    
    #define GLCHA(x)    #@x         //加单引号
    #define GLSTR(x)     #x            //加双引号
    #define GET_GLSTR(x) GL_VERSION#x
    
    
    const char *vsrc = GET_GLSTR(
    
        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 = aTexCoord;
        }
    
    );
    
    const char *fsrc =GET_GLSTR(
    
        out vec4 FragColor;
    
        in vec3 ourColor;
        in vec2 TexCoord;
    
    
        uniform sampler2D texture1;
        uniform sampler2D texture2;
    
        void main()
        {
            FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.7);
        }
    
    );
    
    
    myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent)
    {
    
    }
    
    
    void myGlWidget::paintGL()
    {
       // 绘制
        int w = width();
        int h = height();
        int side = qMin(w, h);
        glViewport((w - side) / 2, (h - side) / 2, side, side);
        glClear(GL_COLOR_BUFFER_BIT);
    
    
       // 渲染Shader
       vao.bind();       //绑定激活vao
       m_texture->bind();
    
       program->setUniformValue("texture1", 0);
       m_texture->bind();
       program->setUniformValue("texture2", 1);
       m_texture2->bind(1);
    
       glDrawArrays(GL_TRIANGLE_FAN, 0, 4);    //绘制3个定点,样式为三角形
    
       m_texture->release();
       m_texture2->release();
       vao.release();       //解绑
    }
    
    void myGlWidget::initializeGL()
    {
    
       // 为当前环境初始化OpenGL函数
       initializeOpenGLFunctions();
    
       glClearColor(1.0f, 1.0f, 1.0f, 1.0f);    //设置背景色为白色
    
    
       //初始化纹理对象
       m_texture  = new QOpenGLTexture(QOpenGLTexture::Target2D);
       m_texture->setData(QImage(":wall")); //加载砖块图片
       m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest);
       //设置缩小和放大的方式,缩小图片采用LinearMipMapLinear线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤
    
       m_texture->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat);
       m_texture->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat);
    
    
       //初始化纹理对象
       m_texture2  = new QOpenGLTexture(QOpenGLTexture::Target2D);
       m_texture2->setData(QImage(":my").mirrored()); //返回图片的镜像,设置为Y轴反向,因为在opengl的Y坐标中,0.0对应的是图片底部
    
    
       m_texture2->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest);
       //设置缩小和放大的方式,缩小图片采用LinearMipMapLinear线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤
    
       m_texture2->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat);
       m_texture2->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat);
    
    
    
    
    
    
       //创建着色器程序
    
       program = new QOpenGLShaderProgram;
       program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc);
       program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc);
    
       program->link();
       program->bind();//激活Program对象
    
    
       //初始化VBO,将顶点数据存储到buffer中,等待VAO激活后才能释放
       float vertices[] = {
       //     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
            0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
            0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
           -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
           -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
       };
    
       vbo.create();
       vbo.bind();              //绑定到当前的OpenGL上下文,
       vbo.allocate(vertices, sizeof(vertices));
       vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);  //设置为一次修改,多次使用
    
    
       //初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等)
       vao.create();
       vao.bind();
    
      // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0);
       program->setAttributeBuffer(0, GL_FLOAT, 0,                  3, 8 * sizeof(float));   //设置aPos顶点属性
       program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float),  3, 8 * sizeof(float));   //设置aColor顶点颜色
       program->setAttributeBuffer(2, GL_FLOAT, 6 * sizeof(float),  2, 8 * sizeof(float));   //设置aColor顶点颜色
    
    
       //offset:第一个数据的偏移量
       //tupleSize:一个数据有多少个元素,比如位置为xyz,颜色为rgb,所以是3
       //stride:步长,下个数据距离当前数据的之间距离,比如右下位置和左下位置之间间隔了:3个xyz值+3个rgb值,所以填入 6 * sizeof(float)
    
    
       program->enableAttributeArray(0); //使能aPos顶点属性
       program->enableAttributeArray(1); //使能aColor顶点颜色
       program->enableAttributeArray(2); //使能aColor顶点颜色
    
    
       //解绑所有对象
       vao.release();
       vbo.release();
    
    
    }
    void myGlWidget::resizeEvent(QResizeEvent *e)
    {
    
    
    }
  • 相关阅读:
    NOIP2011 D1T1 铺地毯
    NOIP2013 D1T3 货车运输 倍增LCA OR 并查集按秩合并
    POJ 2513 trie树+并查集判断无向图的欧拉路
    599. Minimum Index Sum of Two Lists
    594. Longest Harmonious Subsequence
    575. Distribute Candies
    554. Brick Wall
    535. Encode and Decode TinyURL(rand and srand)
    525. Contiguous Array
    500. Keyboard Row
  • 原文地址:https://www.cnblogs.com/lifexy/p/13843319.html
Copyright © 2011-2022 走看看