OpenGL 紋理基礎(chǔ)與索引

前言

OpenGL的紋理實際上運(yùn)用十分廣泛,是OpenGL中的重點。如果你有看過Android底層的繪制原理枪向,能夠發(fā)現(xiàn)實際上忍宋,一般的ui界面,Android把會把像素點當(dāng)作紋理數(shù)據(jù)繪制在屏幕上湃缎。

因此還是有必要稍微學(xué)習(xí)一下OpenGL的紋理九巡。本文講述的是OpenGL的紋理基礎(chǔ)冕广。

如果在本文遇到什么問題,請在http://www.reibang.com/p/9c58cd895fa5這里聯(lián)系本人

正文

紋理介紹

從前面幾節(jié)OpenGL我們可以清楚睬辐,OpenGL可以結(jié)合頂點數(shù)組對象,頂點緩存對象丰刊,使用OpenGL命令藻三,生成各種圖形棵帽。但是逗概,有沒有想過逾苫,如果需要圖像看起來十分真實铅搓,就需要足夠多的頂點星掰,指定足夠多的顏色怀偷。這樣會產(chǎn)生許多額外的開銷。

因此维蒙,誕生了紋理。紋理是一個2D圖片(甚至也有1D八千,3D的紋理)燎猛,他可以添加物體細(xì)節(jié)恋捆≈乇粒可以想像沸停,實際上OpenGL可以建立了一個模型昭卓,但是還需要很多圖像的細(xì)節(jié)愤钾,為了添加細(xì)節(jié)候醒,把這個帶著圖像的紙貼在模型上能颁。

除了圖像以外倒淫,紋理也可以用來存儲大量的數(shù)據(jù),可以把數(shù)據(jù)發(fā)送到著色器上镜硕。

比如說矩欠,我們可以把一個磚塊的紋理貼在上兩章的三角形上峰伙。


image.png

為了把紋理正確的映射到模型上,我們必須要知道紋理映射的坐標(biāo)该默,指定三角形每個對應(yīng)上紋理的哪個部分瞳氓。

紋理坐標(biāo)系

下面是上圖的紋理坐標(biāo):


紋理坐標(biāo)系.png

能看到這個紋理坐標(biāo)和之前的OpenGL的頂點坐標(biāo)系不太一樣,這邊的坐標(biāo)系是以左下角為原點栓袖。

記得匣摘,這坐標(biāo)系和頂點坐標(biāo)系思路上不太一樣。頂點坐標(biāo)系相當(dāng)于在一個三維空間中裹刮,找個位置構(gòu)建一個2d/3d的模型音榜。

但是紋理坐標(biāo)系,一旦紋理加載了圖片之后捧弃,想要獲取到完整的圖片赠叼,就要x軸(0,1)和y軸(0,1)整個的區(qū)域才能把這個圖片讀取出來。

舉個例子:
如上圖违霞,我們希望添的圖片左下角能對應(yīng)三角形的左下角嘴办,右下角對應(yīng)右下角,頂部對應(yīng)三角形的中心买鸽。
那么我們可以創(chuàng)建一個如下的紋理坐標(biāo)系:

float texCoords[] = {
    0.0f, 0.0f, // 左下角
    1.0f, 0.0f, // 右下角
    0.5f, 1.0f // 上中
};

但是涧郊,注意到?jīng)]有,這是個正方形的區(qū)域眼五,如果遇到長方形的圖片怎么辦妆艘。OpenGL其實有自己的策略。這個稍后會繼續(xù)談到看幼。

紋理環(huán)繞方式

注意到紋理坐標(biāo)系的問題批旺,這里就有OpenGL提供的幾種解決這個問題的思路。

紋理坐標(biāo)一般在(0,0),(1,1)浮動诵姜,如果我們紋理坐標(biāo)設(shè)置到了坐標(biāo)之外朱沃,那么會發(fā)生什么?OpenGL會默認(rèn)重復(fù)這個圖像茅诱。

