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\n"

#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\n"

#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)
{


}

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM