[翻譯] 現(xiàn)代OpenGL教程 02 - 貼圖

<img src="http://dn-huangweipro.qbox.me/static/img/opengl-tutorials/modern-opengl-02.png" width="60%">

在本文中污桦,我們將給三角形加一個(gè)貼圖灵迫,這需要在頂點(diǎn)和片段著色器中加入一些新變量,創(chuàng)建和使用貼圖對象阱持,并且學(xué)習(xí)一點(diǎn)貼圖單元和貼圖坐標(biāo)的知識。

本文會使用兩個(gè)新的類到tdogl命名空間中:tdogl:Bitmaptdogl:Texture呀洲。這些類允許我們將jpg紊选,png或bmp圖片上傳到顯存并用于著色器。tdogl:Program類也增加一些相關(guān)接口道逗。

獲取代碼

所有例子代碼的zip打包可以從這里獲缺铡:https://github.com/tomdalling/opengl-series/archive/master.zip

這一系列文章中所使用的代碼都存放在:https://github.com/tomdalling/opengl-series滓窍。你可以在頁面中下載zip卖词,加入你會git的話,也可以復(fù)制該倉庫吏夯。

本文代碼你可以在<code>source/02_textures</code>目錄里找到此蜈。使用OS X系統(tǒng)的,可以打開根目錄里的opengl-series.xcodeproj噪生,選擇本文工程裆赵。使用Windows系統(tǒng)的,可以在Visual Studio 2013里打開opengl-series.sln跺嗽,選擇相應(yīng)工程战授。

工程里已包含所有依賴,所以你不需要再安裝或者配置額外的東西桨嫁。如果有任何編譯或運(yùn)行上的問題植兰,請聯(lián)系我。

著色器變量Uniform與Attribute

教程一里的著色器變量都是attribute璃吧,本文介紹另外一種類型的變量:uniform變量楣导。

著色器變量有兩種類型:uniformattributeattribute變量可以在每個(gè)頂點(diǎn)上有不同值畜挨。而uniform變量在多個(gè)頂點(diǎn)上保持相同值筒繁。比如,你想要給一個(gè)三角形設(shè)置一種顏色巴元,那你應(yīng)該使用uniform變量毡咏,如果你希望每個(gè)三角形頂點(diǎn)有不同顏色,你應(yīng)該使用attribute變量务冕。從這開始血当,我稱呼他們?yōu)椤皍niforms”和“attributes”幻赚。

Uniforms能被任意著色器訪問禀忆,但是Attributes必須先進(jìn)入頂點(diǎn)著色器臊旭,而非片段著色器。頂點(diǎn)著色器在需要時(shí)會將該值傳給片段著色器箩退。這因?yàn)閁niforms像常量-它們不會被任何著色器更改离熏。然而,Attributes不是常量戴涝。頂點(diǎn)著色器會改變Attribute變量的值滋戳,在片段著色器獲取之前。就是說啥刻,頂點(diǎn)著色器的輸出就是片段著色器的輸入奸鸯。

為了設(shè)置Uniform的值,我們可以調(diào)用glUniform*系列函數(shù)可帽。而設(shè)置Attribute的值娄涩,我們需要在VBO中保存,并且和VAO一起發(fā)送給著色器映跟,就像前一篇教程里的glVertexAttribPointer蓄拣。加入你不想把值存在VBO里,你也可以使用glVertexAttrib*系列函數(shù)來設(shè)置Attribute值努隙。

貼圖

貼圖球恤,大體上來說就是你應(yīng)用在3D物體上的2D圖像。它有其它用途荸镊,但顯示2D圖像在3D幾何上是最常用的咽斧。有1D,2D贷洲,3D貼圖收厨,但本文只講2D貼圖。更深入閱讀优构,請參見Learning Modern 3D Graphics Programming書中的Textures are not Pictures章節(jié)诵叁。

貼圖是存放在顯存里的。那就是說钦椭,你需要在使用之前上傳你的貼圖數(shù)據(jù)給顯卡拧额。這類似VBO在前文的作用-VBO也是在使用之前需要存放到顯存上。

貼圖的高和寬需要是2的冪次方彪腔。比如16侥锦,32,64德挣,128恭垦,256,512。本文中使用的是256*256的圖像作為貼圖番挺,如下圖所示唠帝。

hazard.png
hazard.png