但是實際上OpenGL除了這些之外還提供了其他的方式:

環(huán)繞方式 描述
GL_REPEAT 對紋理的默認(rèn)行為逗物。重復(fù)紋理圖像。
GL_MIRRORED_REPEAT 和GL_REPEAT一樣瑟俭,但每次重復(fù)圖片是鏡像放置的翎卓。
GL_CLAMP_TO_EDGE 紋理坐標(biāo)會被約束在0到1之間,超出的部分會重復(fù)紋理坐標(biāo)的邊緣摆寄,產(chǎn)生一種邊緣被拉伸的效果失暴。
GL_CLAMP_TO_BORDER 超出的坐標(biāo)為用戶指定的邊緣顏色坯门。
image.png

那么反過來說,如果小于當(dāng)前的坐標(biāo)呢逗扒?
OpenGL將會截取一部分的圖像貼在整個模型上面古戴。之后會在實戰(zhàn)演練中見識到現(xiàn)象。

OpenGL在設(shè)置紋理的時候矩肩,可以設(shè)置紋理單元的繪制參數(shù)现恼,調(diào)用如下方法:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

這個glTexParameteri,是設(shè)置紋理之前的做的參數(shù)設(shè)置黍檩,相當(dāng)于構(gòu)建構(gòu)建一個紋理繪制的坐標(biāo)之類的參數(shù)叉袍。
第一個參數(shù)是指:我們要繪制2d紋理,第二個參數(shù)是指:我們要設(shè)定的紋理軸中的行為刽酱,第三個參數(shù)是指:當(dāng)這個紋理軸上的紋理超過了紋理坐標(biāo)的范圍喳逛,的表現(xiàn)形式。

在紋理中存在著s,t,r三種坐標(biāo)軸:


image.png

一般繪制2D棵里,就在s和t軸上繪制润文。s我們可以類比x軸,t可以類比y軸殿怜。

如果我們選擇了GL_CLAMP_TO_BORDER转唉,如果當(dāng)前的圖像不是正方形,圖像紋理將會把圖像壓縮到(0,0),(1,1)之間稳捆,剩下的部分會用黑色填充赠法。我們可以傳遞一個float數(shù)組作為邊緣的顏色值。

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

紋理過濾

紋理過濾實際上是對紋理中圖像像素的處理策略乔夯。

紋理坐標(biāo)不依賴分辨率砖织,它可以是任意的浮點值,所以O(shè)penGL需要知道紋理像素怎么映射到紋理坐標(biāo)系中末荐。當(dāng)你有一個很大的物體但是紋理分辨率很低的時候就很重要了侧纯。

因此OpenGL提供了很多紋理過濾的方式,其中兩種十分重要甲脏,這兩種方式也會在OpenCV中涉及到這塊東西的講解:

  • 1.鄰近過濾(GL_NEAREST)
  • 2.線性過濾(GL_LINEAR)

鄰近過濾

鄰近過濾是OpenGL默認(rèn)的方式眶熬。當(dāng)設(shè)置GL_NEAREST,OpenGL會挑選中心點最接近紋理坐標(biāo)的像素值块请。


鄰近過濾.png

線性過濾

線性過濾會基于紋理坐標(biāo)附近紋理像素值娜氏,做一個插值計算,計算出近似周邊的像素值墩新。一個紋理像素中心距離紋理坐標(biāo)越近贸弥,其貢獻(xiàn)越大。這就有點像一個掩碼操作海渊,通過一個核算出一個相關(guān)性绵疲。

下圖你能看到返回的是一個周邊像素的混合色:


線性過濾.png

通過兩者的比較我們不難發(fā)現(xiàn)哲鸳,線性過濾的圖像會柔和一點,而鄰近過濾的圖像就會增加對比度一點盔憨。

實際上這個操作和OpenCV的filter過濾器有這同工異曲之妙徙菠。當(dāng)我們放大像素就有如下的效果:


image.png

