ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MikeShah OpenGL 笔记(三)

MikeShah OpenGL 笔记(三) 我们为网格提供了一个默认的变换位置以便移动它们。目前我们依次创建了两个网格这是有意为之因为我们尚未讨论深度测试。在完成一些重构后我们会处理这个问题。接着我们创建图形管线。管线设置完成后就可以设置网格。你可能会想我们其实可以在一步中创建网格并设置管线。我们可能会在后续进行这样的重组例如在程序启动前编译所有图形管线然后将管线与网格关联。但目前这是一个两步过程每个函数只做一件事这是一种合理的软件设计方法。然后我们进入主循环。在主循环中我们基本上就是绘制网格当然也处理输入例如移动按键。重构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.pnghttps://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.pnghttps://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_10.pnghttps://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a67a8e4_9.pnghttps://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/7ac28094df232dad262990eb7a8e4_10.pngglm::perspective函数需要以下参数视野FOV浮点数、宽高比浮点数、近平面浮点数和远平面浮点数。它返回一个4x4矩阵。因此我们的setProjectionMatrix函数将简单地调用glm::perspective并设置成员变量m_projectionMatrix。以下是该函数的核心代码voidCamera::setProjectionMatrix(floatfovY,floataspect,floatnear,floatfar){m_projectionMatrixglm::perspective(fovY,aspect,near,far);}在主程序中使用新的相机功能现在回到main.cpp文件。在初始化程序并设置相机之后我们可以调用GApp.m_camera.setProjectionMatrix(...)来设置投影矩阵。我们需要传入视野、宽高比、近平面和远平面的值。然后在meshDraw函数中我们不再直接计算透视矩阵而是从相机中获取投影矩阵。我们可以这样调用glm::mat4 projectionGApp.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 modelglm::mat4(1.0f);modelglm::translate(model,glm::vec3(x,y,z));modelglm::rotate(model,glm::radians(angle),glm::vec3(0.0f,1.0f,0.0f));modelglm::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_matrixglm::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_matrixglm::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.pnghttps://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_matrixglm::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_matrixglm::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_locglGetUniformLocation(mesh-pipeline,model);glUniformMatrix4fv(model_loc,1,GL_FALSE,glm::value_ptr(mesh-model_matrix));// ... 绑定VBO、IBO并执行绘制调用glDrawElements}在主循环中使用现在我们可以在主渲染循环中以更清晰的方式使用这些函数。以下是更新后的主循环示例// 初始化网格Mesh3D g_mesh1mesh_create(...);Mesh3D g_mesh2mesh_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_angle0.0f;while(!glfwWindowShouldClose(window)){// ... 处理输入、清屏等// 更新旋转角度rotation_angle0.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函数我们将变换逻辑从绘制函数中分离出来使代码结构更加模块化和清晰。我们主要完成了以下工作在Mesh3D结构体中添加了model_matrix成员来存储变换状态。实现了独立的变换函数这些函数直接操作模型矩阵。更新了mesh_draw函数使其直接使用网格自身的模型矩阵。在主循环中演示了如何使用新的API来轻松控制多个网格的变换。https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_9.pnghttps://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/mike-shah-ogl/img/57994a811e1e96dcc6b36402dd61c3bd_10.png现在我们拥有了一个更健壮的小型图形框架基础可以方便地创建、变换和渲染多个网格对象。这为我们后续加载复杂几何体、添加颜色和光照等功能奠定了良好的基础。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进