我們使用tdogl:Bitmap來加載“hazard.png”的原始像素?cái)?shù)據(jù)到內(nèi)存中,參見stb_image幫助文檔玄柏。然后我們使用tdogl:Texture上傳原始像素?cái)?shù)據(jù)給OpenGL貼圖對象襟衰。幸運(yùn)的是OpenGL中的貼圖創(chuàng)建方法從面世到現(xiàn)在都沒有實(shí)質(zhì)性的變化,所以網(wǎng)上有大量的創(chuàng)建貼圖的好文章粪摘。雖然貼圖坐標(biāo)的傳輸方式有變化瀑晒,但創(chuàng)建貼圖還是跟以前一樣。

以下是tdogl:Texture的構(gòu)造函數(shù)徘意,用于OpenGL貼圖創(chuàng)建苔悦。

Texture::Texture(const Bitmap& bitmap, GLint minMagFiler, GLint wrapMode) :
    _originalWidth((GLfloat)bitmap.width()),
    _originalHeight((GLfloat)bitmap.height())
{
    glGenTextures(1, &_object);
    glBindTexture(GL_TEXTURE_2D, _object);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minMagFiler);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, minMagFiler);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    glTexImage2D(GL_TEXTURE_2D,
                 0, 
                 TextureFormatForBitmapFormat(bitmap.format()),
                 (GLsizei)bitmap.width(), 
                 (GLsizei)bitmap.height(),
                 0, 
                 TextureFormatForBitmapFormat(bitmap.format()), 
                 GL_UNSIGNED_BYTE, 
                 bitmap.pixelBuffer());
    glBindTexture(GL_TEXTURE_2D, 0);
}

貼圖坐標(biāo)

毫無疑問,貼圖坐標(biāo)就是貼圖上的坐標(biāo)椎咧。關(guān)于貼圖坐標(biāo)比較奇特的是它們不是以像素為單位间坐。它們范圍是從0到1,(0, 0)是左下角邑退,(1, 1)是右上角竹宋。假如你上傳到OpenGL的圖像是顛倒的,那(0, 0)就是左上角地技,而非左下角蜈七。將像素坐標(biāo)轉(zhuǎn)換為貼圖坐標(biāo),你必須除上貼圖的寬和高莫矗。比如飒硅,在256*256的圖像中,像素坐標(biāo)(128, 256)的貼圖坐標(biāo)是(0.5, 1)作谚。

uv_coords.png
uv_coords.png

貼圖坐標(biāo)通常被稱為UV坐標(biāo)三娩。你也可以叫它們是XY坐標(biāo),但是XYZ通常被用來表示頂點(diǎn)妹懒,我們不希望將這兩者混淆雀监。

貼圖圖像單元

貼圖圖像單元,亦或簡稱“貼圖單元”眨唬,是在OpenGL中略怪異的一部分会前。你無法直接發(fā)送貼圖給著色器。首先匾竿,你要綁定貼圖到貼圖單元瓦宜,然后呢要發(fā)送貼圖單元的索引給著色器

對于貼圖單元是有數(shù)量限制的。在低端硬件上岭妖,如手機(jī)临庇,它們只有兩個(gè)貼圖單元反璃。既然如此,即使我們有許多的貼圖假夺,我們也只能同時(shí)使用兩個(gè)貼圖單元在著色器中版扩。我們在本文中只用到了一個(gè)貼圖,所以也只需要一個(gè)貼圖單元侄泽,但它可以在多個(gè)不同的著色器中混合。

實(shí)現(xiàn)貼圖

首先蜻韭,讓我們創(chuàng)建一個(gè)新的全局貼圖悼尾。

tdogl::Texture* gTexture = NULL;

我們?yōu)榧虞d“hazard.png”圖片新增一個(gè)函數(shù)。該函數(shù)能被AppMain所調(diào)用肖方。

static void LoadTexture() {
    tdogl::Bitmap bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("hazard.png"));
    bmp.flipVertically();
    gTexture = new tdogl::Texture(bmp);
}

下一步闺魏,我們給每個(gè)三角形的頂點(diǎn)一個(gè)貼圖坐標(biāo)。假如你跟上圖比較過UV坐標(biāo)俯画,就可以看出按順序這個(gè)坐標(biāo)表示(中析桥,上),(左艰垂,下)和(右泡仗,下)。