能夠發(fā)現(xiàn)GL_NEAREST更加偏向像素風(fēng)格,每個顆粒會很大郁岩。而GL_LINEAR會平滑一點婿奔。

OpenCV對應(yīng)的api

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

多級漸遠(yuǎn)紋理

假設(shè)一下,當(dāng)一個空間有上千物體驯用,就需要上千紋理脸秽。有些紋理很遠(yuǎn)儒老,其紋理難以和近的紋理難以有一個分辨率蝴乔。由于遠(yuǎn)處的物體可能只產(chǎn)生很少的片段,OpenGL從高分辨率紋理中為這些片段獲取正確的顏色值就很困難驮樊,因為它需要對一個跨過紋理很大部分的片段只拾取一個紋理顏色薇正。在小物體上這會產(chǎn)生不真實的感覺,更不用說對它們使用高分辨率紋理浪費(fèi)內(nèi)存的問題了囚衔。

為了解決這個問題挖腰,OpenGL使用一種叫做多級漸遠(yuǎn)紋理(Mipmap)的概念來解決。

簡單來說练湿,他是一系列的紋理圖像猴仑,后者是前者的1/2。其原理很簡單肥哎,當(dāng)距離觀察者超過一定的閾值辽俗,OpenGL會使用不同的多級漸遠(yuǎn)紋理來處理,選擇最適合當(dāng)前距離的紋理篡诽。

由于距離遠(yuǎn)崖飘,解析度不高也不會被用戶注意到。同時杈女,多級漸遠(yuǎn)紋理另一加分之處是它的性能非常好朱浴。讓我們看一下多級漸遠(yuǎn)紋理是什么樣子的:


多級漸遠(yuǎn)紋理

手工創(chuàng)建多個紋理比較復(fù)雜,因此OpenGL提供了glGenerateMipmaps方法設(shè)置一系列多級漸遠(yuǎn)紋理.

在渲染過程中达椰,切換多級漸遠(yuǎn)紋理級別(Level)時翰蠢,OpenGL在兩個不同級別的多級漸遠(yuǎn)紋理層之間會產(chǎn)生不真實的生硬邊界,就像普通的紋理過濾一樣啰劲,切換多級漸遠(yuǎn)紋理級別時你也可以在兩個不同多級漸遠(yuǎn)紋理級別之間使用NEAREST和LINEAR過濾躏筏。

切換紋理方式 描述
GL_NEAREST_MIPMAP_NEAREST 使用最近鄰的多級漸遠(yuǎn)紋理來匹配像素大小,并使用近鄰插值進(jìn)行紋理過濾
GL_LINEAR_MIPMAP_NEAREST 使用最近鄰的多級漸遠(yuǎn)紋理來匹配像素大小呈枉,并且使用線性插值起進(jìn)行紋理過濾趁尼。
GL_NEAREST_MIPMAP_LINEAR 在兩個最匹配像素大小的多級漸遠(yuǎn)紋理之間進(jìn)行線性插值埃碱,使用鄰近插值進(jìn)行采樣
GL_LINEAR_MIPMAP_LINEAR 在兩個鄰近的多級漸遠(yuǎn)紋理之間使用線性插值,并使用線性插值進(jìn)行采樣

OpenGL提供的api:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

GL_TEXTURE_MIN_FILTER代表縮小操作時候的紋理操作酥泞,GL_TEXTURE_MAG_FILTER代表漸遠(yuǎn)紋理級別切換時候操作砚殿。

注意一個常見的錯誤,放大不會使用多級漸遠(yuǎn)紋理芝囤。使用了會報錯似炎。

紋理常規(guī)開發(fā)流程

紋理的常見實際上和VAO,VBO十分相似悯姊。都是走一個套路羡藐,通過Gen函數(shù)創(chuàng)建對象,bind函數(shù)綁定對象悯许,最后再傳輸數(shù)據(jù)仆嗦。

首先通過下面這個函數(shù),常見一個紋理對象:

GLuint texture;
glGenTextures(1,&texture);

綁定:

glBindTexture(GL_TEXTURE_2D,texture);

