首先需要了解矩阵,程序里经常用矩阵做坐标变换
常见的矩阵有:
1.缩放矩阵
2.位移矩阵
多一维坐标:齐次坐标(Homogeneous Coordinates)
用途:
1.允许我们在3D向量上进行位移(如果没有w分量我们是不能位移向量的)
2.把x、y和z坐标分别除以w坐标,用w值创建3D视觉效果
如果一个向量的齐次坐标是0,这个坐标就是方向向量(Direction Vector)
3.旋转矩阵
不同轴向的旋转矩阵
任意轴旋转的复合矩阵
.
注意:不同的矩阵可以组合,比如:缩放+旋转,但是要注意顺序
OpenGL如何使用矩阵呢?
使用库: OpenGL Mathematics (GLM)
传送门
矩阵的用法:
详情见代码注释
//引入相关头文件
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
//矩阵定义,默认里面的数据都是非法数据,需要初始化
glm::mat4 trans;
//初始化方式1
// trans = mat4(1.0f);
//mat4中4行4列的元素是按照列的方式排列的
// 主要是因为opengl在显存中是按列存储的,所以在准备好数据后可以直接将mat4复制到显存中
//可以使用[]操作符来访问mat4的第几列,并使用二级[]来访问某一列的第几个元素,
//初始化方式2,熟悉内存分布
trans[0][0] = 1.0f; trans[1][0] = 0.0f; trans[2][0] = 0.0f; trans[3][0] = 0.0f;
trans[0][1] = 0.0f; trans[1][1] = 1.0f; trans[2][1] = 0.0f; trans[3][1] = 0.0f;
trans[0][2] = 0.0f; trans[1][2] = 0.0f; trans[2][2] = 1.0f; trans[3][2] = 0.0f;
trans[0][3] = 0.0f; trans[1][3] = 0.0f; trans[2][3] = 0.0f; trans[3][3] = 1.0f;
//平移 1,1,0
//0.5 0.0 0.0 1.0 * 1 = 1.5
//0.0 0.5 0.0 1.0 * 0 = 1
//0.0 0.0 0.5 0.0 * 0 = 0
//0.0 0.0 0.0 1.0 * 1 = 1
trans = translate(trans, vec3(1.0f, 1.0f, 0.0f));
//旋转
//0.5 0.0 0.0 0.0 * 1 = 0.5
//0.0 0.5 0.0 0.0 * 0 = 0
//0.0 0.0 0.5 0.0 * 0 = 0
//0.0 0.0 0.0 1.0 * 1 = 1
trans = scale(trans, vec3(0.5f, 0.5f, 0.5f));
//旋转 90度
//trans = rotate(trans, radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
vec4 vec(1.0f, 0.0f, 0.0f, 1.0f); //w是1是坐标,代表位移是有效的
vec = trans * vec;
std::cout << vec.x << "," << vec.y << "," << vec.z << std::endl;
//右乘,先缩放再平移
//最后输出 =====>> 1.5,1,0
一个顶点,要经历几个不同的空间,最后被转化为一个片段
不同坐标系统都有什么?
1.局部空间(Local Space,或者称为物体空间(Object Space))
2.世界空间(World Space)
3.观察空间(View Space,或者称为视觉空间(Eye Space))
4.裁剪空间(Clip Space)
5.屏幕空间(Screen Space)
如图:
其中需要注意的:
裁剪空间的投影矩阵分成两种:
a)正射投影矩阵(Orthographic Projection Matrix)
b)透视投影矩阵(Perspective Projection Matrix)
OpenGL希望每次顶点着色器运行后,返回的顶点是NDC空间的
但是OpenGL会自动进行透视除法(除以W)和裁剪,然后再进行视口变换(关联到屏幕分辨率上)
所以最后返回的 gl_Position,其实就是转换到上面的 裁剪空间
公式如下:
V代表顶点,M是矩阵
右乘的公式,从右往左看
绘制3d物体代码如何实现?
假设没有摄像机,相机视角是固定的,自己定义MVP矩阵
1.模型矩阵
包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变换到全局的世界空间
//定义model矩阵
glm::mat4 model = glm::mat4(1.0f);
//平移
model = glm::translate(model, cubePositions[i]);
//旋转
model = glm::rotate(model, time * glm::radians(20), glm::vec3(1.0f, 0.3f, 0.5f));
2.观察矩阵
在场景里面稍微往后移动,以使得物体变成可见的(也就是物体向前)
OpenGL是一个右手坐标系(Right-handed System),所以要取反
//定义view矩阵
glm::mat4 view = glm::mat4(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
3.投影矩阵
拿透视矩阵举例(正交更简单,不用透视除法)
但是也都是glm封装好的方法
//定义projection矩阵
glm::mat4 projection = glm::mat4(1.0f);
//perspective创建透视投影矩阵
// 参数1:视野FOV,默认是45,接近真实
// 参数2:宽高比
// 参数3:近裁剪面
// 参数4:远裁剪面
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
4.传递给顶点着色器
//Shader.cpp 新增辅助函数
void Shader::setM4(const std::string &name, const glm::mat4 &m4) const
{
auto location = glGetUniformLocation(ID, name.c_str());
//设置矩阵值
// 参数1:location
// 参数2:几个矩阵
// 参数3:是否转置
// 参数4:矩阵数据,要求指针
glUniformMatrix4fv(location, 1, GL_FALSE, &m4[0][0]);
}
//通过 glUniformMatrix4fv 设置矩阵
testShader.setM4("model", model);
testShader.setM4("view", view);
testShader.setM4("projection", projection);
4.shader进行坐标转换
#version 330 core
layout (location=0) in vec3 aPos; //0是顶点位置
layout (location=1) in vec2 aTexcoord; //2是UV坐标
uniform mat4 model; //模型矩阵
uniform mat4 view; //观察矩阵
uniform mat4 projection; //投影矩阵
out vec2 Texcoord; //返回给片段着色器的UV
void main()
{
//通过变换矩阵右乘
vec4 pos = projection * view * model * vec4(aPos, 1.0f);
//返回裁剪坐标
gl_Position = pos;
//返回UV
Texcoord = aTexcoord;
}
5.绘制正方形显示
//定义顶点
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
};
//通过 GL_TRIANGLES 绘制正方形
glDrawArrays(GL_TRIANGLES, 0, 36);
这样就可以绘制出
注意:
1.3D空间下需要开启深度测试
OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)
GLFW会自动为你生成这样一个缓冲,深度值存储在每个片段里面(作为片段的z值)
当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较
如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖
//开启深度测试
glEnable(GL_DEPTH_TEST);
//每次渲染循环开始时,清空深度缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2.多个物体渲染的话,改变model矩阵即可
//多个立方体,定义世界空间
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)
};
// 伪代码
// 渲染循环中 ...
testShader.setM4("view", glm::value_ptr(view));
testShader.setM4("projection", glm::value_ptr(projection));
lBindVertexArray(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;
model = glm::rotate(model, time * glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
testShader.setM4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
效果图: