学习自:
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 << " " << infoLog << " -- --------------------------------------------------- -- " << 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 << " " << infoLog << " -- --------------------------------------------------- -- " << 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 }