設(shè)置紋理坐標(biāo)的參數(shù):

 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    
    //    float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
    //    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
    //設(shè)置紋理過濾
    //縮小時候
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

最后再傳輸數(shù)據(jù):

 glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
 glGenerateMipmap(GL_TEXTURE_2D);

針對glTexImage2D進(jìn)行講解:

  • 1.第一個參數(shù):指定了紋理目標(biāo)先壕。設(shè)置為GL_TEXTURE_2D意味著會生成與當(dāng)前綁定的紋理對象在同一個目標(biāo)上的紋理(任何綁定到GL_TEXTURE_1D和GL_TEXTURE_3D的紋理不會受到影響)瘩扼。

  • 2.第二個參數(shù):第二個參數(shù)為紋理指定多級漸遠(yuǎn)紋理的級別,如果你希望單獨(dú)手動設(shè)置每個多級漸遠(yuǎn)紋理的級別的話垃僚。這里我們填0集绰,也就是基本級別。

  • 3.第三個參數(shù)告訴OpenGL我們希望把紋理儲存為何種格式谆棺。我們的圖像只有RGB值栽燕,因此我們也把紋理儲存為RGB值(記住要符合圖片通道數(shù),如果是4通道是RGBA)改淑。

  • 4.第四個和第五個參數(shù)設(shè)置最終的紋理的寬度和高度碍岔。我們之前加載圖像的時候儲存了它們,所以我們使用對應(yīng)的變量溅固。

  • 5.下個參數(shù)應(yīng)該總是被設(shè)為0(歷史遺留的問題)

  • 6.第七第八個參數(shù)定義了源圖的格式和數(shù)據(jù)類型付秕。我們使用RGB值加載這個圖像,并把它們儲存為char(byte)數(shù)組侍郭,我們將會傳入對應(yīng)值

  • 7.最后一個參數(shù)是真正的圖像數(shù)據(jù)

當(dāng)調(diào)用glTexImage2D時询吴,當(dāng)前綁定的紋理對象就會被附加上紋理圖像。然而亮元,目前只有基本級別(Base-level)的紋理圖像被加載了猛计,如果要使用多級漸遠(yuǎn)紋理,我們必須手動設(shè)置所有不同的圖像(不斷遞增第二個參數(shù))爆捞》盍觯或者,直接在生成紋理之后調(diào)用glGenerateMipmap。這會為當(dāng)前綁定的紋理自動生成所有需要的多級漸遠(yuǎn)紋理盗温。

索引繪制

在這里稍微聊聊索引藕赞。實際上,在我的第一篇OpenGL學(xué)習(xí)中卖局,發(fā)現(xiàn)當(dāng)我們繪制一個三角形會指定3個坐標(biāo)斧蜕,一個矩形就需要2個三角形,6個頂點組成三角形組合起來砚偶。

但是實際上這種操作會浪費(fèi)內(nèi)存批销,而且有很多冗余的頂點數(shù)據(jù)。為了解決這種情況OpenGL引入了索引的概念染坯。

舉一個例子:

不實用索引去繪制矩形均芽,需要6個頂點,其中有2個頂點是冗余的单鹿。

