☰
MikeShah OpenGL 笔记(三)
2026/10/7 11:57:14 网站建设 项目流程

我们为网格提供了一个默认的变换位置,以便移动它们。目前,我们依次创建了两个网格,这是有意为之,因为我们尚未讨论深度测试。在完成一些重构后,我们会处理这个问题。

接着,我们创建图形管线。管线设置完成后,就可以设置网格。你可能会想,我们其实可以在一步中创建网格并设置管线。我们可能会在后续进行这样的重组,例如在程序启动前编译所有图形管线,然后将管线与网格关联。但目前,这是一个两步过程,每个函数只做一件事,这是一种合理的软件设计方法。

然后,我们进入主循环。在主循环中,我们基本上就是绘制网格,当然也处理输入(例如移动按键)。

重构Mesh绘制函数

现在,让我们看看mesh3D类。今天的目标是继续清理这个类。我们需要更多地思考之前提到的transform结构体。在上节课中,我们开始处理获取统一变量位置,因为需要在meshDraw函数中进行清理。

meshDraw函数目前看起来有些杂乱,我们将所有统一变量的设置集中在一起。但还有一些问题需要处理,例如如何处理我们为每个网格不断创建的透视相机。这个功能应该属于相机类本身。

因此,是时候移除这部分代码,将其改为两步操作。我们希望实现类似Gf.mcaa.git projection matrix这样的调用。这里称之为“投影矩阵”,因为我们不确定它将是透视投影还是正交投影。

为相机类添加投影矩阵

让我们打开Camera.cpp文件。就像我们有一个getViewMatrix函数一样,让我们创建一个getProjectionMatrix函数。它应该是一个常量函数。

我们可以在相机类中存储一个投影矩阵。在构造相机时,也许可以用一个默认的投影矩阵来初始化它。但这里隐藏了一些细节,我们可能想要改变它。

实际上,我们更希望有一个单独的函数来设置投影矩阵,而不是在构造函数中隐藏算法。因此,让我们创建一个setProjectionMatrix函数。

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_7.png

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_7.png

最好的方法是参考GLM的透视矩阵函数。GLM是一个高度模板化的库,如果你不习惯阅读C++代码,可能会有些挑战。

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_9.png

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_10.png

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_9.png

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a8e4_10.png

glm::perspective函数需要以下参数:视野(FOV,浮点数)、宽高比(浮点数)、近平面(浮点数)和远平面(浮点数)。它返回一个4x4矩阵。

因此,我们的setProjectionMatrix函数将简单地调用glm::perspective并设置成员变量m_projectionMatrix。

以下是该函数的核心代码:

voidCamera::setProjectionMatrix(floatfovY,floataspect,floatnear,floatfar){m_projectionMatrix=glm::perspective(fovY,aspect,near,far);}

在主程序中使用新的相机功能

现在,回到main.cpp文件。在初始化程序并设置相机之后,我们可以调用GApp.m_camera.setProjectionMatrix(...)来设置投影矩阵。我们需要传入视野、宽高比、近平面和远平面的值。

然后,在meshDraw函数中,我们不再直接计算透视矩阵,而是从相机中获取投影矩阵。我们可以这样调用:

glm::mat4 projection=GApp.m_camera.getProjectionMatrix();

这样,我们就将投影矩阵的计算和管理抽象到了相机类中。

测试与总结

编译并运行程序,应该能看到两个四边形正常显示,与重构前一样。这次重构减少了出错的可能性,使代码更加清晰,并进一步抽象了功能。

然而,仍然有一些小问题需要处理。例如,变换操作仍然在draw函数中处理,这负担过重。我们希望能够控制网格的平移、旋转和缩放。这将是下一节课的重点。

一旦我们有了可以操作的网格和基础框架,我们将深入介绍更多图形学入门知识。

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_12.png

本节课中,我们一起学习了如何将投影矩阵的计算从网格绘制函数中抽离,并将其封装到相机类中,使代码结构更加清晰和模块化。

040:为Mesh3D类添加平移、旋转和缩放操作 🎮