GLfloat vertexData[] = {
    //  X     Y     Z       U     V
     0.0f, 0.8f, 0.0f,   0.5f, 1.0f,
    -0.8f,-0.8f, 0.0f,   0.0f, 0.0f,
     0.8f,-0.8f, 0.0f,   1.0f, 0.0f,
};

現(xiàn)在我們需要修改片段著色器猜憎,使得它能使用貼圖和貼圖坐標(biāo)作為輸入娩怎。下面是新的片段著色器代碼:

#version 150
uniform sampler2D tex; //this is the texture
in vec2 fragTexCoord; //this is the texture coord
out vec4 finalColor; //this is the output color of the pixel

void main() {
    finalColor = texture(tex, fragTexCoord);
}

uniform關(guān)鍵字說明texuniform變量。貼圖是一致的胰柑,因?yàn)樗腥切雾旤c(diǎn)有相同的貼圖截亦。sampler2D是變量類型,說明它包含一個(gè)2D貼圖柬讨。

fragTexCoordattribute變量崩瓤,因?yàn)槊總€(gè)三角形頂點(diǎn)是不同的貼圖坐標(biāo)。

texture函數(shù)是用來查找給定貼圖坐標(biāo)的像素顏色踩官。在GLSL舊版本中却桶,你應(yīng)該使用texture2D函數(shù)來實(shí)現(xiàn)該功能。

我們無法直接傳送attribute給判斷著色器蔗牡,因?yàn)?em>attribute必須首先通過頂點(diǎn)著色器肾扰。這兒是修改過的頂點(diǎn)著色器:

#version 150
in vec3 vert;
in vec2 vertTexCoord;
out vec2 fragTexCoord;

void main() {
    // Pass the tex coord straight through to the fragment shader
    fragTexCoord = vertTexCoord;
    
    gl_Position = vec4(vert, 1);
}

頂點(diǎn)著色器使用vertTexCoord作為輸入,并且將它不經(jīng)修改蛋逾,直接傳給名為fragTexCoordattribute片段著色器變量集晚。

著色器有兩個(gè)變量需要我們設(shè)置:vertTexCoordattribute變量和texuniform變量。讓我們從設(shè)置tex變量開始区匣。打開main.cpp偷拔,找到Render()函數(shù)蒋院。我們在繪制三角形之前設(shè)置texuniform變量:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gTexture->object());
gProgram->setUniform("tex", 0); //set to 0 because the texture is bound to GL_TEXTURE0

貼圖在沒有綁定到貼圖單元時(shí),是無法使用的莲绰。glActiveTexture告訴OpenGL我們希望使用哪個(gè)貼圖單元欺旧。GL_TEXTURE0是第一個(gè)貼圖單元,我們就使用它蛤签。

下一本辞友,我們使用glBindTexture來綁定我們的貼圖到激活的貼圖單元。

然后我們設(shè)置貼圖單元索引給texuniform著色器變量震肮。我們使用0號貼圖單元称龙,所以我們設(shè)置tex變量為整數(shù)0setUniform方法只是調(diào)用了glUnifrom1i函數(shù)戳晌。

最后一步鲫尊,獲取貼圖坐標(biāo)給vertTexCoordattribute變量。為了實(shí)現(xiàn)它沦偎,我們需要修改LoadTriangle()函數(shù)中的VAO疫向。之前的代碼是這樣的:

// Put the three triangle vertices into the VBO
GLfloat vertexData[] = {
    //  X     Y     Z
     0.0f, 0.8f, 0.0f,
    -0.8f,-0.8f, 0.0f,
     0.8f,-0.8f, 0.0f
};

// connect the xyz to the "vert" attribute of the vertex shader
glEnableVertexAttribArray(gProgram->attrib("vert"));
glVertexAttribPointer(gProgram->attrib("vert"), 3, GL_FLOAT, GL_FALSE, 0, NULL);

現(xiàn)在我們需要改成這樣:

// Put the three triangle vertices (XYZ) and texture coordinates (UV) into the VBO
GLfloat vertexData[] = {
    //  X     Y     Z       U     V
     0.0f, 0.8f, 0.0f,   0.5f, 1.0f,
    -0.8f,-0.8f, 0.0f,   0.0f, 0.0f,
     0.8f,-0.8f, 0.0f,   1.0f, 0.0f,
};

// connect the xyz to the "vert" attribute of the vertex shader
glEnableVertexAttribArray(gProgram->attrib("vert"));
glVertexAttribPointer(gProgram->attrib("vert"), 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), NULL);
    
