關於紋理
通常遊戲裏的物體不必定都是純色的物體,物體上面會有一些圖片貼在上面,好比牆壁,箱子,地板,能夠看到磚頭、木板和大理石組成的圖片,要把圖片貼到計算機裏的幾何圖形的話,就要把圖片的顏色採樣貼到幾何圖形上,採樣是計算機常常乾的工做,計算機要處理天然中的數據就須要對數據進行採樣,好比說對聲音採樣就是採集聲音的頻率和頻幅,分別表明聲音的音色和聲量,固然,採集到的是一個模擬量,然而計算機沒法處理模擬量,因此須要將模擬量量化爲數字量,也就是二進制數進行存儲,同理,顏色也是同樣,現代計算機中顏色通常以RGB的形式,分別表明Red,Green,Blud三種顏色在這個顏色中的佔比,通常使用3個字節存儲,也就是說顏色的數量能夠達到2^24種,已經足夠用了,想要把採集到的顏色貼到物體上的話,咱們須要指定頂點的紋理座標,告訴着色器要從紋理的哪一個點開始採樣, 紋理座標的範圍是0到1
項目的代碼
learnOpenGL裏的圖解
git
紋理環繞
紋理座標的範圍是0到1,假如超出這個範圍的話,在OpenGL裏會有幾種方式來貼圖,這些方式叫作紋理環繞方式github
- GL_REPEAT 對紋理的默認行爲。重複紋理圖像。
- GL_MIRRORED_REPEAT 和GL_REPEAT同樣,但每次重複圖片是鏡像放置的。
- GL_CLAMP_TO_EDGE 紋理座標會被約束在0到1之間,超出的部分會重複紋理座標的邊緣,產生一種邊緣被拉伸的效果。
- GL_CLAMP_TO_BORDER 超出的座標爲用戶指定的邊緣顏色。
在個人工程裏有用來測試的函數SurroundTest,把環繞方式和過濾模式封裝成枚舉方便調試數組
//環繞模式 enum class SurroundMode { Repeat = GL_REPEAT, //重複紋理圖像 MirroredRepeat = GL_MIRRORED_REPEAT, //鏡像重複紋理圖像 ClampToEdge = GL_CLAMP_TO_EDGE, //將邊緣拉伸 ClampToBoreder = GL_CLAMP_TO_BORDER //超出的部分爲指定邊緣顏色 }; //過濾方式 enum class FilteringMode { Nearest = GL_NEAREST, Linear = GL_LINEAR, };
將紋理座標的份量值設置到[0,1]以外,進行測試緩存
float vertices[] = { // ---- 位置 ---- - 紋理座標 - 0.5f, 0.5f, 0.0f, 2.0f, 2.0f, // 右上 0.5f, -0.5f, 0.0f, 2.0f, -1.0f, // 右下 -0.5f, -0.5f, 0.0f, -1.0f, -1.0f, // 左下 -0.5f, 0.5f, 0.0f, -1.0f, 2.0f // 左上 };
測試結果
函數
過濾模式
能夠看到環繞方式測試的圖片是有一丟丟模糊的(實際上是用錯過濾模式了),這是由於用了線性過濾模式(Linear),使用線性過濾模式採樣的時候,每一個像素的顏色會和周圍的顏色進行混合算出一個插值,這個插值近似於這些像素
而採用 鄰近過濾(Nearest) 的話,那麼像素的顏色就是採樣器採到的顏色,若是分辨率小的圖片貼到大的物體上的話,就會出現顆粒狀的圖案,但使用線性過濾模式的話這些顆粒就會變得比較平滑
測試一下
是否是能夠很明顯地看出Linear方式採樣的珂朵莉的眼神比較溫柔啊,這就是通過平滑處理過的圖片。在這裏順便說下unity的三種過濾模式測試
- Point 點像素過濾,紋理像素會在附近變爲塊狀。
- Bilinear 雙線性過濾,平均紋理樣本
- Trilinear 三線性過濾,平均紋理樣本,同時也在多級漸遠紋理級別之間混合
測試代碼
首先是讀取圖片用的代碼,對 learnOpenGL 裏的代碼進行了一次封裝,針對不一樣位深度的圖片進行處理,位深度就是圖片記錄每一個像素點的位數,24位表示3個float,也就是RGB值,32位則是4個float,即RGBA,讀取圖片用了官方介紹的stb_image庫spa
//加載紋理 template <typename S1 = std::string> void LoadTexture(unsigned int &texture, S1&& pic) { 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; stbi_set_flip_vertically_on_load(true); unsigned char *data = stbi_load((TEXTURE_PATH + std::forward<std::string>(pic)).c_str(), &width, &height, &nrChannels, 0); std::cout << "nrChannels = " << nrChannels << endl; if (data) { //位深度爲24,3個通道(jpg if (nrChannels == 3) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); //位深度爲32,4個通道(png 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; } stbi_image_free(data); data = nullptr; }
着色器代碼
頂點着色器
vertex_3.vs3d
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 aTexCoord; out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0); TexCoord = aTexCoord; }
位置0是頂點的座標,位置1是輸入的紋理座標,要經過頂點着色器將紋理座標輸出到片斷着色器
片斷着色器
fragment_3.fs調試
#version 330 core out vec4 FragColor; in vec2 TexCoord; uniform sampler2D ourTexture; void main() { FragColor = texture(ourTexture, TexCoord); }
TexCoord是從頂點着色器接收的紋理座標,使用texture函數來採樣,第一個參數是採樣器(Sampler),第二個參數是紋理座標,着色器輸出過濾後的顏色值FragColor
接下來是就是正常的測試代碼了rest
void NormalTest() { float vertices[] = { // ---- 位置 ---- - 紋理座標 - 0.9f, 0.9f, 0.0f, 1.0f, 1.0f, // 右上 0.9f, -0.9f, 0.0f, 1.0f, 0.0f, // 右下 -0.9f, -0.9f, 0.0f, 0.0f, 0.0f, // 左下 -0.9f, 0.9f, 0.0f, 0.0f, 1.0f // 左上 }; //索引 unsigned int indices[] = { 0,1,3, 1,2,3 }; //編譯着色器 Shader ourShader("vertex_3.vs", "fragment_3.fs"); ourShader.use();//glUseProgram(shaderProgram); unsigned int VAO, VBO, EBO; //頂點數組 glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); //綁定頂點數組緩存 glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //索引緩存 glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //生成紋理 unsigned int texture1; LoadTexture(texture1, std::move("picture4.png")); //加載紋理 glBindTexture(GL_TEXTURE_2D, texture1);//綁定紋理 // 位置屬性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 紋理屬性 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); //最後一個參數是數據的起點 glEnableVertexAttribArray(1); while (!glfwWindowShouldClose(glWindow)) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); //draw glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0); glfwPollEvents(); glfwSwapBuffers(glWindow); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); glfwTerminate(); }
測試結果
珂朵莉好可愛~