目录
1.坐标系统
首先回顾一下渲染管线如何把顶点显示到屏幕上,OpenGL希望在每次顶点着色器运行后,我们可见的所有顶点都为标准化设备坐标(-1, 1)。超出这个坐标范围的顶点都将不可见。我们通常会自己设定一个坐标的范围,之后再在顶点着色器中将这些坐标变换为标准化设备坐标(NDC)。然后将这些标准化设备坐标传入光栅(Rasterizer),将它们变换为屏幕上的二维坐标或像素。
将坐标变换为标准化设备坐标,接着再转化为屏幕坐标的过程通常是分步进行的,物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统,变换到多个过渡坐标系统的优点是在一些特定的坐标系中,运算和操作会变得相对容易。比较重要的5个坐标系统如下所示,下面五种状态是指一个顶点在最终被转化为片段之前需要经历的所有不同状态。
- 局部空间(Local Space,或者称为物体空间(Object Space))
- 世界空间(World Space)
- 观察空间(View Space,或者称为视觉空间(Eye Space))
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
我们知道了五种坐标系空间的名字,而下面的流程图直观地告诉了我们图形的坐标最终呈现在屏幕上是如何变化的。
总结一下上图:
1.局部坐标中是以物体的原点作为坐标系原点。
2.通过模型矩阵变换到了世界坐标,模型矩阵可以是位移矩阵、旋转矩阵、缩放矩阵的组合。此时物体在以世界原点为中心的坐标系中,如果不经过这样的变换那么世界中所有的物体都将叠加在世界坐标的原点。
3.然后通过观察矩阵变换到观察者坐标,是以摄像机或观察者为原点的坐标系。
4.再通过投影矩阵变换到裁剪坐标系,此时裁剪标准设备坐标系之外的坐标。为什么要裁剪?因为透视的原理,近大远小,比如我们站在一座高楼的门口,我们只能看见门,我们站到几十米之外将目睹整个大楼的外观,裁剪就是根据透视的原理裁减掉我们视野之外的图像。这里将引入一个重要概念叫“透视除法”,即x,y,z分量分别于齐次分量w相除(w越大说明顶点越远),将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。
5.最后通过视口变换到屏幕坐标,由glViewport函数把标准设备坐标转换成屏幕坐标。最后会进入光栅器,转换为片段。
2.透视投影与齐次坐标
上图就是现实中我们经常看到的透视现象,远处的两条平行的铁轨会聚焦成一个点。在图形学中通过投影矩阵完成。
上述效果在图形学中就需要齐次坐标来实现。因为在数学领域的笛卡尔坐标系中,两条平行的线永远无法相交,平行或相交是对立的。但是在计算机图形学或计算机视觉领域引入了齐次坐标的概念,两条平行的线是可以相交的。具体原理就是引入第4分量w来处理。
距离越远w越大,其它分量通过透视除法后,原来平行线上的点会随着距离越远发生偏移,最终使得两条平行线交于一点。
下图是一个透视投影,投影就是将特定范围内的坐标转化成标准化设备坐标的过程。投影的内容就是平截头体中的图像。
在GLM中可以这样创建一个透视投影矩阵:
glm::perspective所做的其实就是创建了一个定义了可视空间的平截头体,任何在这个平截头体以外的东西最后都不会出现在裁剪空间体积内,并且将会受到裁剪。
矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
然后顶点着色器的输出要求所有的顶点都在裁剪空间内,这正是我们刚才使用变换矩阵所做的。OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中是一个800x600的屏幕)。这个过程称为视口变换。
3.进入3D
我们需要把之前的箱子往后倒55°,如何实现?
首先在main函数中创建3个矩阵:模型矩阵、观察矩阵、投影矩阵。然后CPU把它们都传入GPU的顶点着色器中,GPU再把它们按顺序与局部坐标相乘后就会得到最终的裁剪矩阵,gl_Position就是顶点着色器输出的坐标。经过GPU渲染管线绘制后的图形就是我们希望看到的。
代码修改如下:
main.c
/*创建模型、观察、投影矩阵*/
glm::mat4 model = glm::mat4(1.0f); //创建模型矩阵;老规矩,创建4分量单位矩阵
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); //绕x轴向后旋转55°
glm::mat4 view = glm::mat4(1.0f); //创建观察矩阵
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); //向屏幕里移动3个单位,即z轴负方向
glm::mat4 projection; //创建投影矩阵
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
//参数1:视野45°;参数2:屏幕比例;参数3、4:近、远平面
unsigned int transformLoc = glGetUniformLocation(shaderobject.ID, "model");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(model));
transformLoc = glGetUniformLocation(shaderobject.ID, "view");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(view));
transformLoc = glGetUniformLocation(shaderobject.ID, "projection");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(projection));
//参数1:uniform的位置值
//参数2:发送多少个矩阵
//参数3:是否需要交换行和列
//参数4:传入真正的矩阵数据,使用value_ptr转换glm格式的trans数据,使得opengl认识它们。
shader.vs
......
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
......
}
运行结果:
4.完整代码
完整代码:
shader.h(未修改)
#ifndef SHADER_H
#define SHADER_H
#include <glad.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <gtc/type_ptr.hpp>
class Shader
{
public:
unsigned int ID;//着色器程序ID,即流水线ID
// 构造函数生成着色器对象
Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr)
{
// 1.从文件路径中获取顶点/片段源代码
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
std::ifstream gShaderFile;
// 确保ifstream对象可以引发异常:
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.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();
// 将流转换为字符串
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
// 如果存在几何体着色器路径,请同时加载几何体着色器
if (geometryPath != nullptr)
{
gShaderFile.open(geometryPath);
std::stringstream gShaderStream;
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
}
catch (std::ifstream::failure& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
}
//将字符串转换为C风格字符串
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 2. 编译着色器
unsigned int vertex, fragment;
// 创建并编译顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 创建并编译片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 如果给定了几何体着色器,请编译几何体着色器
unsigned int geometry;
if (geometryPath != nullptr)
{
const char* gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
checkCompileErrors(geometry, "GEOMETRY");
}
// 链接着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
if (geometryPath != nullptr)
glAttachShader(ID, geometry);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// 删除着色器,因为它们现在链接到着色器程序中,不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
if (geometryPath != nullptr)
glDeleteShader(geometry);
}
// 选择着色器程序
void use()
{
glUseProgram(ID);
}
// uniform的实用函数
void setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
void setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void setVec2(const std::string& name, const glm::vec2& value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string& name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void setVec3(const std::string& name, const glm::vec3& value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string& name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void setVec4(const std::string& name, const glm::vec4& value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string& name, float x, float y, float z, float w)
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void setMat2(const std::string& name, const glm::mat2& mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string& name, const glm::mat3& mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string& name, const glm::mat4& mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
private:
// 用于检查着色器编译/链接错误的实用函数。
void checkCompileErrors(GLuint shader, std::string type)
{
GLint success;
GLchar 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;
}
}
}
};
#endif
shader.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;
uniform float offsetx;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}
shader.fs(未做修改)
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
uniform float mixValue;
void main()
{
//FragColor = texture(ourTexture1, TexCoord); //使用1号纹理进行采样
FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, vec2(1.0 - TexCoord.x, TexCoord.y)), mixValue); //纹理混合采样
//参数3:0.2代表第二个纹理图像透明度为20%
}
main.cpp(在渲染循环中做了修改)
#include <glad.h> //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>
#include <cmath>
#include "shader.h"
#include "stb_image.h"
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
};
float mixValue = 0.2f; //初次运行就是0.2的混合值
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
int main()
{
//初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__ //如果是苹果操作系统
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) //glad:加载所有OpenGL函数指针,固定写法就这一行
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glViewport(0, 0, 600, 400); //设置opengl实际渲染的区域大小;0,0为左下角坐标
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //注册窗口大小改变回调函数
Shader shaderobject("shaders/shader.vs", "shaders/shader.fs");
//2.创建VBO和VAO、EBO对象并赋予ID值
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//3.绑定VBO,VAO、EBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//填充EBO缓冲
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//4.开辟并填充数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//5.告知Shader如何解析缓冲里的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
//6.开启VAO管理的第一个属性值,第一个VAO编号为0
glEnableVertexAttribArray(0);
//新增VAO颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3*sizeof(float))); //颜色属性开始偏移3个顶点属性
glEnableVertexAttribArray(1);
//新增VAO纹理坐标属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); //纹理坐标属性开始偏移了6个顶点属性
glEnableVertexAttribArray(2);
//7.解绑VBO,VAO,EBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//创建纹理对象1和2
unsigned int texture1, texture2;
glGenTextures(1, &texture1);
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture1);
stbi_set_flip_vertically_on_load(true);//放于纹理加载之前,防止颠倒
// 加载并生成纹理texture1
int width, height, nrChannels;
unsigned char* data = stbi_load("./pics/container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//把data的纹理数据加载到GL_TEXTURE_2D
glGenerateMipmap(GL_TEXTURE_2D);//自动生成多级渐远纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);//data已经加载进缓冲了就可以释放
glBindTexture(GL_TEXTURE_2D, texture2);
// 加载并生成纹理texture2
data = stbi_load("./pics/awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);//把data的纹理数据加载到GL_TEXTURE_2D
glGenerateMipmap(GL_TEXTURE_2D);//自动生成多级渐远纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);//data已经加载进缓冲了就可以释放
//激活纹理单元
glActiveTexture(GL_TEXTURE0); //注意宏是从0号纹理开始
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
shaderobject.use();//没有这一句箱子上不显示纹理
shaderobject.setInt("ourTexture1", 0); //使用自己封装好的setInt,里面还是实现了glUniform1i函数
shaderobject.setInt("ourTexture2", 1);
shaderobject.setFloat("mixValue", mixValue);
while (!glfwWindowShouldClose(window)) //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置颜色:深绿色
glClear(GL_COLOR_BUFFER_BIT); //刷新屏幕,使用设置的颜色
// set the texture mix value in the shader
/*shaderobject.setFloat("mixValue", mixValue);*/
//d.选择使用着色器给三角形上色,即上面创建链接好的流水线
shaderobject.use(); //设置uniform变量之前激活着色器程序
//9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
glBindVertexArray(VAO);
//11.绑定EBO,注意在绑定VAO之后才绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//10.绘制元素:长方形
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
/*创建模型、观察、投影矩阵*/
glm::mat4 model = glm::mat4(1.0f); //创建模型矩阵;老规矩,创建4分量单位矩阵
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); //绕x轴向后旋转55°
glm::mat4 view = glm::mat4(1.0f); //创建观察矩阵
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); //向屏幕里移动3个单位,即z轴负方向
glm::mat4 projection; //创建投影矩阵
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
//参数1:视野45°;参数2:屏幕比例;参数3、4:近、远平面
unsigned int transformLoc = glGetUniformLocation(shaderobject.ID, "model");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(model));
transformLoc = glGetUniformLocation(shaderobject.ID, "view");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(view));
transformLoc = glGetUniformLocation(shaderobject.ID, "projection");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(projection));
//参数1:uniform的位置值
//参数2:发送多少个矩阵
//参数3:是否需要交换行和列
//参数4:传入真正的矩阵数据,使用value_ptr转换glm格式的trans数据,使得opengl认识它们。
glfwSwapBuffers(window); //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
glfwPollEvents(); //轮训检查输入设备触发事件
}
//e.渲染结束后把VAO,VBO和着色器都释放掉
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);//销毁的顺序没有规定
glDeleteProgram(shaderobject.ID);
glfwTerminate(); //释放分配的所有glfw资源
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) //窗口大小被改变就重新设置渲染区域大小
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //按Esc键关闭界面
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
{
mixValue += 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)
if (mixValue >= 1.0f)
mixValue = 1.0f;
}
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
{
mixValue -= 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)
if (mixValue <= 0.0f)
mixValue = 0.0f;
}
}
5.绘制一个旋转立方体箱子
要想渲染一个立方体,我们一共需要36个顶点(6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点),这36个顶点的位置你可以从这里获取。
(1)我们用立方体顶点数据替换之前的三角形顶点数据,删除掉index和glDrawElement,并重新启用glDrawArray函数就可以绘制出下面的图形。
//float vertices[] = {
// // 位置 // 颜色 // 纹理坐标
// 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上 VAO步长为8
// 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 // 左上
//};
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
//10.绘制元素:长方形
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glDrawArrays(GL_TRIANGLES, 0, 36);
......
(2)由于我们的步长已经从8变为5了,所以要改一下顶点属性中的步长,然后在着色器和main函数中删除没有用到的颜色属性,因为我们用的是纹理属性。
shader.vs
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform float offsetx;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
shader.fs
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
uniform float mixValue;
void main()
{
//FragColor = texture(ourTexture1, TexCoord); //使用1号纹理进行采样
FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, vec2(1.0 - TexCoord.x, TexCoord.y)), mixValue); //纹理混合采样
//参数3:0.2代表第二个纹理图像透明度为20%
}
main.cpp
//5.告知Shader如何解析缓冲里的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
//6.开启VAO管理的第一个属性值,第一个VAO编号为0
glEnableVertexAttribArray(0);
//新增VAO颜色属性
//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); //颜色属性开始偏移3个顶点属性
//glEnableVertexAttribArray(1);
//新增VAO纹理坐标属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(6 * sizeof(float))); //纹理坐标属性开始偏移了6个顶点属性
glEnableVertexAttribArray(1);
这样起码看起来像立方形了,但是纹理是乱的。
(3)我们来改一下VAO纹理属性的步长,从之前的6改为3,因为8-5=3。
//新增VAO纹理坐标属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); //纹理坐标属性开始偏移了3个顶点属性
我们看到这下箱子纹理已经显示出来了,但是显示到了不该显示的面。
解决办法就是增加深度测试,在main.cpp中添加代码,顺便把自动旋转也加上。
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除上一帧的深度信息
......
/*创建模型、观察、投影矩阵*/
glm::mat4 model = glm::mat4(1.0f); //创建模型矩阵;老规矩,创建4分量单位矩阵
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
6.更多的立方体箱子
若想实现更多立方体箱子,就把之前的立方体放在不同位置不就好了。
我们在上面代码的基础上修改,需要添加10个立方体在世界坐标中的原点坐标,然后遍历进行坐标变换,最后再把裁剪坐标传给shader.vs,gpu去计算坐标并绘制出立方体。然后我们使用shader.h中封装的函数替换opengl原始函数对代码进行优化。
/*10个立方体的坐标原点*/
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
......
//glDrawArrays(GL_TRIANGLES, 0, 36);
for (unsigned int i = 0; i < 10; i++) {
glm::mat4 model = glm::mat4(1.0f); //创建单位矩阵
model = glm::translate(model, cubePositions[i]); //移动到坐标系的不同点
float angle = 20.0f * i; //设置不同角度
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); //旋转到不同角度
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f)); //动态旋转
shaderobject.setMat4("model", model); //把模型矩阵传给着色器
glDrawArrays(GL_TRIANGLES, 0, 36); //向gpu发送绘图指令
}
/*创建模型、观察、投影矩阵*/
//glm::mat4 model = glm::mat4(1.0f); //创建模型矩阵;老规矩,创建4分量单位矩阵
//model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
//unsigned int transformLoc = glGetUniformLocation(shaderobject.ID, "model");
//glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(model));
//使用shader.h中封装的函数,下面四行opengl原始的函数就可以注释了
shaderobject.setMat4("view", view);
shaderobject.setMat4("projection", projection);
/*unsigned int transformLoc = glGetUniformLocation(shaderobject.ID, "view");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(view));
transformLoc = glGetUniformLocation(shaderobject.ID, "projection");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(projection));*/
运行结果:10个立方体在自由地自转。
7.完整代码
shader.h(未修改)
#ifndef SHADER_H
#define SHADER_H
#include <glad.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <gtc/type_ptr.hpp>
class Shader
{
public:
unsigned int ID;//着色器程序ID,即流水线ID
// 构造函数生成着色器对象
Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr)
{
// 1.从文件路径中获取顶点/片段源代码
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
std::ifstream gShaderFile;
// 确保ifstream对象可以引发异常:
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.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();
// 将流转换为字符串
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
// 如果存在几何体着色器路径,请同时加载几何体着色器
if (geometryPath != nullptr)
{
gShaderFile.open(geometryPath);
std::stringstream gShaderStream;
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
}
catch (std::ifstream::failure& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
}
//将字符串转换为C风格字符串
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 2. 编译着色器
unsigned int vertex, fragment;
// 创建并编译顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 创建并编译片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 如果给定了几何体着色器,请编译几何体着色器
unsigned int geometry;
if (geometryPath != nullptr)
{
const char* gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
checkCompileErrors(geometry, "GEOMETRY");
}
// 链接着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
if (geometryPath != nullptr)
glAttachShader(ID, geometry);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// 删除着色器,因为它们现在链接到着色器程序中,不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
if (geometryPath != nullptr)
glDeleteShader(geometry);
}
// 选择着色器程序
void use()
{
glUseProgram(ID);
}
// uniform的实用函数
void setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
void setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void setVec2(const std::string& name, const glm::vec2& value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string& name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void setVec3(const std::string& name, const glm::vec3& value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string& name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void setVec4(const std::string& name, const glm::vec4& value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string& name, float x, float y, float z, float w)
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void setMat2(const std::string& name, const glm::mat2& mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string& name, const glm::mat3& mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string& name, const glm::mat4& mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
private:
// 用于检查着色器编译/链接错误的实用函数。
void checkCompileErrors(GLuint shader, std::string type)
{
GLint success;
GLchar 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;
}
}
}
};
#endif
shader.vs
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform float offsetx;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
shader.fs
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
uniform float mixValue;
void main()
{
//FragColor = texture(ourTexture1, TexCoord); //使用1号纹理进行采样
FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, vec2(1.0 - TexCoord.x, TexCoord.y)), mixValue); //纹理混合采样
//参数3:0.2代表第二个纹理图像透明度为20%
}
main.cpp
#include <glad.h> //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>
#include <cmath>
#include "shader.h"
#include "stb_image.h"
/*一个立方体的顶点*/
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, //一个面的每个顶点步长为5
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
/*10个立方体的坐标原点*/
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
unsigned int indices[] = { //两个三角形拼接成一个长方形
0, 1, 3,
1, 2, 3
};
float mixValue = 0.2f; //初次运行就是0.2的混合值
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
int main()
{
//初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__ //如果是苹果操作系统
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) //glad:加载所有OpenGL函数指针,固定写法就这一行
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glViewport(0, 0, 600, 400); //设置opengl实际渲染的区域大小;0,0为左下角坐标
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //注册窗口大小改变回调函数
Shader shaderobject("shaders/shader.vs", "shaders/shader.fs");
//2.创建VBO和VAO、EBO对象并赋予ID值
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//3.绑定VBO,VAO、EBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//填充EBO缓冲
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//4.开辟并填充数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//5.告知Shader如何解析缓冲里的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
//6.开启VAO管理的第一个属性值,第一个VAO编号为0
glEnableVertexAttribArray(0);
//新增VAO纹理坐标属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); //纹理坐标属性开始偏移了3个顶点属性
glEnableVertexAttribArray(1);
//7.解绑VBO,VAO,EBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//创建纹理对象1和2
unsigned int texture1, texture2;
glGenTextures(1, &texture1);
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture1);
stbi_set_flip_vertically_on_load(true);//放于纹理加载之前,防止颠倒
// 加载并生成纹理texture1
int width, height, nrChannels;
unsigned char* data = stbi_load("./pics/container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//把data的纹理数据加载到GL_TEXTURE_2D
glGenerateMipmap(GL_TEXTURE_2D);//自动生成多级渐远纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);//data已经加载进缓冲了就可以释放
glBindTexture(GL_TEXTURE_2D, texture2);
// 加载并生成纹理texture2
data = stbi_load("./pics/awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);//把data的纹理数据加载到GL_TEXTURE_2D
glGenerateMipmap(GL_TEXTURE_2D);//自动生成多级渐远纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);//data已经加载进缓冲了就可以释放
//激活纹理单元
glActiveTexture(GL_TEXTURE0); //注意宏是从0号纹理开始
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
shaderobject.use();//没有这一句箱子上不显示纹理
shaderobject.setInt("ourTexture1", 0); //使用自己封装好的setInt,里面还是实现了glUniform1i函数
shaderobject.setInt("ourTexture2", 1);
shaderobject.setFloat("mixValue", mixValue);
while (!glfwWindowShouldClose(window)) //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置颜色:深绿色
glClear(GL_COLOR_BUFFER_BIT); //刷新屏幕,使用设置的颜色
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//d.选择使用着色器给三角形上色,即上面创建链接好的流水线
shaderobject.use(); //设置uniform变量之前激活着色器程序
//9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
glBindVertexArray(VAO);
//11.绑定EBO,注意在绑定VAO之后才绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
for (unsigned int i = 0; i < 10; i++) {
glm::mat4 model = glm::mat4(1.0f); //创建单位矩阵
model = glm::translate(model, cubePositions[i]); //移动到坐标系的不同点
float angle = 20.0f * i; //设置不同角度
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); //旋转到不同角度
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f)); //动态旋转
shaderobject.setMat4("model", model); //把模型矩阵传给着色器
glDrawArrays(GL_TRIANGLES, 0, 36); //向gpu发送绘图指令
}
glm::mat4 view = glm::mat4(1.0f); //创建观察矩阵
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); //向屏幕里移动3个单位,即z轴负方向
glm::mat4 projection; //创建投影矩阵
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
//参数1:视野45°;参数2:屏幕比例;参数3、4:近、远平面
//使用shader.h中封装的函数
shaderobject.setMat4("view", view);
shaderobject.setMat4("projection", projection);
glfwSwapBuffers(window); //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
glfwPollEvents(); //轮训检查输入设备触发事件
}
//e.渲染结束后把VAO,VBO和着色器都释放掉
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);//销毁的顺序没有规定
glDeleteProgram(shaderobject.ID);
glfwTerminate(); //释放分配的所有glfw资源
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) //窗口大小被改变就重新设置渲染区域大小
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //按Esc键关闭界面
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
{
mixValue += 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)
if (mixValue >= 1.0f)
mixValue = 1.0f;
}
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
{
mixValue -= 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)
if (mixValue <= 0.0f)
mixValue = 0.0f;
}
}