Learn OpenGL (四):纹理
為了能夠把紋理映射(Map)到三角形上,我們需要指定三角形的每個頂點各自對應紋理的哪個部分。這樣每個頂點就會關聯著一個紋理坐標(Texture Coordinate),用來標明該從紋理圖像的哪個部分采樣(譯注:采集片段顏色)。之后在圖形的其它片段上進行片段插值(Fragment Interpolation)。
紋理坐標在x和y軸上,范圍為0到1之間(注意我們使用的是2D紋理圖像)。使用紋理坐標獲取紋理顏色叫做采樣(Sampling)。紋理坐標起始于(0, 0),也就是紋理圖片的左下角,終始于(1, 1),即紋理圖片的右上角。下面的圖片展示了我們是如何把紋理坐標映射到三角形上的。
我們為三角形指定了3個紋理坐標點。如上圖所示,我們希望三角形的左下角對應紋理的左下角,因此我們把三角形左下角頂點的紋理坐標設置為(0, 0);三角形的上頂點對應于圖片的上中位置所以我們把它的紋理坐標設置為(0.5, 1.0);同理右下方的頂點設置為(1, 0)。我們只要給頂點著色器傳遞這三個紋理坐標就行了,接下來它們會被傳片段著色器中,它會為每個片段進行紋理坐標的插值。
紋理坐標看起來就像這樣:
float texCoords[] = {0.0f, 0.0f, // 左下角1.0f, 0.0f, // 右下角0.5f, 1.0f // 上中
};
對紋理采樣的解釋非常寬松,它可以采用幾種不同的插值方式。所以我們需要自己告訴OpenGL該怎樣對紋理采樣。
紋理環繞方式
紋理坐標的范圍通常是從(0, 0)到(1, 1),那如果我們把紋理坐標設置在范圍之外會發生什么?OpenGL默認的行為是重復這個紋理圖像(我們基本上忽略浮點紋理坐標的整數部分),但OpenGL提供了更多的選擇:
| 環繞方式 | 描述 |
|---|---|
| GL_REPEAT | 對紋理的默認行為。重復紋理圖像。 |
| GL_MIRRORED_REPEAT | 和GL_REPEAT一樣,但每次重復圖片是鏡像放置的。 |
| GL_CLAMP_TO_EDGE | 紋理坐標會被約束在0到1之間,超出的部分會重復紋理坐標的邊緣,產生一種邊緣被拉伸的效果。 |
| GL_CLAMP_TO_BORDER | 超出的坐標為用戶指定的邊緣顏色。 |
當紋理坐標超出默認范圍時,每個選項都有不同的視覺效果輸出。我們來看看這些紋理圖像的例子:
前面提到的每個選項都可以使用glTexParameter*函數對單獨的一個坐標軸設置(s、t(如果是使用3D紋理那么還有一個r)它們和x、y、z是等價的):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一個參數指定了紋理目標;我們使用的是2D紋理,因此紋理目標是GL_TEXTURE_2D。第二個參數需要我們指定設置的選項與應用的紋理軸。我們打算配置的是WRAP選項,并且指定S和T軸。最后一個參數需要我們傳遞一個環繞方式(Wrapping),在這個例子中OpenGL會給當前激活的紋理設定紋理環繞方式為GL_MIRRORED_REPEAT。
如果我們選擇GL_CLAMP_TO_BORDER選項,我們還需要指定一個邊緣的顏色。這需要使用glTexParameter函數的fv后綴形式,用GL_TEXTURE_BORDER_COLOR作為它的選項,并且傳遞一個float數組作為邊緣的顏色值:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
紋理過濾
紋理坐標不依賴于分辨率(Resolution),它可以是任意浮點值,所以OpenGL需要知道怎樣將紋理像素(Texture Pixel,也叫Texel,譯注1)映射到紋理坐標。當你有一個很大的物體但是紋理的分辨率很低的時候這就變得很重要了。你可能已經猜到了,OpenGL也有對于紋理過濾(Texture Filtering)的選項。紋理過濾有很多個選項,但是現在我們只討論最重要的兩種:GL_NEAREST和GL_LINEAR。
譯注1
Texture Pixel也叫Texel,你可以想象你打開一張.jpg格式圖片,不斷放大你會發現它是由無數像素點組成的,這個點就是紋理像素;注意不要和紋理坐標搞混,紋理坐標是你給模型頂點設置的那個數組,OpenGL以這個頂點的紋理坐標數據去查找紋理圖像上的像素,然后進行采樣提取紋理像素的顏色。
GL_NEAREST(也叫鄰近過濾,Nearest Neighbor Filtering)是OpenGL默認的紋理過濾方式。當設置為GL_NEAREST的時候,OpenGL會選擇中心點最接近紋理坐標的那個像素。下圖中你可以看到四個像素,加號代表紋理坐標。左上角那個紋理像素的中心距離紋理坐標最近,所以它會被選擇為樣本顏色:
GL_LINEAR(也叫線性過濾,(Bi)linear Filtering)它會基于紋理坐標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大。下圖中你可以看到返回的顏色是鄰近像素的混合色:
那么這兩種紋理過濾方式有怎樣的視覺效果呢?讓我們看看在一個很大的物體上應用一張低分辨率的紋理會發生什么吧(紋理被放大了,每個紋理像素都能看到):
GL_NEAREST產生了顆粒狀的圖案,我們能夠清晰看到組成紋理的像素,而GL_LINEAR能夠產生更平滑的圖案,很難看出單個的紋理像素。GL_LINEAR可以產生更真實的輸出,但有些開發者更喜歡8-bit風格,所以他們會用GL_NEAREST選項。
當進行放大(Magnify)和縮小(Minify)操作的時候可以設置紋理過濾的選項,比如你可以在紋理被縮小的時候使用鄰近過濾,被放大時使用線性過濾。我們需要使用glTexParameter*函數為放大和縮小指定過濾方式。這段代碼看起來會和紋理環繞方式的設置很相似:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
多級漸遠紋理
stb_image.h
stb_image.h是Sean Barrett的一個非常流行的單頭文件圖像加載庫,它能夠加載大部分流行的文件格式,并且能夠很簡單得整合到你的工程之中。stb_image.h可以在這里下載。下載這一個頭文件,將它以stb_image.h的名字加入你的工程,并另創建一個新的C++文件,輸入以下代碼:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
通過定義STB_IMAGE_IMPLEMENTATION,預處理器會修改頭文件,讓其只包含相關的函數定義源碼,等于是將這個頭文件變為一個?.cpp?文件了?,F在只需要在你的程序中包含stb_image.h并編譯就可以了。
下面的教程中,我們會使用一張木箱的圖片。要使用stb_image.h加載圖片,我們需要使用它的stbi_load函數:
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
這個函數首先接受一個圖像文件的位置作為輸入。接下來它需要三個int作為它的第二、第三和第四個參數,stb_image.h將會用圖像的寬度、高度和顏色通道的個數填充這三個變量。我們之后生成紋理的時候會用到的圖像的寬度和高度的。
生成紋理
和之前生成的OpenGL對象一樣,紋理也是使用ID引用的。讓我們來創建一個:
unsigned int texture;
glGenTextures(1, &texture);
glGenTextures函數首先需要輸入生成紋理的數量,然后把它們儲存在第二個參數的unsigned int數組中(我們的例子中只是單獨的一個unsigned int),就像其他對象一樣,我們需要綁定它,讓之后任何的紋理指令都可以配置當前綁定的紋理:
glBindTexture(GL_TEXTURE_2D, texture);
現在紋理已經綁定了,我們可以使用前面載入的圖片數據生成一個紋理了。紋理可以通過glTexImage2D來生成:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
函數很長,參數也不少,所以我們一個一個地講解:
- 第一個參數指定了紋理目標(Target)。設置為GL_TEXTURE_2D意味著會生成與當前綁定的紋理對象在同一個目標上的紋理(任何綁定到GL_TEXTURE_1D和GL_TEXTURE_3D的紋理不會受到影響)。
- 第二個參數為紋理指定多級漸遠紋理的級別,如果你希望單獨手動設置每個多級漸遠紋理的級別的話。這里我們填0,也就是基本級別。
- 第三個參數告訴OpenGL我們希望把紋理儲存為何種格式。我們的圖像只有
RGB值,因此我們也把紋理儲存為RGB值。 - 第四個和第五個參數設置最終的紋理的寬度和高度。我們之前加載圖像的時候儲存了它們,所以我們使用對應的變量。
- 下個參數應該總是被設為
0(歷史遺留的問題)。 - 第七第八個參數定義了源圖的格式和數據類型。我們使用RGB值加載這個圖像,并把它們儲存為
char(byte)數組,我們將會傳入對應值。 - 最后一個參數是真正的圖像數據。
當調用glTexImage2D時,當前綁定的紋理對象就會被附加上紋理圖像。然而,目前只有基本級別(Base-level)的紋理圖像被加載了,如果要使用多級漸遠紋理,我們必須手動設置所有不同的圖像(不斷遞增第二個參數)。或者,直接在生成紋理之后調用glGenerateMipmap。這會為當前綁定的紋理自動生成所有需要的多級漸遠紋理。
生成了紋理和相應的多級漸遠紋理后,釋放圖像的內存是一個很好的習慣。
stbi_image_free(data);
生成一個紋理的過程應該看起來像這樣:
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 為當前綁定的紋理對象設置環繞、過濾方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加載并生成紋理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);
}
else
{std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
應用紋理
后面的這部分我們會使用glDrawElements繪制「你好,三角形」教程最后一部分的矩形。我們需要告知OpenGL如何采樣紋理,所以我們必須使用紋理坐標更新頂點數據:
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 // 左上
};
由于我們添加了一個額外的頂點屬性,我們必須告訴OpenGL我們新的頂點格式:
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
注意,我們同樣需要調整前面兩個頂點屬性的步長參數為8 * sizeof(float)。
接著我們需要調整頂點著色器使其能夠接受頂點坐標為一個頂點屬性,并把坐標傳給片段著色器:
#version 330 core
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;
}
片段著色器應該接下來會把輸出變量TexCoord作為輸入變量。
片段著色器也應該能訪問紋理對象,但是我們怎樣能把紋理對象傳給片段著色器呢?GLSL有一個供紋理對象使用的內建數據類型,叫做采樣器(Sampler),它以紋理類型作為后綴,比如sampler1D、sampler3D,或在我們的例子中的sampler2D。我們可以簡單聲明一個uniform sampler2D把一個紋理添加到片段著色器中,稍后我們會把紋理賦值給這個uniform。
#version 330 core
out vec4 FragColor;in vec3 ourColor;
in vec2 TexCoord;uniform sampler2D ourTexture;void main()
{FragColor = texture(ourTexture, TexCoord);
}
我們使用GLSL內建的texture函數來采樣紋理的顏色,它第一個參數是紋理采樣器,第二個參數是對應的紋理坐標。texture函數會使用之前設置的紋理參數對相應的顏色值進行采樣。這個片段著色器的輸出就是紋理的(插值)紋理坐標上的(過濾后的)顏色。
現在只剩下在調用glDrawElements之前綁定紋理了,它會自動把紋理賦值給片段著色器的采樣器:
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
如果你跟著這個教程正確地做完了,你會看到下面的圖像:
需要注意的是,圖片尺寸必須是2^n,且長寬1:1
如果你的紋理代碼不能正常工作或者顯示是全黑,請繼續閱讀,并一直跟進我們的代碼到最后的例子,它是應該能夠工作的。在一些驅動中,必須要對每個采樣器uniform都附加上紋理單元才可以,這個會在下面介紹。
我們還可以把得到的紋理顏色與頂點顏色混合,來獲得更有趣的效果。我們只需把紋理顏色與頂點顏色在片段著色器中相乘來混合二者的顏色:
FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
最終的效果應該是頂點顏色和紋理顏色的混合色:
紋理單元
你可能會奇怪為什么sampler2D變量是個uniform,我們卻不用glUniform給它賦值。使用glUniform1i,我們可以給紋理采樣器分配一個位置值,這樣的話我們能夠在一個片段著色器中設置多個紋理。一個紋理的位置值通常稱為一個紋理單元(Texture Unit)。一個紋理的默認紋理單元是0,它是默認的激活紋理單元,所以教程前面部分我們沒有分配一個位置值。
紋理單元的主要目的是讓我們在著色器中可以使用多于一個的紋理。通過把紋理單元賦值給采樣器,我們可以一次綁定多個紋理,只要我們首先激活對應的紋理單元。就像glBindTexture一樣,我們可以使用glActiveTexture激活紋理單元,傳入我們需要使用的紋理單元:
glActiveTexture(GL_TEXTURE0); // 在綁定紋理之前先激活紋理單元
glBindTexture(GL_TEXTURE_2D, texture);
激活紋理單元之后,接下來的glBindTexture函數調用會綁定這個紋理到當前激活的紋理單元,紋理單元GL_TEXTURE0默認總是被激活,所以我們在前面的例子里當我們使用glBindTexture的時候,無需激活任何紋理單元。
OpenGL至少保證有16個紋理單元供你使用,也就是說你可以激活從GL_TEXTURE0到GL_TEXTRUE15。它們都是按順序定義的,所以我們也可以通過GL_TEXTURE0 + 8的方式獲得GL_TEXTURE8,這在當我們需要循環一些紋理單元的時候會很有用。
我們仍然需要編輯片段著色器來接收另一個采樣器。這應該相對來說非常直接了:
#version 330 core
...uniform sampler2D texture1;
uniform sampler2D texture2;void main()
{FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
著色器里的texture1 2跟cpp里的unsigned int texture1, texture2;不是一個東西
最終輸出顏色現在是兩個紋理的結合。GLSL內建的mix函數需要接受兩個值作為參數,并對它們根據第三個參數進行線性插值。如果第三個值是0.0,它會返回第一個輸入;如果是1.0,會返回第二個輸入值。0.2會返回80%的第二個輸入顏色和20%的第一個輸入顏色,即返回兩個紋理的混合色。
?mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2)
我們現在需要載入并創建另一個紋理;你應該對這些步驟很熟悉了。記得創建另一個紋理對象,載入圖片,使用glTexImage2D生成最終紋理。對于第二個紋理我們使用一張你學習OpenGL時的面部表情圖片。
為了使用第二個紋理(以及第一個),我們必須改變一點渲染流程,先綁定兩個紋理到對應的紋理單元,然后定義哪個uniform采樣器對應哪個紋理單元:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
我們還要通過使用glUniform1i設置每個采樣器的方式告訴OpenGL每個著色器采樣器屬于哪個紋理單元。我們只需要設置一次即可,所以這個會放在渲染循環的前面:
ourShader.use(); // 別忘記在激活著色器前先設置uniform!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手動設置
ourShader.setInt("texture2", 1); // 或者使用著色器類設置while(...)
{[...]
}
通過使用glUniform1i設置采樣器,我們保證了每個uniform采樣器對應著正確的紋理單元。你應該能得到下面的結果:
你可能注意到紋理上下顛倒了!這是因為OpenGL要求y軸0.0坐標是在圖片的底部的,但是圖片的y軸0.0坐標通常在頂部。很幸運,stb_image.h能夠在圖像加載時幫助我們翻轉y軸,只需要在加載任何圖像前加入以下語句即可:
stbi_set_flip_vertically_on_load(true);
在讓stb_image.h在加載圖片時翻轉y軸之后你就應該能夠獲得下面的結果了:
思考:
嘗試用不同的紋理環繞方式,設定一個從0.0f到2.0f范圍內的(而不是原來的0.0f到1.0f)紋理坐標。試試看能不能在箱子的角落放置4個笑臉
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <learnopengl/shader_s.h>
#include <iostream>// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordinglyvoid processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);
}// glfw: whenever the window size changed (by OS or user resize) this callback function executesvoid framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height);
}int main()
{// glfw: initialize and configureglfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creationGLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointersif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader zprogramShader ourShader("4.3.texture.vs", "4.3.texture.fs");// set up vertex data (and buffer(s)) and configure vertex attributesfloat vertices[] = {// positions // colors // texture coords (note that we changed them to 2.0f!)0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 2.0f, // top right0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 2.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 2.0f // top left };unsigned int indices[] = {0, 1, 3, // first triangle1, 2, 3 // second triangle};unsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);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);// position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// texture coord attributeglVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));glEnableVertexAttribArray(2);// load and create a texture unsigned int texture1, texture2;// texture 1glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1);// set the texture wrapping parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // note that we set the container wrapping method to GL_CLAMP_TO_EDGEglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// set texture filtering parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// load image, create texture and generate mipmapsint width, height, nrChannels;stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.// The FileSystem::getPath(...) is part of the GitHub repository so we can find files on any IDE/platform; replace it with your own image path.unsigned char *data = stbi_load(FileSystem::getPath("resources/textures/container.jpg").c_str(), &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);// texture 2// ---------glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);// set the texture wrapping parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // we want to repeat the awesomeface pattern so we kept it at GL_REPEATglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// set texture filtering parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// load image, create texture and generate mipmapsdata = stbi_load(FileSystem::getPath("resources/textures/awesomeface.png").c_str(), &width, &height, &nrChannels, 0);if (data){// note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBAglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);// tell opengl for each sampler to which texture unit it belongs to (only has to be done once)ourShader.use(); // don't forget to activate/use the shader before setting uniforms!// either set it manually like so:glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);// or set it via the texture classourShader.setInt("texture2", 1);// render loopwhile (!glfwWindowShouldClose(window)){// input// -----processInput(window);// renderglClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// bind textures on corresponding texture unitsglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);// render containerourShader.use();glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)glfwSwapBuffers(window);glfwPollEvents();}// optional: de-allocate all resources once they've outlived their purpose:glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);// glfw: terminate, clearing all previously allocated GLFW resources.glfwTerminate();return 0;
}
?
總結
以上是生活随笔為你收集整理的Learn OpenGL (四):纹理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Learn OpenGL (三):着色器
- 下一篇: Learn OpenGL (五):向量