一,基础概念
对绘制的图形进行位置,旋转,缩放变化实现整个界面的变换,主要通过各种变换矩阵实现(具体资料参考教程链接或者线性代数);
二,实现基础变换
由于OpenGL没有自带任何的矩阵和向量相关的函数,需要自己实现相关功能或者下载数学库,本教程使用的是外部下载的GLM库;
1,下载GLM数学库
- 下载链接;https://link.zhihu.com/?target=https%3A///g-truc/glm/tags
- 下载glm文件,解压找到里边glm文件夹拷贝到环境搭建时的include文件夹内;

目录
- 使用前包含相关头文件,我们需要的GLM的大多数功能都可以从下面这3个头文件中找到;
#include
#include
#include2,测试一下GLM是否正常工作
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
// 如果使用的是0.9.9及以上版本
// 下面这行代码就需要改为:
// glm::mat4 trans = glm::mat4(1.0f)
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;
//如若工作正常 这个代码片段将会输出2103,具体实现代码


实现效果
更改渲染逻辑(LearnOpenGL.cpp文件)
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。
#include
#include
//GLM数学库
#include
#include
#include
#include "ShaderBase.h" //基础的渲染着色器
//通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" //图片处理
#include
using namespace std;
/*******************************************定义常量************************************************/
//设置窗口的宽和高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
float mixValue = 0.2f;
/*******************************************函数************************************************/
//响应键盘输入事件
void processInput(GLFWwindow* window)
{
//ESC 退出窗口
//glfwGetKey()用来判断一个键是否按下。第一个参数是GLFW窗口句柄,第二个参数是一个GLFW常量,代表一个键。
//GLFW_KEY_ESCAPE表示Esc键。如果Esc键按下了,glfwGetKey将返回GLFW_PRESS(值为1),否则返回GLFW_RELEASE(值为0)。
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
//glfwSetWindowShouldClose()函数,为窗口设置关闭标志。第一个参数是窗口句柄,第二个参数表示是否关闭
//这里为GLFW_TRUE,表示关闭该窗口。
//注意,这时窗口不会立即被关闭,但是glfwWindowShouldClose()将返回GLFW_TRUE,到了glfwTerminate()就会关闭窗口。
glfwSetWindowShouldClose(window, true);
}
//上键
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
{
mixValue += 0.01f;
if (mixValue >= 1.0f)
mixValue = 1.0f;
}
//下键
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
{
mixValue -= 0.01f;
if (mixValue <= 0.0f)
mixValue = 0.0f;
}
}
//当用户改变窗口的大小的时候,视口也应该被调整。
//对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//OpenGL渲染窗口的尺寸大小
//glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
glViewport(0, 0, width, height);
}
/*******************************************主函数************************************************/
//主函数
int main()
{
//测试代码
//glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
//glm::mat4 trans = glm::mat4(1.0f);
//trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
//vec = trans * vec;
//std::cout << vec.x << vec.y << vec.z << std::endl;
//初始化GLFW
glfwInit();
//声明版本与核心
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建窗口并设置其大小,名称,与检测是否创建成功
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
return -1;
}
//创建完毕之后,需要让当前窗口的环境在当前线程上成为当前环境,就是接下来的画图都会画在我们刚刚创建的窗口上
glfwMakeContextCurrent(window);
//告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//glad寻找opengl的函数地址,调用opengl的函数前需要初始化glad
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
/*******************************************着色器************************************************/
//构建和编译Shader
Shader ourShader01("vs01.vs", "fs01.fs");
Shader ourShader02("vs02.vs", "fs02.fs");
/*******************************************顶点数据************************************************/
//设置顶点数据和顶点属性
//第一个三角形数据
float vertices01[] = {
// 位置 顶点颜色 UV
0.1f, 0.9f, 0.0f, 1.0f,0.0f,0.0f, 0.0f,1.0f,
0.9f, 0.9f, 0.0f, 0.0f,1.0f,0.0f, 1.0f,1.0f,
0.9f, 0.1f, 0.0f, 0.0f,0.0f,1.0f, 1.0f,0.0f,
0.1f, 0.1f, 0.0f, 1.0f,1.0f,1.0f, 0.0f,0.0f
};
unsigned int indices[] = {
0, 1, 3,
1, 2, 3
};
// 第二个三角形数据
float vertices02[] = {
//位置 UV
-0.5f, 0.9f, 0.0f, 0.5f,1.0f,
-0.1f, 0.1f, 0.0f, 1.0f,0.0f,
-0.9f, 0.1f, 0.0f, 0.0f,0.0f
};
/*******************************************VAO/VBO/EBO************************************************/
//创建 VBO 顶点缓冲对象 VAO顶点数组对象 EBO索引缓冲对象
unsigned int VBOs[2], VAOs[2], EBOs[2];
glGenVertexArrays(2, VAOs);
glGenBuffers(2, VBOs);
glGenBuffers(2, EBOs);
//绑定VAO,VBO与EBO对象
/*******************************************第一个************************************************/
glBindVertexArray(VAOs[0]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOs[0]);
// 复制顶点数据到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices01), vertices01, GL_STATIC_DRAW);
//赋值顶点索引到缓冲内存中
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);
//顶点颜色
//属性位置值为1的顶点属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
//以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。
glEnableVertexAttribArray(1);
//顶点UV坐标
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
/*******************************************第二个************************************************/
//绑定VAO,VBO与EBO对象
glBindVertexArray(VAOs[1]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
//复制顶点数据到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices02), vertices02, GL_STATIC_DRAW);
//链接顶点属性,设置顶点属性指针
//顶点位置
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);
/*******************************************纹理对象************************************************/
//生成纹理
//创建ID
unsigned int texture1, texture2, texture3;
/*******************************************第一个纹理对象************************************************/
//创建纹理对象
//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中
glGenTextures(1, &texture1);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture1);
//设置纹理的环绕方式
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;
//因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部
//翻转y轴
stbi_set_flip_vertically_on_load(true);
//stbi_load()函数 载入图片数据
unsigned char *data = stbi_load(("resources/textures/container.jpg"), &width, &height, &nrChannels, 0);
//判断数据是否加载成功
if (data)
{
//利用载入图片数据,生成纹理
//当前绑定的纹理对象就会被附加上纹理图像
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//删除加载的图片数据,释放内存
stbi_image_free(data);
/*******************************************第二个纹理对象************************************************/
//创建纹理对象
//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中
glGenTextures(1, &texture2);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture2);
//设置纹理的环绕方式
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);
//声明变量 用来储存图片的宽度,高度和颜色通道个数
data = stbi_load(("resources/textures/awesomeface.png"), &width, &height, &nrChannels, 0);
//判断数据是否加载成功
if (data)
{
//利用载入图片数据,生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//删除加载的图片数据,释放内存
stbi_image_free(data);
/* ****************************************第二个三角形******************************************************/
//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中
glGenTextures(1, &texture3);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture3);
//设置纹理的环绕方式
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);
//因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部
//翻转y轴
stbi_set_flip_vertically_on_load(true);
//stbi_load()函数 载入图片数据
data = stbi_load(("resources/textures/wall.jpg"), &width, &height, &nrChannels, 0);
//判断数据是否加载成功
if (data)
{
//利用载入图片数据,生成纹理
//当前绑定的纹理对象就会被附加上纹理图像
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//删除加载的图片数据,释放内存
stbi_image_free(data);
/*******************************************分配纹理单元************************************************/
//设置uniform前,激活着色器
ourShader01.use();
//glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。
//只需要设置一次即可,所以这个会放在渲染循环的前面
//手动设置
//glUniform1i(glGetUniformLocation(ourShader01.ID, "texture1"), 0);
//使用内置自定义函数
ourShader01.setInt("texture1", 0);
ourShader01.setInt("texture2", 1);
/*******************************************第二个三角形************************************************/
ourShader02.use();
ourShader02.setInt("texture3", 2);
/*******************************************渲染循环************************************************/
//程序可以一直运行,直到用户关闭窗口。这样我们就需要创建一个循环,叫做游戏循环
//glfwWindowShouldClose()检查窗口是否需要关闭。如果是,游戏循环就结束了,接下来我们将会清理资源,结束程序
while (!glfwWindowShouldClose(window))
{
//响应键盘输入
processInput(window);
//设置清除颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//清除当前窗口,把颜色设置为清除颜色
glClear(GL_COLOR_BUFFER_BIT);
/*******************************************绘制************************************************/
//获取时间
float timeValue = glfwGetTime();
float greenValue = sin(timeValue) / 2.0f + 0.5f;
//激活链接程序,激活着色器,开始渲染
//绘制第一个三角形
//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
//glBindTexture()函数调用,会绑定这个纹理到当前激活的纹理单元
//纹理单元GL_TEXTURE0默认总是被激活
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
ourShader01.use();
ourShader01.setFloat("YOffset", greenValue);
ourShader01.setFloat("mixValue", mixValue);
//创建一个矩阵4X4
glm::mat4 transform = glm::mat4(1.0f);
//移动 旋转和缩放矩阵
transform = glm::translate(transform, glm::vec3(0.5f, 0.0f, 0.0f));
transform = glm::scale(transform, glm::vec3(0.5, 0.5, 0.5));
transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
//为着色器中的矩阵赋值
unsigned int transformLoc01 = glGetUniformLocation(ourShader01.ID, "transform01");
glUniformMatrix4fv(transformLoc01, 1, GL_FALSE, glm::value_ptr(transform));
//绑定VAO
glBindVertexArray(VAOs[0]);
//绘制四边形
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//绘制三角形 三个顶点
//glDrawArrays(GL_TRIANGLES, 0, 3);
//绘制第二个
//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, texture3);
ourShader02.use();
ourShader02.setVec4("outColor", 0.0f,greenValue,0.0f,1.0f);
//创建一个矩阵4X4
glm::mat4 transform02 = glm::mat4(1.0f);
//移动 旋转和缩放矩阵
transform02 = glm::scale(transform02, glm::vec3(greenValue, greenValue, greenValue));
//为着色器中的矩阵赋值
unsigned int transformLoc02 = glGetUniformLocation(ourShader02.ID, "transform02");
glUniformMatrix4fv(transformLoc02, 1, GL_FALSE, glm::value_ptr(transform02));
//绑定VAO
glBindVertexArray(VAOs[1]);
//绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
/*******************************************结束************************************************/
//交换颜色缓冲
glfwSwapBuffers(window);
//处理事件
glfwPollEvents();
}
//解除绑定
glDeleteVertexArrays(2, VAOs);
glDeleteBuffers(2, VBOs);
glDeleteBuffers(2, EBOs);
//释放前面所申请的资源
glfwTerminate();
return 0;
}vs01.vs代码为
#version 330 core
layout (location = 0) in vec3 aPos; //顶点位置
layout (location = 1) in vec3 aColor; //顶点颜色
layout (location = 2) in vec2 aTexCoord; //顶点的UV坐标
out vec3 ourColor; //输出颜色
out vec2 TexCoord; //输出顶点UV坐标
//out vec3 ourPosition;
uniform float YOffset; //声明一个float 变量
uniform mat4 transform01; //声明一个矩阵
void main()
{
//赋值
gl_Position = transform01*vec4(aPos.x, aPos.y - YOffset, aPos.z, 1.0);
ourColor = aColor;
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}fs01.fs代码为
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
//in vec3 ourPosition;
uniform sampler2D texture1; //声明一个贴图
uniform sampler2D texture2; //声明一个贴图
uniform float mixValue; //声明控制混合的变量
void main()
{
//采样贴图
//mix()函数 接受两个值作为参数,并对它们根据第三个参数进行线性插值
FragColor = mix(texture(texture1, TexCoord) * vec4(ourColor, 1.0), texture(texture2, vec2(TexCoord.x, TexCoord.y)), mixValue);
}vs02.vs代码为
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord; //顶点的UV坐标
out vec3 ourPosition;
out vec2 TexCoord; //输出顶点UV坐标
uniform mat4 transform02; //声明一个矩阵
void main()
{
gl_Position = transform02 * vec4(aPos, 1.0);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
ourPosition = aPos;
}fs02.fs代码为
#version 330 core
out vec4 FragColor;
in vec3 ourPosition;
in vec2 TexCoord;
uniform vec4 outColor;
uniform sampler2D texture3; //声明一个贴图
void main()
{
vec4 tex = texture(texture3, TexCoord);
FragColor = tex * (outColor + vec4(ourPosition, 1.0f));
}

