float vertices[] = {
    // 第一個三角形
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, 0.5f, 0.0f,  // 左上角
    // 第二個三角形
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

但是如果我們使用索引掀宋,只需要創(chuàng)建如下,2個數(shù)組,一個是頂點數(shù)組羞反,一個是索引數(shù)組:

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

這樣我們就能通過索引找到這些頂點布朦,復(fù)用頂點并且繪制2個三角形囤萤。

索引的用法

同樣的配方:
生成一個Gen對象:

GLuint EBO;
glGenBuffers(1, &EBO);

綁定對象昼窗,并且輸入頂點信息:

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

但是繪制頂點的方法卻是出現(xiàn)了變化:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

glDrawElements 第一個參數(shù)指定了我們繪制的模式,這個和glDrawArrays的一樣涛舍。第二個參數(shù)是我們打算繪制頂點的個數(shù)澄惊,這里填6,也就是說我們一共需要繪制6個頂點富雅。第三個參數(shù)是索引的類型掸驱,這里是GL_UNSIGNED_INT。最后一個參數(shù)里我們可以指定EBO中的偏移量(或者傳遞一個索引數(shù)組没佑,但是這是當(dāng)你不在使用索引緩沖對象的時候)毕贼,但是我們會在這里填寫0。

我們會使用glDrawElements蛤奢,繪制6個頂點鬼癣,其順序是依照索引來繪制。這樣就能繪制出一個矩形啤贩。

在這里解釋一下待秃,此時索引數(shù)組有兩個:

    1. 0,1痹屹,3
  • 2.1章郁,2,3
    這些索引會根據(jù)之前編寫好的glVertexAttribPointer志衍,去尋找頂點數(shù)組對象對應(yīng)緩存對象的數(shù)組暖庄,找到對應(yīng)的頂點信息聊替。

因此畫出來的如下圖:


image.png

這里稍微解釋一下索引繪制的存儲原理。glDrawElements會通過GL_ELEMENT_ARRAY_BUFFER目標(biāo)的EBO中獲取索引培廓。這意味著我們每一次渲染索引都需要綁定一次索引佃牛。不過頂點數(shù)組對象,同樣可以保存索引的綁定狀態(tài)医舆,就和VBO一樣俘侠。

VAO綁定時正在綁定的索引緩沖對象會被保存為VAO的元素緩沖對象。綁定VAO的同時也會自動綁定EBO蔬将。如下圖:


索引綁定.png

當(dāng)目標(biāo)是GL_ELEMENT_ARRAY_BUFFER的時候爷速,VAO會儲存glBindBuffer的函數(shù)調(diào)用。這也意味著它也會儲存解綁調(diào)用霞怀,所以確保你沒有在解綁VAO之前解綁索引數(shù)組緩沖惫东,否則它就沒有這個EBO配置了。

因此當(dāng)我們調(diào)用glDelete函數(shù)的時候毙石,記得最后才銷毀VAO廉沮。

實戰(zhàn)演練

繪制箱子

當(dāng)我們熟知了紋理以及索引繪制的基礎(chǔ)知識之后,開始實戰(zhàn)環(huán)節(jié)徐矩。我們嘗試著把一個箱子繪制到一個矩形中滞时。

接下來的源碼,為了避免和前面的文章產(chǎn)生重復(fù)性滤灯,在閱讀了Android 的源碼之后坪稽,抽象了一個渲染引擎。這里面還是很簡單鳞骤,只是做一個OpenGL渲染環(huán)境的初始化窒百,讓我們集中本文所學(xué)習(xí)到的知識。

首先初始化環(huán)境豫尽。

 RenderEngine *engine = new RenderEngine();
    GLuint VAO;
    GLuint VBO;
    GLuint EBO;
    GLuint texture[] = {1};
    const char* vPath = "/Users/yjy/Desktop/iOS workspcae/first_opengl/Texture/vertex.glsl";
    const char* fPath = "/Users/yjy/Desktop/iOS workspcae/first_opengl/Texture/fragment.glsl";
    Shader *shader = new Shader(vPath,fPath);
    shader->compile();

接下來篙梢,靈活運(yùn)用上一篇文章的知識,創(chuàng)建VAO美旧,VBO渤滞,EBO(索引)。

//要畫矩形陈症,因此要兩個三角形
void flushRetriangle(GLuint& VAO,GLuint& VBO,GLuint& EBO){
    float vertices[] = {
        //位置
        // 右上角          //顏色              //紋理
        0.5f, 0.5f, 0.0f,   1.0f,0.0f,0.0f,  2.0f,2.0f,
        // 右下角
        0.5f, -0.5f, 0.0f,  0.0f,1.0f,0.0f,  2.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,2.0f
    };
    
    unsigned int indices[] = { // 注意索引從0開始!
        0, 1, 3, // 第一個三角形
        1, 2, 3  // 第二個三角形
    };
    
    //分配VAO
    glGenVertexArrays(1,&VAO);
    //分配VBO
    glGenBuffers(1,&VBO);
    //分配EBO
    glGenBuffers(1,&EBO);
    
    //綁定
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER,VBO);
    
    //設(shè)置緩存數(shù)據(jù)
    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);
    
    
    //告訴OpenGL怎么讀取數(shù)據(jù)
    //分成三次來讀取
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,8 * sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,8* sizeof(float),(void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2,2,GL_FLOAT,GL_FALSE,8* sizeof(float),(void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
    

    
    //解綁
    glBindBuffer(GL_ARRAY_BUFFER,0);
    //glBindTexture(GL_TEXTURE_2D,0);
    glBindVertexArray(0);
   
}

稍微解釋一下蔼水,其核心原理,實際上就是把頂點录肯,顏色趴腋,紋理都集中在一個地方處理,并且告訴OpenGL應(yīng)該怎么讀取整個數(shù)據(jù)。

因為有三種數(shù)據(jù)要讀取优炬,所以要告訴OpenGL讀取數(shù)據(jù)該怎么移動颁井,從哪里讀取。并且設(shè)置3中l(wèi)ocation蠢护,放在頂點著色器中解析雅宾。

  • 1.頂點數(shù)據(jù),是float型葵硕,每次讀取3個眉抬,每讀取完一次就移動8sizeof(float)的大小(作者是4字節(jié)大小8),從第0個位置開始讀取。
  • 2.顏色數(shù)據(jù)懈凹,是float型蜀变,每次讀取3個,每讀取完一次就移動8*sizeof(float)的大小介评,從第3個位置開始讀取.
  • 3.紋理坐標(biāo)數(shù)據(jù)库北,是float型,每次讀取2個们陆,每讀取完一次就移動8*sizeof(float)的大小寒瓦,從第6個位置開始讀取.

這樣就能正確的讀取到緩存中所有的數(shù)據(jù)。

image.png

最后再設(shè)置好繪制紋理的環(huán)境參數(shù)坪仇,以及讀取紋理數(shù)據(jù)杂腰。

bool initTexture(GLuint& texture,const char* str,bool isResver){
    //設(shè)置紋理信息
    //生成和綁定
    glGenTextures(1,&texture);
    glBindTexture(GL_TEXTURE_2D,texture);
    //綁定當(dāng)前的紋理對象,設(shè)置環(huán)繞烟很,過濾的方式
    //設(shè)置S軸和T軸颈墅,環(huán)繞方式
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    
//    float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
//    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
    //設(shè)置紋理過濾
    //縮小時候
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    //切換級別
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    
    //加載圖片生成wenli數(shù)據(jù)
    int width ,height,nrChannels;
    stbi_set_flip_vertically_on_load(isResver);
    GLubyte *data = stbi_load(str, &width, &height, &nrChannels, 0);

    if(data){
        if(nrChannels == 3){
            glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width,height,0,GL_RGB,GL_UNSIGNED_BYTE,data);
        }else if(nrChannels == 4){
            glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
        }
        
        glGenerateMipmap(GL_TEXTURE_2D);
    }else{
        std::cout << "Failed to load texture" << std::endl;
        return false;
    }
    
    stbi_image_free(data);
    
    return true;
}

