先上三张效果图
SampleTex.cpp
/**
* 加载纹理图片
*/
#include
#include
#include
#include
//定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了
#define STB_IMAGE_IMPLEMENTATION
#include
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void inputProcess(GLFWwindow* window);
//顶点数据
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 // 左上
};
unsigned int indices[] = {
0, 1, 3, // 第一个三角形
1, 2, 3 // 四二个三角形
};
//顶点缓冲对象(Vertex Buffer Objects, VBO),它会在GPU内存(通常被称为显存)中储存大量顶点
//使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次
unsigned int VBO;
//顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。
//这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
unsigned int VAO;
//索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)
//只储存不同的顶点,并设定绘制这些顶点的顺序,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点[简化一些重复的顶点定义]
unsigned int EBO;
// Holds uniform value of texture mix
GLfloat mixValue = 0.2f;
int main()
{
/*--------------------------------------------------------------------------------------*/
//初始化glfw
glfwInit();
//第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值
//其实就是设置用Opengl哪个版本,这里是3.3版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//MAC系统要打开
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
//创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
//将创建的窗口上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
//在调用任何OpenGL的函数之前我们需要初始化GLAD
//加载系统相关的OpenGL函数指针地址
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
//设置窗口大小改变的回调
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
/*--------------------------------------------------------------------------------------*/
/**生成一个纹理*/
/*--------------------------------------------------------------------------------------*/
unsigned int texture1, texture2;
//生成纹理的数量和纹理ID
glGenTextures(1, &texture1);
//绑定图片
glBindTexture(GL_TEXTURE_2D ,texture1);
//在图像加载时翻转y轴
stbi_set_flip_vertically_on_load(true);
为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
int width, height, nrChannels;
//宽度、高度和颜色通道的个数
unsigned char* imageData = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (imageData)
{
/*
使用前面载入的图片数据生成一个纹理
1. 指定纹理目标,设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理
2. 指定多级渐远纹理的级别 0
3. 把纹理储存为RGB格式
4.5 设置图片的宽高
6. 无作用
7. 定义源图的格式和数据类型
8. data 真正的图像数据
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
//为当前绑定的纹理自动生成所有需要的多级渐远纹理
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
cout << "Failed to load texture" << endl;
}
//释放图像内存
stbi_image_free(imageData);
// texture 2
// ---------
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 加载图片,创建纹理,生成mipmaps
imageData = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
if (imageData)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(imageData);
/*--------------------------------------------------------------------------------------*/
Shader ourShader("Shader/Vertex/VertexShader.vs", "Shader/Fragment/FragmentShader.fs");
ourShader.use();
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
ourShader.setInt("texture2", 1);
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindVertexArray(EBO);
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);
// 顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 纹理坐标
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
//渲染循环(Render Loop),一直检测glfw窗口是否被要求关闭
while (!glfwWindowShouldClose(window))
{
//处理按键输入
inputProcess(window);
/***********这里做具体的渲染指令************/
//设置清空屏幕所用的颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//清空颜色缓冲
glClear(GL_COLOR_BUFFER_BIT);
// 在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
// 设置一个混合变量,改变两张图的可见度
glUniform1f(glGetUniformLocation(ourShader.ID, "mixValue"), mixValue);
ourShader.use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
/***********这里做具体的渲染指令************/
//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
glfwSwapBuffers(window);
//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
//释放或者删除之前的分配的所有资源
glfwTerminate();
return 0;
}
//窗口大小变化的时候的回调
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//看下宽高变化的数据
printf("w = %d, h = %d \n",width, height);
//设置OpenGL渲染窗口的尺寸大小(视口Viewport),前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
glViewport(0, 0, width, height);
}
//处理按键输入
void inputProcess(GLFWwindow* window)
{
//按下ESC的时候
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
//把WindowShouldClose属性设置为 true,在循环肿关闭窗口
glfwSetWindowShouldClose(window, true);
printf("窗口即将关闭...");
}
// 改变可见度的变量
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
{
mixValue += 0.1f;
if (mixValue >= 1.0f)
mixValue = 1.0f;
}
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
{
mixValue -= 0.1f;
if (mixValue <= 0.0f)
mixValue = 0.0f;
}
}
VertexShader.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;
}
FragmentShader.fs
#version 330 core
out vec4 FragColor;
//顶点着色器过来的纹理颜色
in vec3 ourColor;
//顶点着色器过来的纹理坐标
in vec2 TexCoord;
//2D纹理的采样器
uniform sampler2D texture1;
uniform sampler2D texture2;
//设定一个变量,通过按键控制纹理可见度
uniform float mixValue;
void main()
{
//使用GLSL内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。
//把纹理颜色与顶点颜色在片段着色器中相乘来混合二者的颜色
//FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
//接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;
//如果是1.0,会返回第二个输入值。
//0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色
FragColor = mix(texture(texture1, TexCoord), texture(texture2, vec2(1.0 - TexCoord.x, TexCoord.y)), mixValue);
}
stb_image.h的话直接在github下载
https://github.com/nothings/stb/blob/master/stb_image.hhttps://github.com/nothings/stb/blob/master/stb_image.h
原文:纹理 - LearnOpenGL CN
特别需要注意的就是宏文件一定要定义:
#define STB_IMAGE_IMPLEMENTATION
#include
然后图片的话直接放在根目录吧,不然加载始终有点问题
unsigned char* imageData = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
这里的container.jpg放在项目根目录读取才没问题,暂时没想再去纠结路径的问题了