// connect the uv coords to the "vertTexCoord" attribute of the vertex shader
glEnableVertexAttribArray(gProgram->attrib("vertTexCoord"));
glVertexAttribPointer(gProgram->attrib("vertTexCoord"), 2, GL_FLOAT, GL_TRUE,  5*sizeof(GLfloat), (const GLvoid*)(3 * sizeof(GLfloat)));

我們第二次調(diào)用了glVertexAttribPointer,但我們也修改了第一個(gè)調(diào)用豪嚎。最重要的是最后兩個(gè)參數(shù)搔驼。

兩個(gè)glVertexAttribPointer調(diào)用的倒數(shù)第二個(gè)參數(shù)都是5*sizeof(GLfloat)。這是“步長”參數(shù)侈询。該參數(shù)是表明每個(gè)值開始位置的間隔是多少字節(jié)匙奴,或者說是到下個(gè)值開始的字節(jié)數(shù)。在兩個(gè)調(diào)用中妄荔,每個(gè)值是5個(gè)GLFloat長度泼菌。舉個(gè)例子,加入我們從“X”開始啦租,往前數(shù)5個(gè)值哗伯,我們會落在下個(gè)“X”值上。從“U”開始也一樣篷角,也是往前數(shù)5個(gè)焊刹。該參數(shù)是字節(jié)單位,不是浮點(diǎn)作為單位恳蹲,所以我們必須乘上浮點(diǎn)類型所占字節(jié)數(shù)虐块。

最后一個(gè)參數(shù)glVertexAttribPointer是一個(gè)“偏移”參數(shù)。該參數(shù)需要知道從開始到第一個(gè)值有多少字節(jié)嘉蕾。開始是XYZ贺奠,所以偏移設(shè)置為NULL表示“到開始的距離為0字節(jié)”。第一個(gè)UV不在最前面-中間有3個(gè)浮點(diǎn)的距離错忱。再說一遍儡率,參數(shù)是以字節(jié)為單位挂据,而非浮點(diǎn),所以我們必須乘上浮點(diǎn)類型所占字節(jié)數(shù)儿普。并且我們必須將數(shù)值轉(zhuǎn)為const GLvoid*類型崎逃,因?yàn)樵谂f版本的OpenGL中該參數(shù)有別于現(xiàn)在的“偏移”。

現(xiàn)在眉孩,當(dāng)你運(yùn)行程序个绍,你就能看到如本文最上方的那個(gè)三角形。

下篇預(yù)告

下一篇教程中我們會學(xué)一些矩陣相關(guān)的東西浪汪,使用矩陣來旋轉(zhuǎn)立方體巴柿,移動(dòng)相機(jī),和添加透視投影吟宦。我們還會學(xué)習(xí)深度緩沖和基于時(shí)間更新的邏輯,比如動(dòng)畫涩维。

更多OpenGL貼圖相關(guān)資源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殃姓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瓦阐,更是在濱河造成了極大的恐慌蜗侈,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睡蟋,死亡現(xiàn)場離奇詭異踏幻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)戳杀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門该面,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人信卡,你說我怎么就攤上這事隔缀。” “怎么了傍菇?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵猾瘸,是天一觀的道長。 經(jīng)常有香客問我丢习,道長牵触,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任咐低,我火速辦了婚禮揽思,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘见擦。我一直安慰自己绰更,他們只是感情好瞧挤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著儡湾,像睡著了一般特恬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徐钠,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天癌刽,我揣著相機(jī)與錄音,去河邊找鬼尝丐。 笑死显拜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爹袁。 我是一名探鬼主播远荠,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼失息!你這毒婦竟也來了譬淳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤盹兢,失蹤者是張志新(化名)和其女友劉穎邻梆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绎秒,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浦妄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了见芹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂娄。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖玄呛,靈堂內(nèi)的尸體忽然破棺而出宜咒,到底是詐尸還是另有隱情,我是刑警寧澤把鉴,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布故黑,位于F島的核電站,受9級特大地震影響庭砍,放射性物質(zhì)發(fā)生泄漏场晶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一怠缸、第九天 我趴在偏房一處隱蔽的房頂上張望诗轻。 院中可真熱鬧,春花似錦揭北、人聲如沸扳炬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨樟。三九已至半醉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劝术,已是汗流浹背缩多。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留养晋,地道東北人衬吆。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像绳泉,于是被迫代替她去往敵國和親逊抡。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容