日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OpenGL从入门到精通--纹理

發布時間:2025/3/15 编程问答 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OpenGL从入门到精通--纹理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

紋理

github源碼倉庫

opengl環境準備

opengl編程從入門到精通-hello,window

OpenGL從入門到精通–你好三角形

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 // 上中 }; // 要繪制區域和對應紋理圖的區域向對應 float vertices[] = {// positions // colors // texture coords0.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 };

使用紋理,取值繪制坐標會從對應紋理的位置取圖,并進行紋理的繪制

紋理環繞方式

紋理坐標的范圍通常是從(0, 0)到(1, 1),那如果我們把紋理坐標設置在范圍之外會發生什么?OpenGL默認的行為是重復這個紋理圖像(我們基本上忽略浮點紋理坐標的整數部分),但OpenGL提供了更多的選擇:

環繞方式描述
GL_REPEAT對紋理的默認行為。重復紋理圖像。
GL_MIRRORED_REPEAT和GL_REPEAT一樣,但每次重復圖片是鏡像放置的。
GL_CLAMP_TO_EDGE紋理坐標會被約束在0到1之間,超出的部分會重復紋理坐標的邊緣,產生一種邊緣被拉伸的效果。
GL_CLAMP_TO_BORDER超出的坐標為用戶指定的邊緣顏色。

GL_REPEAT

GL_MIRRORED_REPEAT注意這個是鏡像的方式進行擴展

GL_CLAMP_TO_EDGE邊緣拉伸效果

GL_CLAMP_TO_BORDER因為用戶沒有指定,因此這里沒有指定的地方被填充為黑色了。

GL_TEXTURE_2D第一個參數說明紋理目標是2D。第二個參數是指定設置參數的紋理軸。

// GL_REPEAT 垂直軸 // GL_TEXTURE_WRAP_T 水平軸 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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);

紋理過濾

紋理不依賴分辨率,當一個很大但是紋理分辨率很低時,需要使用紋理過濾選項,先討論兩種重要的選項:GL_NEAREST和GL_LINEA

GL_NEAREST(也叫鄰近過濾,Nearest Neighbor Filtering)是OpenGL默認的紋理過濾方式。當設置為GL_NEAREST的時候,OpenGL會選擇中心點最接近紋理坐標的那個像素。下圖中你可以看到四個像素,加號代表紋理坐標。左上角那個紋理像素的中心距離紋理坐標最近,所以它會被選擇為樣本顏色:

GL_LINEAR(也叫線性過濾,(Bi)linear Filtering)它會基于紋理坐標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大。

那么這兩種紋理過濾方式有怎樣的視覺效果呢?讓我們看看在一個很大的物體上應用一張低分辨率的紋理會發生什么吧(紋理被放大了,每個紋理像素都能看到):

GL_NEAREST產生了顆粒狀的圖案,我們能夠清晰看到組成紋理的像素,而GL_LINEAR能夠產生更平滑的圖案,很難看出單個的紋理像素。GL_LINEAR可以產生更真實的輸出.

當進行放大(Magnify)和縮小(Minify)操作的時候可以設置紋理過濾的選項,比如你可以在紋理被縮小的時候使用鄰近過濾,被放大時使用線性過濾。我們需要使用glTexParameter*函數為放大和縮小指定過濾方式。這段代碼看起來會和紋理環繞方式的設置很相似:

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

多級漸遠紋理

假設我們有一個包含成千上萬個物體的大房間,每個物體上都有紋理,有些物體很遠,看起來很小,就不需要分辨率很高的紋理。

OpenGL使用一種叫做多級漸遠紋理Mipmap的概念解決了這個問題,簡單的來說就i事一系列的紋理圖像,后一個是前一個的二分之一,OpenGL會根據距離觀察者的遠近,使用適合的紋理,由于隨著距離的增加,解析度不高也不會被用戶注意到。同事多級漸進紋理的另一個優點就是它的性能非常好。

OpenGL中使用 glGenerateMipmaps函數會創建紋理。后面章節會講到。

在渲染中切換多級漸遠紋理級別(Level)時,OpenGL在兩個不同級別的多級漸遠紋理層之間會產生不真實的生硬邊界。就像普通的紋理過濾一樣,切換多級漸遠紋理級別時你也可以在兩個不同多級漸遠紋理級別之間使用NEAREST和LINEAR過濾。為了指定不同多級漸遠紋理級別之間的過濾方式,你可以使用下面四個選項中的一個代替原有的過濾方式:

過濾方式描述
GL_NEAREST_MIPMAP_NEAREST使用最鄰近的多級漸遠紋理來匹配像素大小,并使用鄰近插值進行紋理采樣
GL_LINEAR_MIPMAP_NEAREST使用最鄰近的多級漸遠紋理級別,并使用線性插值進行采樣
GL_NEAREST_MIPMAP_LINEAR在兩個最匹配像素大小的多級漸遠紋理之間進行線性插值,使用鄰近插值進行采樣
GL_LINEAR_MIPMAP_LINEAR在兩個鄰近的多級漸遠紋理之間使用線性插值,并使用線性插值進行采樣

就像紋理過濾一樣,我們可以使用glTexParameteri將過濾方式設置為前面四種提到的方法之一:

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

一個常見的錯誤是,將放大過濾的選項設置為多級漸遠紋理過濾選項之一。這樣沒有任何效果,因為多級漸遠紋理主要是使用在紋理被縮小的情況下的:紋理放大不會使用多級漸遠紋理,為放大過濾設置多級漸遠紋理的選項會產生一個GL_INVALID_ENUM錯誤代碼。

加載與創建紋理

紋理的加載,紋理圖像可以被存儲為各種各樣的格式,每種都有自己的數據格式,我們可以根據不同的文件格式實現自己的文件加載,把圖像轉換為自己序列,但是使用庫函數更加快一點 – stb_image.h庫。

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 文件了。現在只需要在你的程序中包含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繪制[「你好,三角形」](https://learnopengl-cn.github.io/01 Getting started/04 Hello Triangle/)教程最后一部分的矩形。我們需要告知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);

我們還可以把得到的紋理顏色與頂點顏色混合,來獲得更有趣的效果。我們只需把紋理顏色與頂點顏色在片段著色器中相乘來混合二者的顏色:

FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

最終的效果應該是頂點顏色和紋理顏色的混合色:

我猜你會說我們的箱子喜歡跳70年代的迪斯科。

紋理單元

你可能會奇怪為什么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); }

最終輸出顏色現在是兩個紋理的結合。GLSL內建的mix函數需要接受兩個值作為參數,并對它們根據第三個參數進行線性插值。如果第三個值是0.0,它會返回第一個輸入;如果是1.0,會返回第二個輸入值。0.2會返回80%的第一個輸入顏色和20%的第二個輸入顏色,即返回兩個紋理的混合色。

先綁定紋理到對應的紋理單元,然后定義哪兒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軸之后你就應該能夠獲得下面的結果了:

當然也可以通過更改shader的fs函數實現在x軸上的鏡像。

更改紋理坐標:

float vertices[] = {// positions // colors // texture coords0.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 };

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的OpenGL从入门到精通--纹理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。