OpenGL学习(三)三维绘制与模型变换矩阵
⽬录
前⾔
上⼀篇回顾:
在上⼀篇博客中我们实现了⼆维平⾯上三⾓形的绘制,今天我们来绘制⼀个⽴⽅体,同时我们将会利⽤模型变换矩阵对⽴⽅体进⾏旋转,平移,缩放等操作,最后我们会通过阅读OFF格式的模型来读取更加复杂的三维模型。
⚠
该部分的绘制代码基于上⼀篇博客:
博客内容因为篇幅关系,不会完整的列出所有的代码
完整代码会放在⽂章末尾
绘制⽴⽅体
⽴⽅体的绘制,⽐起⼆维的三⾓形来说,要相对复杂。⽴⽅体有 6 个⾯,⽽我们的绘制是基于基本的三⾓形图元,这意味着我们需要⽤ 2个三⾓形来描述正⽅体的⼀个⾯。
此外,⼀个⽴⽅体有 6 个⾯,这意味着我们需要有 12 个三⾓⾯⽚,⽽每个三⾓⾯⽚有 3 个顶点,所以我们总共要⽣成 36 个顶点。
你可能注意到哪⾥不对了,毕竟⼀个⽴⽅体才 8 个顶点啊,你搁这整 36 个,啥啊?
注意到我们的绘制函数 glDrawArrays ,在该函数中如果第⼀个参数指定的绘制模式为 GL_TRIANGLES ,表⽰三个顶点⼀组绘制三⾓形,所以尽管会有⼀些点的冗余,但我们应该按照规则来给定顶点。下⾯以绘制⼀个正⽅形为例,解释为何⽴⽅体会有 36 个顶点:
可以看到,尽管⼀个正⽅形只有 4 个顶点,但是为了⽤两个三⾓形去绘制它,我们⽣成了 6 个顶点,将两个三⾓形拼凑为正⽅形。
所以我们⾸先需要定义正⽅体的 8 个顶点,和其顶点的颜⾊。我个⼈更加倾向于把它定义为全局变量:
// 正⽅形的8个顶点位置坐标
std::vector<glm::vec3> vertexPosition ={
glm::vec3(-0.5,-0.5,-0.5),
glm::vec3(0.5,-0.5,-0.5),
glm::vec3(-0.5,0.5,-0.5),
glm::vec3(0.5,0.5,-0.5),
glm::vec3(-0.5,-0.5,0.5),
glm::vec3(0.5,-0.5,0.5),
glm::vec3(-0.5,0.5,0.5),
glm::vec3(0.5,0.5,0.5)
};
/
/ 正⽅形的8个顶点的颜⾊
std::vector<glm::vec3> vertexColor ={
glm::vec3(1.0,1.0,1.0),// White
glm::vec3(1.0,1.0,0.0),// Yellow
glm::vec3(0.0,1.0,0.0),// Green
glm::vec3(0.0,1.0,1.0),// Cyan
glm::vec3(1.0,0.0,1.0),// Magenta
glm::vec3(1.0,0.0,0.0),// Red
glm::vec3(0.5,0.5,0.5),// Gray
glm::vec3(0.0,0.0,1.0)// Blue
};
随后我们需要指定正⽅形的 6 个⾯,即 12 个三⾓⾯⽚。我们创建⼀个数组 faces,其中第 i 个元素 faces[i] 表⽰第 i 个三⾓⾯⽚的顶点下标:
// 正⽅形由6个⾯ -- 12个三⾓形⾯⽚组成 faces存储顶点在vertexPosition中的下标
std::vector<glm::vec3> faces ={
glm::vec3(0,2,1),
glm::vec3(1,2,3),
glm::vec3(1,3,7),
glm::vec3(7,5,1),
glm::vec3(0,1,5),
glm::vec3(5,4,0),
glm::vec3(0,4,2),
glm::vec3(4,6,2),
glm::vec3(4,5,7),
glm::vec3(7,6,4),
glm::vec3(6,7,3),
glm::vec3(3,2,6)
};
如图:通过 faces 数组指定三⾓⾯⽚的顶点:
知晓了 faces 中定义⽴⽅体三⾓⾯⽚的⽅式之后,我们就可以利⽤ faces 数组,⽣成顶点属性的索引:
⾸先我们建⽴两个变量,分别表⽰顶点的位置坐标和顶点的颜⾊
// 顶点坐标 / 颜⾊
std::vector<glm::vec3> points, colors;
然后我们在初始化(上⼀篇博客的 init 函数)中,通过 faces 数组,⽣成顶点属性:
// 由⾯⽚信息⽣成三⾓形⾯⽚顶点
for(int i =0; i < faces.size(); i++)
{
// 取得第 i 个三⾓⾯⽚的三个顶点的下标
int index1 = faces[i].x;
int index2 = faces[i].y;
int index3 = faces[i].z;
// ⽣成顶点
points.push_back(vertexPosition[index1]);
points.push_back(vertexPosition[index2]);
points.push_back(vertexPosition[index3]);
// ⽣成顶点颜⾊
colors.push_back(vertexColor[index1]);
colors.push_back(vertexColor[index2]);
colors.push_back(vertexColor[index3]);
}
剩下的步骤和我们在上⼀篇博客: 中的操作⼀样,创建vao vbo,读取着⾊器,指定vao格式,传输数据…
对了,别忘了改 display 中的绘制函数,我们绘制的顶点数不再是3个了:
重新运⾏程序,我们得到了⼀个。。。唔。。。正⽅形,⽽不是⽴⽅体?
结果是意料之中的,因为我们从⽴⽅体的正⾯看过去,那么就应该是⼀个正⽅形。如果我们想看到更多的⾯,我们就应该让正⽅体旋转起来!
模型变换矩阵
提到旋转,就不得不提⼀下模型变换。事实上,建模师在建⽴3D模型的时候,是以⼀个叫 模型坐标系 为参考建⽴的。⽐如⽴⽅体,如下的图展⽰了⽴⽅体的模型坐标。
但是事实上,在我们建⽴ 3D 场景的时候,我们不同的三维模型具有不同的位置。我们不能强求建模师在建模时就确定模型的绝对位置,况且我们还会实时地对模型进⾏移动旋转等操作,这就意味着,对模型的平移旋转缩放必须是由程序完成的!于是我们引⼊ 模型变换矩阵 这个概念。我们通过对模型的坐标进⾏变换,得到我们想要的效果。
齐次坐标
在开始构建我们的模型变换矩阵前,⾸先了解到齐次坐标的概念。通常情况下,我们都可以⽤三维向量来描述三维空间下的点,或者是⼀个⽅向:
glm ::vec3(0,2,1)glm ::vec3(1,2,3)
可是我们如何区分⼀个三维向量是坐标还是⽅向向量?
如果是坐标,那么我们平移这个向量,对应的坐标需要发⽣改变。如果是⽅向向量,那么我们平移这个向量,对应的坐标不能发⽣改变。
这就带来了难题。于是数学家们通过巧妙⽽猥琐的构造⽅式,想出了⼀个完美的解决⽅案:”即然没法区分,那就增加⼀个维度来保存向量的属性信息“,这就是齐次坐标。
齐次坐标在三维坐标的基础上,拓展了⼀个维度,变为四维的向量。那么增加了⼀个维度就能够区分 坐标点 和 ⽅向向量 了吗?
直接说结论:第四维度为0则为⽅向向量,第四维度为1则为坐标
注:这其中涉及巧妙的构造,但是我们暂时记住结论。接下来我们会验证这种构造的正确性。
通过矩阵进⾏变换rotate属性
平移旋转和缩放都是线性变换,我们观察矩阵乘法的定义式:
我们发现齐次坐标左乘⼀个矩阵,对于齐次坐标的四个维度⽽⾔,全都是 ⼒⼠ 线性变换!
线性变换意味着我们可以通过将其次坐标和⼀个矩阵进⾏乘法,从⽽实现平移旋转和缩放等线性变换。
平移变换矩阵
平移变换是最简单的线性变换!我们只需要将⼀个坐标加上⼀定的偏移就可以实现。所以我们有
如果写成齐次坐标的矩阵乘法的形式,我们通过⼀个平移矩阵,对坐标进⾏变换(注意这⾥第四个维度为 1 表⽰这是⼀个点):
我们再来看第四维度为 0 的情况:
v =′v +d
∗⎣
⎢⎢⎡10000100
0010d x d y d z 1⎦⎥
⎥⎤x ,y ,z ,1=()x +d ,y +d ,z +d ,1(x y z )
什么都没有发⽣。因为对于第四维度为 0 的情况,我们认为它是⼀个⽅向向量。对于⽅向向量的平移操作为⽆效,因为向量始终表⽰⽅向!
于是得到我们的平移变换矩阵:
旋转变换矩阵
虽然短,但是能够旋转
旋转也是⼀种线性变换。旋转分为三个部分:绕 xyz 轴旋转,所以我们理应有三个旋转变换矩阵。⾸先我们来看绕着 z 轴的旋转:
绕着 z 轴旋转相当于 z 不变。通过推理我们可以得到点的变换规律:
于是有绕着 z 轴的旋转矩阵:
∗⎣
⎢⎢⎡10000100
0010d x d y d z 1⎦⎥
⎥⎤x ,y ,z ,0=()x ,y ,z ,0()
T (d ,d ,d )=
x y z ⎣
⎢⎢⎡10000100
0010d x d y d z 1⎦
⎥⎥⎤x =x cos θ−y sin θ′y =x sin θ+y cos θ′z =z
′R (θ)=
z ⎣⎢⎢⎡cos θsin θ00−sin θcos θ000
0100001
⎦
⎥⎥⎤
同理我们可以得到绕 xy 轴的旋转矩阵:
⚠
此处默认模型的中⼼点在原点,才能够实现绕 xyz 轴的旋转。如果中⼼不在原点,就会进⾏错误的变换
缩放变换矩阵
缩放的变换和平移类似,也是各个坐标乘⼀个系数(平移是加上⼀个系数)
于是我们能够很快的得出缩放变换的变换矩阵:
⚠
此处默认模型的中⼼点在原点,才能够实现等⽐缩放。如果中⼼不在原点,就会进⾏错误的变换
变换的级联
对于上述的三种变换:平移 旋转 缩放,我们得到了 5 个变换矩阵,那么我们该怎么利⽤他们呢?我们当然可以逐个逐个的对顶点坐标进⾏变换:
v = Rx * v // x 旋转v = Ry * v // y 旋转v = Rz * v // z 旋转v = S * v // 缩放v = T * v // 平移
但是这⼀切值得吗?
这⾥就再次体现出利⽤矩阵对齐次坐标的巧妙性:我们可以先将所有的变换矩阵都乘起来,形成最终的模型变换矩阵,再利⽤模型变换矩阵对顶点坐标进⾏变换
R (θ)=
x ⎣
⎢⎢⎡10000cos θsin θ00−sin θcos θ00
001
⎦
⎥⎥⎤R (θ)=
y ⎣⎢⎢
⎡cos θ0−sin θ00100sin θ
0cos θ00
001
⎦
⎥⎥⎤v =′v ∗s
S s ,s ,s =
(x y z )⎣
⎢⎢⎡s x 0000s y 0000s z 00001
⎦
⎥⎥⎤
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论