學習自:
https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/
先上一波效果圖:
實際上就是:畫了一個矩形,然后貼了兩張圖,下面是一個木窗,上面一個笑臉。
首先放上這次教程所需要的貼圖和庫文件的百度雲
鏈接:https://pan.baidu.com/s/1Ejn65QoYW11cDukiC6ZFjg
提取碼:hl93
(1)添加需要的庫文件
我們本次教程的流程,用到了本地資源中的圖片,讀取和使用本地圖片,需要使用一個新的庫:stb_image.h
這里我已經下載好了,你們可以直接下載我的百度雲,找到需要的頭文件,然后加到自己的項目目錄中。
(2)編寫需要的shader類:這里的shader類和我們上一節的教程中是一樣的
#ifndef SHADER_H #define SHADER_H #include <glad/glad.h> // 包含glad來獲取所有的必須OpenGL頭文件 #include <string> #include <fstream> #include <sstream> #include <iostream> class Shader { public: // 程序ID unsigned int ID; // 構造器讀取並構建着色器 Shader(const GLchar* vertexPath, const GLchar* fragmentPath); // 使用/激活程序 void use(); // uniform工具函數 void setBool(const std::string &name, bool value) const; void setInt(const std::string &name, int value) const; void setFloat(const std::string &name, float value) const; private: void checkCompileErrors(unsigned int shader, std::string type); }; #endif
#include "shader_s.h" Shader::Shader(const GLchar * vertexPath, const GLchar * fragmentPath) { // 1. 從文件路徑中獲取頂點/片段着色器 std::string vertexCode; std::string fragmentCode; std::ifstream vShaderFile; std::ifstream fShaderFile; // 保證ifstream對象可以拋出異常: vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); try { // 打開文件 vShaderFile.open(vertexPath); fShaderFile.open(fragmentPath); std::stringstream vShaderStream, fShaderStream; // 讀取文件的緩沖內容到數據流中 vShaderStream << vShaderFile.rdbuf(); fShaderStream << fShaderFile.rdbuf(); // 關閉文件處理器 vShaderFile.close(); fShaderFile.close(); // 轉換數據流到string vertexCode = vShaderStream.str(); fragmentCode = fShaderStream.str(); } catch (std::ifstream::failure e) { std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl; } const char* vShaderCode = vertexCode.c_str(); const char * fShaderCode = fragmentCode.c_str(); // 2. 編譯着色器 unsigned int vertex, fragment; // 頂點着色器 vs vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &vShaderCode, NULL); glCompileShader(vertex); checkCompileErrors(vertex, "VERTEX"); // 片段着色器 fs fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &fShaderCode, NULL); glCompileShader(fragment); checkCompileErrors(fragment, "FRAGMENT"); // 着色器程序 ID = glCreateProgram(); glAttachShader(ID, vertex); glAttachShader(ID, fragment); glLinkProgram(ID); checkCompileErrors(ID, "PROGRAM"); // 刪除着色器,它們已經鏈接到我們的程序中了,已經不再需要了 glDeleteShader(vertex); glDeleteShader(fragment); } void Shader::use() { glUseProgram(ID); } void Shader::setBool(const std::string & name, bool value) const { glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); } void Shader::setInt(const std::string & name, int value) const { glUniform1i(glGetUniformLocation(ID, name.c_str()), value); } void Shader::setFloat(const std::string & name, float value) const { glUniform1f(glGetUniformLocation(ID, name.c_str()), value); } void Shader::checkCompileErrors(unsigned int shader, std::string type) { int success; char infoLog[1024]; if (type != "PROGRAM") { glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 1024, NULL, infoLog); std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; } } else { glGetProgramiv(shader, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader, 1024, NULL, infoLog); std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; } } }
(3)編寫shader腳本,這里有三個版本,大家可以直接放上(三)然后寫完主程序后,再調整(一)和(二)
文件目錄可以像我這樣創建:
(一)僅放一張貼圖
a)texture.vs頂點着色器:
#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; }
b)texture.vs片段着色器:
#version 330 core
out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D ourTexture; void main() { FragColor = texture(ourTexture, TexCoord); }
此時我們的運行結果是這樣:
(二)第一張貼圖的基礎上再加上漸變色(上一個教程的三色漸變)
修改的地方是我們的片段着色器
texture.fs
#version 330 core out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D ourTexture; void main() {
//我們只需把紋理顏色與頂點顏色在片段着色器中相乘來混合二者的顏色: FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0); }
此時效果圖如下:
(三)貼兩張紋理
頂點着色器texture.vs
#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; }
片段着色器texture.fs
#version 330 core out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D texture1; uniform sampler2D texture2; uniform sampler2D ourTexture; void main() {
//GLSL內建的mix函數需要接受兩個值作為參數,並對它們根據第三個參數進行線性插值。
//如果第三個值是0.0
,它會返回第一個輸入;如果是1.0
,會返回第二個輸入值。//0.2
會返回80%
的第一個輸入顏色和20%
的第二個輸入顏色,即返回兩個紋理的混合色。 FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2); }
最終輸出顏色現在是兩個紋理的結合:
(4)編寫主程序:
關鍵代碼的注釋我已經翻譯並且加上去,一些常規代碼可以忽略或者把英文注釋翻一下吧
1 #include <glad/glad.h> 2 #include <GLFW/glfw3.h> 3 4 #include "stb_image.h" 5 #include "shader_s.h" 6 #include <iostream> 7 8 void framebuffer_size_callback(GLFWwindow* window, int width, int height); 9 void processInput(GLFWwindow *window); 10 11 // settings 12 const unsigned int SCR_WIDTH = 800; 13 const unsigned int SCR_HEIGHT = 600; 14 15 int main() 16 { 17 // glfw: initialize and configure 18 // ------------------------------ 19 glfwInit(); 20 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 21 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 22 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 23 24 #ifdef __APPLE__ 25 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X 26 #endif 27 28 // glfw window creation 29 // -------------------- 30 GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); 31 if (window == NULL) 32 { 33 std::cout << "Failed to create GLFW window" << std::endl; 34 glfwTerminate(); 35 return -1; 36 } 37 glfwMakeContextCurrent(window); 38 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); 39 40 // glad: load all OpenGL function pointers 41 // --------------------------------------- 42 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) 43 { 44 std::cout << "Failed to initialize GLAD" << std::endl; 45 return -1; 46 } 47 48 // build and compile our shader zprogram 49 // ------------------------------------ 50 Shader ourShader("../res/textures/texture.vs", "../res/textures/texture.fs"); 51 52 // set up vertex data (and buffer(s)) and configure vertex attributes 53 // ------------------------------------------------------------------ 54 float vertices[] = { 55 // 位置信息 // 顏色信息 // 紋理 coords 56 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上 57 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下 58 -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下 59 -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上 60 }; 61 unsigned int indices[] = { 62 0, 1, 3, // 第一個三角形 63 1, 2, 3 // 第二個三角形 64 }; 65 unsigned int VBO, VAO, EBO; 66 glGenVertexArrays(1, &VAO); 67 glGenBuffers(1, &VBO); 68 glGenBuffers(1, &EBO); 69 70 glBindVertexArray(VAO); 71 72 glBindBuffer(GL_ARRAY_BUFFER, VBO); 73 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 74 75 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 76 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 77 78 // position attribute 79 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); 80 glEnableVertexAttribArray(0); 81 // color attribute 82 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); 83 glEnableVertexAttribArray(1); 84 // texture coord attribute 85 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); 86 glEnableVertexAttribArray(2); 87 88 89 // 加載並創建紋理 90 // ------------------------- 91 unsigned int texture1, texture2; 92 // 第一張紋理 93 94 glGenTextures(1, &texture1); 95 glBindTexture(GL_TEXTURE_2D, texture1); 96 // 為當前綁定的紋理對象設置環繞、過濾方式 97 // 將紋理包裝設置為GL_REPEAT(默認包裝方法) 98 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 99 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 100 // 設置紋理過濾參數 101 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 102 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 103 // 加載並生成紋理 104 int width, height, nrChannels; 105 stbi_set_flip_vertically_on_load(true); //告訴stb_image.h在y軸上翻轉加載的紋理。 106 107 unsigned char *data = stbi_load("../res/textures/container.jpg", &width, &height, &nrChannels, 0); 108 if (data) 109 { 110 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); 111 glGenerateMipmap(GL_TEXTURE_2D); 112 } 113 else 114 { 115 std::cout << "Failed to load texture" << std::endl; 116 } 117 stbi_image_free(data); 118 119 120 // texture 2 121 glGenTextures(1, &texture2); 122 glBindTexture(GL_TEXTURE_2D, texture2); 123 // set the texture wrapping parameters 124 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 125 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 126 // set texture filtering parameters 127 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 128 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 129 // load image, create texture and generate mipmaps 130 data = stbi_load("../res/textures/awesomeface.png", &width, &height, &nrChannels, 0); 131 if (data) 132 { 133 //請注意,awesomeface.png具有透明度,因此具有alpha通道, 134 //因此請務必告訴OpenGL數據類型為GL_RGBA 135 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); 136 glGenerateMipmap(GL_TEXTURE_2D); 137 } 138 else 139 { 140 std::cout << "Failed to load texture" << std::endl; 141 } 142 stbi_image_free(data); 143 144 //告訴每個采樣器的opengl它屬於哪個紋理單元(只需要做一次) 145 ourShader.use(); //激活着色器 146 // either set it manually like so: 147 glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); 148 // or set it via the texture class 149 ourShader.setInt("texture2", 1); 150 151 152 153 // render loop 154 // ----------- 155 while (!glfwWindowShouldClose(window)) 156 { 157 // input 158 // ----- 159 processInput(window); 160 161 // render 162 // ------ 163 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 164 glClear(GL_COLOR_BUFFER_BIT); 165 166 // bind textures on corresponding texture units 167 glActiveTexture(GL_TEXTURE0); 168 glBindTexture(GL_TEXTURE_2D, texture1); 169 glActiveTexture(GL_TEXTURE1); 170 glBindTexture(GL_TEXTURE_2D, texture2); 171 172 // render container 173 ourShader.use(); 174 glBindVertexArray(VAO); 175 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 176 177 // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) 178 // ------------------------------------------------------------------------------- 179 glfwSwapBuffers(window); 180 glfwPollEvents(); 181 } 182 183 // optional: de-allocate all resources once they've outlived their purpose: 184 // ------------------------------------------------------------------------ 185 glDeleteVertexArrays(1, &VAO); 186 glDeleteBuffers(1, &VBO); 187 glDeleteBuffers(1, &EBO); 188 189 // glfw: terminate, clearing all previously allocated GLFW resources. 190 // ------------------------------------------------------------------ 191 glfwTerminate(); 192 return 0; 193 } 194 195 // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly 196 // --------------------------------------------------------------------------------------------------------- 197 void processInput(GLFWwindow *window) 198 { 199 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) 200 glfwSetWindowShouldClose(window, true); 201 } 202 203 // glfw: whenever the window size changed (by OS or user resize) this callback function executes 204 // --------------------------------------------------------------------------------------------- 205 void framebuffer_size_callback(GLFWwindow* window, int width, int height) 206 { 207 // make sure the viewport matches the new window dimensions; note that width and 208 // height will be significantly larger than specified on retina displays. 209 glViewport(0, 0, width, height); 210 }