這里在讀取圖片數(shù)據(jù)的時候蜡镶,我借助了stbi庫去讀取雾袱,它就是一個頭文件,直接引入就能使用了官还。
設(shè)置了環(huán)繞類型為GL_REPEAT類型芹橡,紋理過濾是GL_LINEAR .
注意了,這里我們需要判斷顏色通道望伦,當(dāng)在顏色通道在3的時候使用RGB林说,顏色通道為4的時候,使用RGBA屯伞,只有正確的設(shè)置了顏色通道才能正確的讀取到圖像數(shù)據(jù)腿箩。

關(guān)于顏色通道具體內(nèi)容,可以看我的OpenCV的教程劣摇。能看到為什么Android系統(tǒng)時候的順序是RGBA的順序而不是OpenCV的ABGR的順序珠移,也是因為Android繪制底層使用了OpenGL進(jìn)行繪制啊。

這里是源碼的下半部分:

    flushRetriangle(VAO, VBO, EBO);

    
    initTexture(texture[0], "/Users/yjy/Desktop/opengl/container.jpg",false);

    
    if(shader&&shader->isCompileSuccess()){
        
        shader->use();
        
        engine->loop(VAO, VBO, texture, 1,shader, [](Shader* shader,GLuint VAO,

            
            //箱子
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D,texture[0]);

            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
        });
    }

