在上章3.QOpenGLWidget-通過著色器來渲染漸變三角形,我們為每個頂點新增顏色來增加圖形的細節,從而建立出有趣的影像。但是,如果想讓圖形看起來更真實,我們就必須有足夠多的頂點,從而指定足夠多的顏色。這將會產生很多額外開銷。
- 除了影像以外,紋理也可以被用來儲存大量的資料,這些資料可以傳送到著色器上,比如傳輸大量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 // 上中 };
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
#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);
編譯並執行,如下圖所示:
3.紋理疊加
在上個原始碼實現中,我們在fragment原始碼中定義了一個uniform型別的ourTexture變數,但是我們卻沒有給它賦值就已經實現了紋理,這是因為如果有一個紋理的話,預設是啟用的.
假如有多個紋理的話,我們就需要設定其紋理位置值(也稱為一個紋理單元(Texture Unit))。然後再將對應的QOpenGLTexture繫結上.
設定如下所示:
program->setUniformValue("texture1", 0); m_texture->bind(); //將m_texture繫結在"texture1"上 program->setUniformValue("texture2", 1); m_texture2->bind(1);//將m_texture2繫結在"texture1"上 ....
修改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) { }