学习网页:https://learnopenglcn.github.io/01%20Getting%20started/07%20Transformations/
主要是记录摘抄笔记
一、变换
1、基础
只记录一下一些特殊的,简单的可以去上面学习链接查看
1.1、向量相关
一个是点乘(Dot Product),记作v¯⋅k¯,
另一个是叉乘(Cross Product),记作v¯×k¯。
1.2、矩阵相关
矩阵相乘
1.3、矩阵与向量相乘实现的几个功能
1.3.1、单位矩阵
1.3.2、放大缩小
1.3.3、位移
1.3.4、旋转
1.3.5、矩阵相乘
要注意顺序
2、GLM实践
GLMgit地址:https://github.com/g-truc/glm/releases/tag/0.9.9.4
2.1、基础
2.1.1、位移矩阵定义
//获取位移矩阵的api
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER mat<4, 4, T, Q> translate(mat<4, 4, T, Q> const& m, vec<3, T, Q> const& v)
{
mat<4, 4, T, Q> Result(m);
Result[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3];
return Result;
}
我们把箱子逆时针旋转90度。然后缩放0.5倍,使它变成原来的一半大。
glm::mat4 trans;
GLM希望它的角度是弧度制的(Radian),所以我们使用glm::radians将角度转化为弧度。
注意有纹理的那面矩形是在XY平面上的,所以我们需要把它绕着z轴旋转。
//rotate 旋转
//trans 单位矩阵
//glm::radians(90.0f)90度
// glm::vec3(0.0, 0.0, 1.0)z轴
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));沿z轴旋转90度
//scale 放缩
//每个轴的glm::vec3(0.5, 0.5, 0.5)
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));每个轴都缩放到0.5倍
2.1.2、变换矩阵&着色器设置
2.2、实践
2.2.1、位移
2.2.2.、旋转放缩
2.2.3、位移并随时间旋转
尽管在代码中我们先位移再旋转,实际的变换却是先应用旋转再是位移的。
2.2.4、先旋转再位移
实践效果就是先位移再旋转;则箱子中心移动轨迹就是一个圆
2.2.5、代码
主代码
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "glm/glm/glm.hpp"
#include "glm/glm/gtc/matrix_transform.hpp"
#include "glm/glm/gtc/type_ptr.hpp"
#include "ShaderClass.h"
#include "stb_image.h"
using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
/******************************1、 初始化opengl窗口*********************************************/
//glfw 初始化和配置
glfwInit();//初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//子版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//指定哪个配置文件配置上下文:GLFW我们使用的是核心模式
#ifdef __APPLE__//如果时mac os xp等系统则要进行配置一下才能生效
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
//glfw创建窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);//GLFW将我们窗口的上下文设置为当前线程的主上下文
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//GLAD加载所有的opengl函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
/******************************2、 着色器编译编译链接 *********************************************/
Shader ourShader("shader.vs", "shader.fs");
/******************************3、 顶点属性缓存这些 *********************************************/
//绘制两个相连的三角形
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, // first triangle
1, 2, 3 // second triangle
};
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定
glGenBuffers(1, &VBO);//创建顶点缓冲对象
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);//绑定顶点数组 配置顶点属性
glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//将GL_ELEMENT_ARRAY_BUFFER类型的缓冲与EBO绑定
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲里
/*
0;指定我们要配置的顶点位置属性 就是顶点着色器里面location那个
*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 *sizeof(float), (void *)0);//步长为8了
glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
/*
1;指定我们要配置的顶点颜色属性 就是顶点着色器里面location那个 glVertexAttribPointer函数更新顶点格式
*/
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
/*
2;指定我们要配置的顶点纹理属性 就是顶点着色器里面location那个 glVertexAttribPointer函数更新顶点格式
*/
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //记住:不要在VAO激活时解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。
glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO
// 加载创建纹理
unsigned int texture, texture2;
glGenTextures(1, &texture);//生成纹理对象,同样也可以创建一个数组的纹理
glBindTexture(GL_TEXTURE_2D, texture); //绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:
//为当前绑定的纹理对象设置环绕、过滤方式
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_MIPMAP_LINEAR);//缩小时采用在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//线性
// 加载图像
int width, height, nrChannels;
unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);//为当前绑定的纹理自动生成所有需要的多级渐远纹理
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);//释放图像的内存
//纹理2
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);
stbi_set_flip_vertically_on_load(true);
data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
ourShader.use();
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);//绑定这个着色器的第0个纹理
ourShader.setInt("texture2", 1);//绑定这个着色器的第0个纹理
//位移
//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;
/* //旋转
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));*/
//渲染循环
//程序在我们主动关闭它之前不断绘制图像并能够接受用户输入 GLFW退出前一直保持运行
while (!glfwWindowShouldClose(window))//检查一次GLFW是否被要求退出
{
//可接收键盘输入esc从而退出
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置状态函数
glClear(GL_COLOR_BUFFER_BIT);//使用状态函数
//位移+随时间旋转
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));//位移
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));//旋转
//传到顶点着色器进行设置
unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
//激活纹理进行绑定
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
ourShader.use();
// glUseProgram(shaderProgram);//使用着色器程序
glBindVertexArray(VAO);//绑定顶点数组 就是使用顶点属性
//glDrawArrays(GL_TRIANGLES, 0, 3);
//配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉我们用线来绘制
//注意如果进行设置glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) 那么就会一直是线框模式
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用当前绑定的索引缓冲对象中的索引进行绘制:
glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
ourShader.dele();
//释放/删除之前的分配的所有资源
glfwTerminate();
return 0;
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//会返回这个按键是否正在被按下
glfwSetWindowShouldClose(window, true);//把WindowShouldClose属性设置为 true的方法关闭GLFW
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//左上角坐标xy和宽高
glViewport(0, 0, width, height);//OpenGL的显示试图
}
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 transform;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = transform * vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
//在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:
uniform sampler2D ourTexture;
//sampler2D就是表示纹理单元类型的 是uniform卡类型
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
//ourTexture 采样器
//TexCoord 由顶点坐标那边传来的纹理坐标
//最后输出还可以把颜色也叠加上去
//FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);;
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2) * vec4(ourColor, 1.0);
}
2.2.6、绘制第二个箱子进行放缩
//第二个
trans = glm::mat4(1.0f); // reset it to identity matrix
trans = glm::translate(trans, glm::vec3(-0.5f, 0.5f, 0.0f));//移动
float scaleAmount = sin(glfwGetTime());
trans = glm::scale(trans, glm::vec3(scaleAmount, scaleAmount, scaleAmount));//放缩
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, &trans[0][0]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
二、坐标系统
1、坐标的转换
OpenGL希望在每次顶点着色器运行后,我们可见的所有顶点都为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说,每个顶点的x,y,z坐标都应该在**-1.0到1.0之间**,超出这个坐标范围的顶点都将不可见。
坐标变换为标准化设备坐标,接着再转化为屏幕坐标的过程通常是分步进行的,也就是类似于流水线那样子。
正射投影:
就是相当于平行投影
实现
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
透视投影:
就是相当于越远越小的视觉投影
实现
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
它的第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小。
如果想要一个真实的观察效果,它的值通常设置为45.0f,但想要一个末日风格的结果你可以将其设置一个更大的值。
第二个参数设置了宽高比,由视口的宽除以高所得。
第三和第四个参数设置了平截头体的近和远平面。
我们通常设置近距离为0.1f,而远距离设为100.0f。所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。
当你把透视矩阵的 near 值设置太大时(如10.0f),OpenGL会将靠近摄像机的坐标(在0.0f和10.0f之间)都裁剪掉、
这会导致一个你在游戏中很熟悉的视觉效果:
在太过靠近一个物体的时候你的视线会直接穿过去。
详情可以查看学习链接
https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/
最后坐标实现的过程就是把这些操作整合到一起
2、实践流程
2.1、api步骤流程
2.1.1、创建模型矩阵:顶点坐标->世界坐标
2.1.2、创建观察矩阵:世界坐标->观察坐标
2.1.3、创建投影矩阵:观察坐标->裁剪坐标
2.1.4、将变换矩阵设置到顶点着色器当中
2.1.5、效果图
2.2、绘制3D立体并旋转_36个顶点
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 transform;
out vec3 ourColor;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
//在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:
uniform sampler2D ourTexture;
//sampler2D就是表示纹理单元类型的 是uniform卡类型
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
//ourTexture 采样器
//TexCoord 由顶点坐标那边传来的纹理坐标
//最后输出还可以把颜色也叠加上去
//FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);;
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2) * vec4(ourColor, 1.0);
}
主代码
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "glm/glm/glm.hpp"
#include "glm/glm/gtc/matrix_transform.hpp"
#include "glm/glm/gtc/type_ptr.hpp"
#include "ShaderClass.h"
#include "stb_image.h"
using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
/******************************1、 初始化opengl窗口*********************************************/
//glfw 初始化和配置
glfwInit();//初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//子版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//指定哪个配置文件配置上下文:GLFW我们使用的是核心模式
#ifdef __APPLE__//如果时mac os xp等系统则要进行配置一下才能生效
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
//glfw创建窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);//GLFW将我们窗口的上下文设置为当前线程的主上下文
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//GLAD加载所有的opengl函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
/******************************2、 着色器编译编译链接 *********************************************/
Shader ourShader("shader.vs", "shader.fs");
/******************************3、 顶点属性缓存这些 *********************************************/
//绘制两个相连的三角形
//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 // 左上
//};
float vertices[] = {
//-- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f,1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f,1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f,1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f,1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f,1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f,1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f,1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f,1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f,1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f,1.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
//索引数组
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定
glGenBuffers(1, &VBO);//创建顶点缓冲对象
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);//绑定顶点数组 配置顶点属性
glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//将GL_ELEMENT_ARRAY_BUFFER类型的缓冲与EBO绑定
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲里
/*
0;指定我们要配置的顶点位置属性 就是顶点着色器里面location那个
*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 *sizeof(float), (void *)0);//步长为8了
glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
/*
1;指定我们要配置的顶点颜色属性 就是顶点着色器里面location那个 glVertexAttribPointer函数更新顶点格式
*/
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
/*
2;指定我们要配置的顶点纹理属性 就是顶点着色器里面location那个 glVertexAttribPointer函数更新顶点格式
*/
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //记住:不要在VAO激活时解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。
glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO
// 加载创建纹理
unsigned int texture, texture2;
glGenTextures(1, &texture);//生成纹理对象,同样也可以创建一个数组的纹理
glBindTexture(GL_TEXTURE_2D, texture); //绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:
//为当前绑定的纹理对象设置环绕、过滤方式
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_MIPMAP_LINEAR);//缩小时采用在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//线性
// 加载图像
int width, height, nrChannels;
unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);//为当前绑定的纹理自动生成所有需要的多级渐远纹理
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);//释放图像的内存
//纹理2
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);
stbi_set_flip_vertically_on_load(true);
data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
ourShader.use();
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);//绑定这个着色器的第0个纹理
ourShader.setInt("texture2", 1);//绑定这个着色器的第0个纹理
//启动Z缓冲
glEnable(GL_DEPTH_TEST);
//渲染循环
//程序在我们主动关闭它之前不断绘制图像并能够接受用户输入 GLFW退出前一直保持运行
while (!glfwWindowShouldClose(window))//检查一次GLFW是否被要求退出
{
//可接收键盘输入esc从而退出
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置状态函数
//glClear(GL_COLOR_BUFFER_BIT);//使用状态函数
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//要在每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中
//定义变化矩阵
//x轴55度旋转
glm::mat4 model = glm::mat4(1.0f);
//model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));//随时间旋转
//z轴负方向3个单位
glm::mat4 view = glm::mat4(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//透视投影
glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
//传到顶点着色器进行设置
unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model");
unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
// 目前我们在每一帧都设置了投影矩阵,但由于投影矩阵很少改变,所以最好只在主循环之外设置一次。
ourShader.setMat4("projection", projection);
//激活纹理进行绑定
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
ourShader.use();
glBindVertexArray(VAO);//绑定顶点数组 就是使用顶点属性
// glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用当前绑定的索引缓冲对象中的索引进行绘制:
glDrawArrays(GL_TRIANGLES, 0, 36);//绘制36个顶点
glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
ourShader.dele();
//释放/删除之前的分配的所有资源
glfwTerminate();
return 0;
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//会返回这个按键是否正在被按下
glfwSetWindowShouldClose(window, true);//把WindowShouldClose属性设置为 true的方法关闭GLFW
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//左上角坐标xy和宽高
glViewport(0, 0, width, height);//OpenGL的显示试图
}
2.3、绘制多个3D立体箱子
现在我们想在屏幕上显示10个立方体。每个立方体看起来都是一样的,区别在于它们在世界的位置及旋转角度不同。立方体的图形布局已经定义好了,所以当渲染更多物体的时候我们不需要改变我们的缓冲数组和属性数组,我们唯一需要做的只是改变每个对象的模型矩阵来将立方体变换到世界坐标系中。
让我们为每个立方体定义一个位移向量来指定它在世界空间的位置。
一个glm::vec3数组中定义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 10次,但这次在我们渲染之前每次传入一个不同的模型矩阵到顶点着色器中。我们将会在游戏循环中创建一个小的循环用不同的模型矩阵渲染我们的物体10次。注意我们也对每个箱子加了一点旋转:
//激活纹理进行绑定
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
//着色器使用
ourShader.use();
//绑定顶点数组 就是使用顶点属性
glBindVertexArray(VAO);
//进行绘制
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+1);
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
ourShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}