最后再來看看頂點著色器

#version 330 core
layout(location = 0)in vec3 aPos;
layout(location = 1)in vec3 aColor;
layout(location = 2)in vec2 aTexCoord;

out vec2 TexCoord;

void main(){
    gl_Position = vec4(aPos,1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

還有片元著色器

#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main(){
    FragColor = texture(ourTexture, TexCoord);
}

image.png

這樣就能看到結(jié)果圖了。

在箱子上面加一個笑臉钧惧,并且添加圖片權(quán)重

你一定很奇怪為什么sampler2D采樣器是一個uniform暇韧,我們卻不用glUniform給它賦值。

使用glUniform1i浓瞪,我們可以給紋理采樣器分配一個位置值懈玻,這樣的話我們能夠在一個片段著色器中設(shè)置多個紋理。一個紋理的位置值通常稱為一個紋理單元(Texture Unit)乾颁。一個紋理的默認(rèn)紋理單元是0涂乌,它是默認(rèn)的激活紋理單元,所以教程前面部分我們沒有分配一個位置值英岭。

紋理單元的主要目的是讓我們在著色器中可以使用多于一個的紋理骂倘。通過把紋理單元賦值給采樣器,我們可以一次綁定多個紋理巴席,只要我們首先激活對應(yīng)的紋理單元历涝。就像glBindTexture一樣,我們可以使用glActiveTexture激活紋理單元漾唉,傳入我們需要使用的紋理單元:

glActiveTexture(GL_TEXTURE0); // 在綁定紋理之前先激活紋理單元
glBindTexture(GL_TEXTURE_2D, texture);

激活紋理單元之后荧库,接下來的glBindTexture函數(shù)調(diào)用會綁定這個紋理到當(dāng)前激活的紋理單元,紋理單元GL_TEXTURE0默認(rèn)總是被激活赵刑,所以我們在前面的例子里當(dāng)我們使用glBindTexture的時候分衫,無需激活任何紋理單元。

OpenGL至少保證有16個紋理單元供你使用般此,也就是說你可以激活從GL_TEXTURE0到GL_TEXTRUE15蚪战。它們都是按順序定義的,所以我們也可以通過GL_TEXTURE0 + 8的方式獲得GL_TEXTURE8铐懊,這在當(dāng)我們需要循環(huán)一些紋理單元的時候會很有用邀桑。

因此為了讓這個片元著色器可以讀取兩個紋理數(shù)據(jù),此時要在片元著色器多建立一個采樣器科乎,并且為其賦值id壁畸。并且調(diào)用glsl中的mix函數(shù)來為兩張圖片添加權(quán)重

#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;
uniform sampler2D ourTexture2;

void main(){
    //mix = mix(x,y,a) = x * (1-a) + y * a
    //箱子 * 0.8 + 笑臉 * 0.2
    FragColor = mix(texture(ourTexture, TexCoord),texture(ourTexture2, TexCoord),0.2);
}
    initTexture(texture[0], "/Users/yjy/Desktop/opengl/container.jpg",false);
    initTexture(texture[1], "/Users/yjy/Desktop/opengl/awesomeface.png",true);
    
    if(shader&&shader->isCompileSuccess()){
        
        shader->use();
        //設(shè)置的是紋理單元
        glUniform1i(glGetUniformLocation(shader->ID,"ourTexture"),0);
        shader->setInt("ourTexture2", 1);
        
        engine->loop(VAO, VBO, texture, 1,shader, [](Shader* shader,GLuint VAO,
                                                     GLuint* texture,GLFWwindow *window){
        
            
            //箱子
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D,texture[0]);
            
            //笑臉
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D,texture[1]);
            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
        });
    }