在本节课中,我们将学习如何为我们的Mesh3D类抽象出平移、旋转和缩放操作。我们将创建mesh_translate、mesh_rotate和mesh_scale函数,以简化模型变换的代码,使主渲染循环更加清晰。

概述

目前,我们的代码在mesh_draw函数中直接处理模型的旋转和平移,这与网格绘制本身的核心逻辑混杂在一起。为了建立更清晰的抽象,我们将把这些变换操作提取出来,作为Mesh3D类的独立功能。这样,mesh_draw函数可以专注于设置管线、传递uniform变量和实际绘制网格。

上一节我们介绍了Mesh3D类的基本结构和绘制流程,本节中我们来看看如何将变换操作从绘制函数中分离出来。

代码回顾与目标

首先,让我们简要回顾当前的代码结构。在我们的main函数和mesh_draw函数中,我们直接使用GLM库进行矩阵变换计算。例如,我们这样设置模型矩阵:

glm::mat4 model=glm::mat4(1.0f);model=glm::translate(model,glm::vec3(x,y,z));model=glm::rotate(model,glm::radians(angle),glm::vec3(0.0f,1.0f,0.0f));model=glm::scale(model,glm::vec3(scaleX,scaleY,scaleZ));

我们的目标是创建类似mesh_create、mesh_delete和mesh_set_pipeline的函数,来专门处理这些变换。我们将创建mesh_translate、mesh_rotate和mesh_scale函数。

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_1.png

重构Mesh3D结构体

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_2.png

为了实现变换的抽象,我们首先需要修改Mesh3D结构体,为其添加一个模型矩阵成员,用于存储该网格的当前变换状态。

