OpenGL学习(三)三维绘制与模型变换矩阵

本文介绍如何使用OpenGL绘制立方体,并通过模型变换矩阵实现旋转、平移和缩放操作。首先,详细讲解了绘制立方体的过程,包括生成36个顶点以构建立方体的面。接着,探讨了模型变换矩阵的概念,特别是齐次坐标的使用,以及平移、旋转和缩放的矩阵表示。最后,展示了如何构建模型变换矩阵并将其传送到GPU,以在GPU中进行矩阵变换,同时启用深度测试以确保正确的覆盖效果。
摘要由CSDN通过智能技术生成

前言

上一篇回顾:OpenGL学习(二)渲染流水线与三角形绘制

在上一篇博客中我们实现了二维平面上三角形的绘制,今天我们来绘制一个立方体,同时我们将会利用模型变换矩阵对立方体进行旋转,平移,缩放等操作,最后我们会通过阅读OFF格式的模型来读取更加复杂的三维模型。


该部分的绘制代码基于上一篇博客:OpenGL学习(二)渲染流水线与三角形绘制
博客内容因为篇幅关系,不会完整的列出所有的代码
完整代码会放在文章末尾

绘制立方体

立方体的绘制,比起二维的三角形来说,要相对复杂。立方体有 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]);
}

剩下的步骤和我们在上一篇博客:OpenGL学习(二)渲染流水线与三角形绘制 中的操作一样,创建vao vbo,读取着色器,指定vao格式,传输数据…

对了,别忘了改 display 中的绘制函数,我们绘制的顶点数不再是3个了:

在这里插入图片描述

重新运行程序,我们得到了一个。。。唔。。。正方形,而不是立方体?

在这里插入图片描述

结果是意料之中的,因为我们从立方体的正面看过去,那么就应该是一个正方形。如果我们想看到更多的面,我们就应该让正方体旋转起来!

模型变换矩阵

提到旋转,就不得不提一下模型变换。事实上,建模师在建立3D模型的时候,是以一个叫 模型坐标系 为参考建立的。比如立方体,如下的图展示了立方体的模型坐标。

在这里插入图片描述

但是事实上,在我们建立 3D 场景的时候,我们不同的三维模型具有不同的位置。我们不能强求建模师在建模时就确定模型的绝对位置,况且我们还会实时地对模型进行移动旋转等操作,这就意味着,对模型的平移旋转缩放必须是由程序完成的!

于是我们引入 模型变换矩阵 这个概念。我们通过对模型的坐标进行变换,得到我们想要的效果。

齐次坐标

在开始构建我们的模型变换矩阵前,首先了解到齐次坐标的概念。通常情况下,我们都可以用三维向量来描述三维空间下的点,或者是一个方向:

glm::vec3(0,2,1)
glm::vec3(1,2,3)

可是我们如何区分一个三维向量是坐标还是方向向量

如果是坐标,那么我们平移这个向量,对应的坐标需要发生改变。如果是方向向量,那么我们平移这个向量,对应的坐标不能发生改变。

在这里插入图片描述

这就带来了难题。于是数学家们通过巧妙而猥琐的构造方式,想出了一个完美的解决方案:”即然没法区分,那就增加一个维度来保存向量的属性信息“,这就是齐次坐标。

齐次坐标在三维坐标的基础上,拓展了一个维度,变为四维的向量。那么增加了一个维度就能够区分 坐标点方向向量 了吗?

直接说结论:第四维度为0则为方向向量,第四维度为1则为坐标

注:这其中涉及巧妙的构造,但是我们暂时记住结论。接下来我们会验证这种构造的正确性。

通过矩阵进行变换

平移旋转和缩放都是线性变换,我们观察矩阵乘法的定义式:

在这里插入图片描述

我们发现齐次坐标左乘一个矩阵,对于齐次坐标的四个维度而言,全都是 力士 线性变换!

线性变换意味着我们可以通过将其次坐标和一个矩阵进行乘法,从而实现平移旋转和缩放等线性变换。

平移变换矩阵

平移变换是最简单的线性变换!我们只需要将一个坐标加上一定的偏移就可以实现。所以我们有

v ′ = v + d v' = v + d v=v+d

如果写成齐次坐标的矩阵乘法的形式,我们通过一个平移矩阵,对坐标进行变换(注意这里第四个维度为 1 表示这是一个点):

[ 1 0 0 d x 0 1 0 d y 0 0 1 d z 0 0 0 1 ] ∗ ( x , y , z , 1 ) = ( x + d x , y + d y , z + d z , 1 ) \left[\begin{array}{cccc} 1 & 0 & 0 & d_{x} \\ 0 & 1 & 0 & d_{y} \\ 0 & 0 & 1 & d_{z} \\ 0 & 0 & 0 & 1 \end{array}\right] * \left(x,y,z, 1\right)=\left(x+d_{x},y+d_{y},z+d_{z}, 1\right) 100001000010dxdydz1(x,y,z,1)=(x+dx,y+d

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值