能夠發(fā)現(xiàn),此時采樣次的id實際上和glActiveTexture激活的GL_TEXTUREN是一一對應(yīng)的茅茂。


image.png

改變紋理范圍

當(dāng)我們嘗試這改變整個紋理范圍捏萍,看看效果如何。

float vertices[] = {
        //位置
        // 右上角          //顏色              //紋理
        0.5f, 0.5f, 0.0f,   1.0f,0.0f,0.0f,  2.0f,2.0f,
        // 右下角
        0.5f, -0.5f, 0.0f,  0.0f,1.0f,0.0f,  2.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,2.0f
    };

當(dāng)超過1的時候:


image.png

此時將會采用環(huán)繞方式空闲,為重復(fù)令杈,把這個圖片添加到擴(kuò)大的地方。

當(dāng)小于1的時候:

   //位置
        // 右上角          //顏色              //紋理
        0.5f, 0.5f, 0.0f,   1.0f,0.0f,0.0f,  0.5f,0.5f,
        // 右下角
        0.5f, -0.5f, 0.0f,  0.0f,1.0f,0.0f,  0.5f,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,0.5f
    };
image.png

能看到此時就是只有圖片的四分之一,左下角碴倾。

總結(jié)

當(dāng)了解了這些差不多逗噩,也就對紋理坐標(biāo)有了初步的認(rèn)識悔常。下面有一個流程圖,可以稍微闡述紋理坐標(biāo)系给赞,頂點坐標(biāo)系的關(guān)系机打。


紋理映射過程.png

可以看見紋理的繪制的過程經(jīng)歷了兩次不同的坐標(biāo)系,這個必須記住了片迅。明白了這個残邀,掌握紋理的基礎(chǔ)已經(jīng)不遠(yuǎn)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柑蛇,一起剝皮案震驚了整個濱河市芥挣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耻台,老刑警劉巖空免,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盆耽,居然都是意外死亡蹋砚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門摄杂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坝咐,“玉大人,你說我怎么就攤上這事析恢∧幔” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵映挂,是天一觀的道長泽篮。 經(jīng)常有香客問我,道長柑船,這世上最難降的妖魔是什么帽撑? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮椎组,結(jié)果婚禮上油狂,老公的妹妹穿的比我還像新娘。我一直安慰自己寸癌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布弱贼。 她就那樣靜靜地躺著蒸苇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吮旅。 梳的紋絲不亂的頭發(fā)上溪烤,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天味咳,我揣著相機(jī)與錄音,去河邊找鬼檬嘀。 笑死槽驶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸳兽。 我是一名探鬼主播掂铐,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼揍异!你這毒婦竟也來了全陨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤衷掷,失蹤者是張志新(化名)和其女友劉穎辱姨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戚嗅,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雨涛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了懦胞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镜悉。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖医瘫,靈堂內(nèi)的尸體忽然破棺而出侣肄,到底是詐尸還是另有隱情,我是刑警寧澤醇份,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布稼锅,位于F島的核電站,受9級特大地震影響僚纷,放射性物質(zhì)發(fā)生泄漏矩距。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一怖竭、第九天 我趴在偏房一處隱蔽的房頂上張望锥债。 院中可真熱鬧,春花似錦痊臭、人聲如沸哮肚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允趟。三九已至,卻和暖如春鸦致,著一層夾襖步出監(jiān)牢的瞬間潮剪,已是汗流浹背涣楷。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留抗碰,地道東北人狮斗。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像弧蝇,于是被迫代替她去往敵國和親碳褒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353