typedefstructMesh3D{// ... 其他现有成员(如VBO、IBO、管线等)glm::mat4 model_matrix;// 新增:存储模型变换矩阵}Mesh3D;

在创建网格时,我们需要将model_matrix初始化为单位矩阵。

Mesh3Dmesh_create(...){Mesh3D mesh;// ... 初始化其他成员mesh.model_matrix=glm::mat4(1.0f);// 初始化为单位矩阵returnmesh;}

创建变换函数

现在,我们可以开始编写处理变换的独立函数了。这些函数将直接操作Mesh3D结构体中的model_matrix。

平移函数 (mesh_translate)

mesh_translate函数将网格平移到指定的(x, y, z)位置。它通过将平移矩阵与网格当前的模型矩阵相乘来实现。

平移矩阵的公式如下:

[ 1, 0, 0, Tx ] [ 0, 1, 0, Ty ] [ 0, 0, 1, Tz ] [ 0, 0, 0, 1 ]

其中Tx, Ty, Tz是平移量。

以下是该函数的实现:

/** * 平移一个网格,更新其模型矩阵。 * @param mesh 指向目标Mesh3D结构体的指针。 * @param x 沿X轴的平移量。 * @param y 沿Y轴的平移量。 * @param z 沿Z轴的平移量。 */voidmesh_translate(Mesh3D*mesh,floatx,floaty,floatz){mesh->model_matrix=glm::translate(mesh->model_matrix,glm::vec3(x,y,z));}

旋转函数 (mesh_rotate)

mesh_rotate函数使网格绕任意轴旋转指定角度(弧度制)。我们提供一个绕Y轴旋转的简化版本mesh_rotate_y。

旋转矩阵(绕Y轴)的公式较为复杂,GLM库为我们处理了这些计算。

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_4.png

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_5.png

以下是该函数的实现:

/** * 绕任意轴旋转一个网格。 * @param mesh 指向目标Mesh3D结构体的指针。 * @param angle_radians 旋转角度(弧度)。 * @param axis 旋转轴向量(例如 glm::vec3(0.0f, 1.0f, 0.0f) 代表Y轴)。 */voidmesh_rotate(Mesh3D*mesh,floatangle_radians,glm::vec3 axis){mesh->model_matrix=glm::rotate(mesh->model_matrix,angle_radians,axis);}/** * 绕Y轴旋转一个网格(便捷函数)。 * @param mesh 指向目标Mesh3D结构体的指针。 * @param angle_radians 绕Y轴的旋转角度(弧度)。 */voidmesh_rotate_y(Mesh3D*mesh,floatangle_radians){mesh_rotate(mesh,angle_radians,glm::vec3(0.0f,1.0f,0.0f));}

缩放函数 (mesh_scale)

mesh_scale函数对网格进行缩放。缩放可以是均匀的(所有轴比例相同)或非均匀的(各轴比例不同)。

缩放矩阵的公式如下:

[ Sx, 0, 0, 0 ] [ 0, Sy, 0, 0 ] [ 0, 0, Sz, 0 ] [ 0, 0, 0, 1 ]

其中Sx, Sy, Sz是缩放因子。

以下是该函数的实现:

/** * 非均匀缩放一个网格。 * @param mesh 指向目标Mesh3D结构体的指针。 * @param scale 包含X、Y、Z轴缩放因子的向量。 */voidmesh_scale(Mesh3D*mesh,glm::vec3 scale){mesh->model_matrix=glm::scale(mesh->model_matrix,scale);}/** * 均匀缩放一个网格(便捷函数)。 * @param mesh 指向目标Mesh3D结构体的指针。 * @param uniform_scale 所有轴的统一缩放因子。 */voidmesh_scale_uniform(Mesh3D*mesh,floatuniform_scale){mesh_scale(mesh,glm::vec3(uniform_scale));}

更新绘制函数

创建了变换函数后,我们需要更新mesh_draw函数。现在,它不再需要自己计算模型矩阵,而是直接使用Mesh3D结构体中存储的model_matrix。

更新后的mesh_draw函数核心部分如下:

voidmesh_draw(Mesh3D*mesh){if(!mesh_is_valid(mesh))return;mesh_set_pipeline(mesh);// 获取模型矩阵并传递给着色器GLint model_loc=glGetUniformLocation(mesh->pipeline,"model");glUniformMatrix4fv(model_loc,1,GL_FALSE,glm::value_ptr(mesh->model_matrix));// ... 绑定VBO、IBO并执行绘制调用(glDrawElements)}

在主循环中使用

现在,我们可以在主渲染循环中以更清晰的方式使用这些函数。以下是更新后的主循环示例:

// 初始化网格Mesh3D g_mesh1=mesh_create(...);Mesh3D g_mesh2=mesh_create(...);// 初始变换mesh_translate(&g_mesh1,0.0f,0.0f,0.0f);mesh_translate(&g_mesh2,0.0f,0.0f,-4.0f);mesh_scale(&g_mesh2,glm::vec3(1.0f,2.0f,1.0f));// 让第二个网格在Y轴上更高floatrotation_angle=0.0f;while(!glfwWindowShouldClose(window)){// ... 处理输入、清屏等// 更新旋转角度rotation_angle+=0.05f;// 应用变换mesh_rotate_y(&g_mesh1,rotation_angle);mesh_rotate_y(&g_mesh2,-rotation_angle);// 反向旋转// 绘制网格mesh_draw(&g_mesh1);mesh_draw(&g_mesh2);// ... 交换缓冲区、检查事件}

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_7.png

总结

本节课中我们一起学习了如何为Mesh3D类抽象出平移、旋转和缩放操作。通过创建mesh_translate、mesh_rotate和mesh_scale函数,我们将变换逻辑从绘制函数中分离出来,使代码结构更加模块化和清晰。

我们主要完成了以下工作:

  1. 在Mesh3D结构体中添加了model_matrix成员来存储变换状态。

  2. 实现了独立的变换函数,这些函数直接操作模型矩阵。

  3. 更新了mesh_draw函数,使其直接使用网格自身的模型矩阵。

  4. 在主循环中演示了如何使用新的API来轻松控制多个网格的变换。

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_9.png

https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_10.png

现在,我们拥有了一个更健壮的小型图形框架基础,可以方便地创建、变换和渲染多个网格对象。这为我们后续加载复杂几何体、添加颜色和光照等功能奠定了良好的基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询