学习自:
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, , &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 片段着色器 fs
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, , &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[];
if (type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, , 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, , 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 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 core
layout (location = ) in vec3 aPos;
layout (location = ) in vec3 aColor;
layout (location = ) in vec2 aTexCoord; out vec3 ourColor;
out vec2 TexCoord; void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}
片段着色器texture.fs
#version 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)编写主程序:
关键代码的注释我已经翻译并且加上去,一些常规代码可以忽略或者把英文注释翻一下吧
#include <glad/glad.h>
#include <GLFW/glfw3.h> #include "stb_image.h"
#include "shader_s.h"
#include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window); // settings
const unsigned int SCR_WIDTH = ;
const unsigned int SCR_HEIGHT = ; int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, );
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, );
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif // glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -;
} // build and compile our shader zprogram
// ------------------------------------
Shader ourShader("../res/textures/texture.vs", "../res/textures/texture.fs"); // set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
// 位置信息 // 颜色信息 // 纹理 coords
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 // 左上
};
unsigned int indices[] = {
, , , // 第一个三角形
, , // 第二个三角形
};
unsigned int VBO, VAO, EBO;
glGenVertexArrays(, &VAO);
glGenBuffers(, &VBO);
glGenBuffers(, &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 attribute
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * sizeof(float), (void*));
glEnableVertexAttribArray();
// color attribute
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * sizeof(float), (void*)( * sizeof(float)));
glEnableVertexAttribArray();
// texture coord attribute
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, * sizeof(float), (void*)( * sizeof(float)));
glEnableVertexAttribArray(); // 加载并创建纹理
// -------------------------
unsigned int texture1, texture2;
// 第一张纹理 glGenTextures(, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
// 为当前绑定的纹理对象设置环绕、过滤方式
// 将纹理包装设置为GL_REPEAT(默认包装方法)
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); //告诉stb_image.h在y轴上翻转加载的纹理。 unsigned char *data = stbi_load("../res/textures/container.jpg", &width, &height, &nrChannels, );
if (data)
{
glTexImage2D(GL_TEXTURE_2D, , GL_RGB, width, height, , 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(, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
// set the texture wrapping parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// set texture filtering parameters
glTexParameteri(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 mipmaps
data = stbi_load("../res/textures/awesomeface.png", &width, &height, &nrChannels, );
if (data)
{
//请注意,awesomeface.png具有透明度,因此具有alpha通道,
//因此请务必告诉OpenGL数据类型为GL_RGBA
glTexImage2D(GL_TEXTURE_2D, , GL_RGBA, width, height, , GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data); //告诉每个采样器的opengl它属于哪个纹理单元(只需要做一次)
ourShader.use(); //激活着色器
// either set it manually like so:
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), );
// or set it via the texture class
ourShader.setInt("texture2", ); // render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window); // render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); // bind textures on corresponding texture units
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2); // render container
ourShader.use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, , GL_UNSIGNED_INT, ); // 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(, &VAO);
glDeleteBuffers(, &VBO);
glDeleteBuffers(, &EBO); // glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return ;
} // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void 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 executes
// ---------------------------------------------------------------------------------------------
void 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(, , width, height);
}