资讯详情

从零搭一个能跑的3D小车:OpenGL矩阵变换与渲染实践

📅 2026/10/15 19:49:20 | 华诺云谱 👁 阅读
从零搭一个能跑的3D小车:OpenGL矩阵变换与渲染实践
简介面向OpenGL入门与进阶学习者的3D小车漫游示例以场景纹理贴图和类封装演示基础三维交互。压缩包共69个文件包含obj模型、cpp/h源码、bmp纹理及psd设计稿等整体仅3.33MB轻量易解析且提供完整VC工程与可执行文件可直接运行并查看实现。功能上支持WASD控制方向、F12切换全屏、V切换三种视角固定世界视角、移动第三视角、移动第一视角PageDown/PageUp调节镜头远近右键可分别设置车身与轮胎颜色。代码按GameScene、CarPart、ButtonTex等模块组织便于学习场景管理、部件建模与纹理贴图流程实验源码由多个类协作实现便于二次修改与复用整体封装思路清晰从顶点处理到纹理映射均有对应模块可帮助读者理解三维小车从建模到交互的完整管线。已有1947人浏览下载适合图形学课程实验、三维漫游入门或OpenGL交互设计的自学参考。1. 从零搭一个会跑的3D小车OpenGL到底在解决什么问题很多第一次接触 OpenGL 实现 3D 小车的开发者以为难点在于“画一辆车”真正动手才发现难点全在“让这辆车在屏幕上以正确的姿态出现、还能被你控制着跑起来”。这个小项目最反直觉的地方在于最复杂的代码往往不是绘制本身的几百行顶点数据而是相机矩阵、模型矩阵、投影矩阵三者之间的乘法顺序以及坐标系到底谁是谁的父级。本文按“渲染骨架 → 观察环境 → 小车模型 → 交互控制 → 排错思路”这条路径展开覆盖从 VAO/VBO 到键盘响应的一整套最小实现适合学过 OpenGL 基础但没完整做过一个可交互三维物体的同学也适合想快速把“绘制一个复合物体”这件事落到代码上的熟手。2. 先立住渲染骨架VAO/VBO、着色器与矩阵变换的顺序2.1 为什么小车这种“静态网格”最适合从 VBO 开始缓冲对象的作用边界3D 小车的几何体可以拆成车身、底盘、四个轮子、车窗几部分整体看是一个“顶点数据基本不变只有矩阵在变”的场景。这种特性决定了它天然适合把顶点数据一次性交给 GPU后续每一帧只更新矩阵而不是反复把顶点数据从 CPU 拷贝到 GPU。这背后就是 VBO顶点缓冲对象存在的意义把顶点坐标、法线、颜色这些静态数据存进显存绘制时直接按偏移量取用。我一般会把小车各部件各自的顶点数据分别放进不同的 VBO而不是把整个小车当作一个整体塞进单个缓冲。原因在于轮子需要绕自己的轴旋转、车身需要绕自己的中心转向如果用单个 VBO 一次性提交所有顶点你得在 CPU 端手动修改顶点坐标才能实现旋转那就完全失去 GPU 加速的意义了。把每个部件拆成独立的 VAO/VBO 组合绘制时用对应的模型矩阵去驱动每个部件控制逻辑会清晰很多。实际落地时先别急着写小车先搭一个能显示出三角形的裸框架。顶点数据用一个简单的立方体即可后面小车所有部件都是立方体、长方体、圆柱体的组合。这个“先跑通裸框架再拼复杂模型”的顺序能帮你把 OpenGL 初始化、着色器编译、矩阵设置这些底层的坑先排干净免得最后分不清问题是出在数学上还是出在渲染管线上。2.2 最小可运行框架从窗口初始化到第一次画出三角形下面这段代码是搭建一个最小窗口并绘制一个彩色三角形的基础流程用的是 C 搭配 GLFW 做窗口GLAD 做函数指针加载这是目前最常见的组合方式。// 初始化 GLFW并指定 OpenGL 版本与核心模式 glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 创建窗口640x480 在本阶段足够用后期再改大 GLFWwindow* window glfwCreateWindow(640, 480, 3D Car, NULL, NULL); glfwMakeContextCurrent(window); // 加载 OpenGL 函数指针 gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); // 编译顶点着色器与片元着色器并将它们链接成着色器程序 unsigned int shaderProgram glCreateProgram(); // 这里省略 glCompileShader / glAttachShader 的具体代码 // 但注意每一步都要检查编译状态出错时打印日志别等黑屏才后悔 // 定义三角形三个顶点的位置和颜色交错排列 float vertices[] { // 位置 // 颜色 -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f }; unsigned int VAO, VBO; glGenVertexArrays(1, VAO); glGenBuffers(1, VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 告诉 GPU 如何解析顶点数据前 3 个 float 是位置后 3 个是颜色 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // 渲染循环 while (!glfwWindowShouldClose(window)) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glfwSwapBuffers(window); glfwPollEvents(); }这段代码的核心在于 glVertexAttribPointer 里最后两个参数第一个是步长告诉 GPU 每个顶点占用 6 个 float第二个是偏移量颜色数据从第 3 个 float 开始。只要你把这两个参数写错一个画面要么不出东西要么颜色完全错乱。顶点数据的解析规则是 OpenGL 最容易出错的地方建议在跑小车之前先用三角形把这一整套流程验证熟再去碰复合模型。2.3 MVP矩阵的乘法顺序为什么“看起来应该对”的代码总是黑屏很多新手写矩阵时习惯写成“模型 × 视图 × 投影”但实际代码里矩阵乘法的顺序和视觉直觉往往是反的。原因是顶点最终要经历四次变换局部坐标 → 世界坐标 → 观察坐标 → 裁剪坐标。用 glm 库时正确的组合方式是 投影矩阵 × 视图矩阵 × 模型矩阵然后乘上顶点坐标因为 glm 遵循列主序矩阵从右往左作用于向量。glm::mat4 model glm::mat4(1.0f); model glm::translate(model, glm::vec3(0.0f, 0.0f, -3.0f)); model glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f)); glm::mat4 view glm::lookAt( glm::vec3(0.0f, 2.0f, 5.0f), // 相机位置略高于地面从前方斜看 glm::vec3(0.0f, 0.0f, 0.0f), // 观察目标点场景中心 glm::vec3(0.0f, 1.0f, 0.0f) // 上方向Y 轴 ); glm::mat4 projection glm::perspective( glm::radians(45.0f), // 垂直视场角45 度是常见起始值 640.0f / 480.0f, // 宽高比必须与窗口尺寸一致 0.1f, // 近裁剪面太小会导致深度精度问题 100.0f // 远裁剪面小车场景 100 足够 ); glm::mat4 mvp projection * view * model; glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp[0][0]);这里最关键的参数是投影矩阵里的宽高比和 near/far 距离。如果你用 640×480 的窗口但宽高比写成 1.0画面里的物体会明显被横向拉伸。near 值如果小到 0.001深度缓冲区精度会被浪费在极近的范围内远处两个物体之间的遮蔽关系会变得错乱。这个参数对后续小车在场景里移动的影响极大建议一开始就固定成 0.1 和 100.0后面再根据实际需要调整。2.4 让坐标系可视化地面网格与坐标轴辅助线纯黑背景里调试 3D 小车特别难受因为你无法判断小车到底是在往前走还是只是相机在动。我的做法是先画一个地面网格一组沿 X 轴和 Z 轴延伸的线段颜色用灰白色。这样小车的移动、转向、轮子旋转都能立刻在视觉上找到参照物。网格的顶点数据可以在 CPU 端用循环生成一次然后放进 VBO 里。std::vectorfloat gridVertices; float halfSize 10.0f; // 网格从 -10 到 10 float step 1.0f; // 每条线间隔 1 个单位 for (float i -halfSize; i halfSize; i step) { // 平行于 X 轴的线 gridVertices.push_back(-halfSize); gridVertices.push_back(0.0f); gridVertices.push_back(i); gridVertices.push_back(halfSize); gridVertices.push_back(0.0f); gridVertices.push_back(i); // 平行于 Z 轴的线 gridVertices.push_back(i); gridVertices.push_back(0.0f); gridVertices.push_back(-halfSize); gridVertices.push_back(i); gridVertices.push_back(0.0f); gridVertices.push_back(halfSize); }整条网格线数量才 84 个顶点每帧循环里传一次不会有任何压力。线段绘制时要记住把 glDrawArrays 的绘制模式改成 GL_LINES而不是 GL_TRIANGLES。网格最大的价值在于当小车转向时你立刻能看出轮子是否在绕正确的轴旋转、车身是否有不自然的倾斜这些信息靠“读代码”是读不出来的必须靠视觉参照。3. 搭一个能看出“3D感”的观察环境摄像机、透视与基础光照3.1 透视投影的视锥参数怎么定fov、宽高比、near/far的连锁反应透视投影的四个参数每个都是连环扣。视场角 fov 越大看到的场景越广但小车的畸变也越明显尤其在屏幕边缘时车身会拉长fov 越小场景看起来越“扁”像长焦镜头效果。我一般把垂直 fov 设在 45 到 60 度之间小车的观察距离在 5 到 8 个单位之间这样既能看到车全身又不会变形到失真。宽高比必须每次窗口大小变化时同步更新。常见的坑是只在初始化时计算一次宽高比之后窗口一拉大整个画面被拉伸或压扁。正确做法是注册一个 framebuffer 回调在窗口尺寸改变时同时更新 glViewport 和投影矩阵。忘了glViewport(0, 0, width, height)是另一个隐蔽问题画面会只渲染窗口的一部分区域其他区域保持清屏色。near 和 far 的距离差直接影响深度缓冲区的精度。near 越小、far 越大深度值分布越不均匀靠近相机的位置能分辨的深度层次多远处几乎糊成一片。小车场景的物体都在 20 个单位以内far 设成 100 已经够保守了不要随手写 1000那会让远处的地面网格出现不明原因的闪烁。3.2 第一人称摄像机与欧拉角绕Y轴转向为什么容易“翻车”观察小车有很多种方式最简单的是固定轨道环绕相机始终看向小车位置用鼠标或按键控制环绕角度。比自由摄像机更好实现也更能体现小车的运动状态。实现环绕的代码本质上是把相机位置放在一个半径为 r 的圆上yaw 角控制左右旋转pitch 角控制上下俯仰。float yaw 0.0f; // 绕 Y 轴左右旋转 float pitch 10.0f; // 绕 X 轴上下俯仰10 度适合俯视小车 float radius 8.0f; // 相机离目标的距离 glm::vec3 target glm::vec3(0.0f, 0.0f, 0.0f); glm::vec3 camPos target glm::vec3( radius * glm::cos(glm::radians(yaw)) * glm::cos(glm::radians(pitch)), radius * glm::sin(glm::radians(pitch)), radius * glm::sin(glm::radians(yaw)) * glm::cos(glm::radians(pitch)) ); glm::mat4 view glm::lookAt(camPos, target, glm::vec3(0.0f, 1.0f, 0.0f));这套环绕相机最容易翻车的地方在 pitch 角接近 90 度时cos(pitch) 趋近于 0camPos 的 XZ 分量趋近于 0相机几乎垂直往下看转动 yaw 时画面会出现剧烈的跳变。解决办法是把 pitch 限制在 -89 到 89 度之间。另一个坑是 glm::lookAt 的第三个参数不能和视线方向平行否则矩阵会出现退化画面瞬间黑掉。这些边界条件是小车项目里最容易被忽略的“翻车”点建议写进自己的模板代码里每次新建项目直接带上。3.3 最小光照模型漫反射环境光让车身不再是一块纯色没有光照时小车就是一块纯色塑料无法体现车身的立体感和各部件之间的层次。加光照不一定非要上复杂的 Blinn-Phong一个漫反射加环境光已经足够让小车显得立体。漫反射需要每个顶点有法线数据这也是为什么前面建议每个部件的顶点数据要带上法线而不是只有位置。在小车的顶点着色器里法线要先经过模型矩阵的逆矩阵转置变换再传入片元着色器。// 顶点着色器中的关键代码 uniform mat4 model; uniform mat4 view; uniform mat4 projection; in vec3 aPos; in vec3 aNormal; out vec3 Normal; out vec3 FragPos; void main() { FragPos vec3(model * vec4(aPos, 1.0)); Normal mat3(transpose(inverse(model))) * aNormal; gl_Position projection * view * vec4(FragPos, 1.0); }// 片元着色器中的关键代码 uniform vec3 lightPos; uniform vec3 lightColor; uniform vec3 objectColor; in vec3 Normal; in vec3 FragPos; void main() { float ambientStrength 0.15f; vec3 ambient ambientStrength * lightColor; vec3 norm normalize(Normal); vec3 lightDir normalize(lightPos - FragPos); float diff max(dot(norm, lightDir), 0.0f); vec3 diffuse diff * lightColor; vec3 result (ambient diffuse) * objectColor; gl_FragColor vec4(result, 1.0); }这里最容易让人困惑的是为什么法线不能直接使用模型矩阵变换。如果模型矩阵里带有非等比缩放顶点的位置被拉伸了但法线直接跟着变换后就不再垂直于表面了光照就会在局部区域出现不自然的明暗断裂。解决方法是使用模型矩阵的“逆转置矩阵”去变换法线。在纯旋转和平移时这个变换与原始模型矩阵结果一致所以很多小车项目之前没加光照时完全没问题一加光照就出“阴阳脸”罪魁祸首就在这一行。4. 把小车拼出来并存档层次模型、轮子旋转与键盘控制4.1 用glm做层级变换车身、轮子、车轴之间的坐标关系小车的结构本质是一个树形层级车身是根节点四个轮子挂在车身下轮子的位置依赖车身的位置和朝向。这种父子关系在 OpenGL 里用模型矩阵的复合来实现先算车身的模型矩阵再在车身坐标系内计算每个轮子相对车身的偏移最后轮子的世界矩阵 车身矩阵 × 轮子局部矩阵。代码里体现为多次 glm::translate 和 glm::rotate 的连续调用。// 车身长 4、宽 2、高 1底部在 y0 处中心在 (0, 0.5, 0) glm::mat4 bodyModel glm::mat4(1.0f); bodyModel glm::translate(bodyModel, glm::vec3(0.0f, 0.5f, 0.0f)); // 左前轮车头方向为 Z车身宽 2轮子在 X 轴方向偏移 ±1.1 // 为简化先调整轮子自身坐标轴使轮子扁平面可以绕 Z 轴滚动 glm::mat4 wheelModel bodyModel; wheelModel glm::translate(wheelModel, glm::vec3(-1.1f, -0.2f, 1.2f)); wheelModel glm::rotate(wheelModel, wheelAngle, glm::vec3(0.0f, 0.0f, 1.0f)); // 右前轮X 轴正方向偏移 glm::mat4 wheelModel2 bodyModel; wheelModel2 glm::translate(wheelModel2, glm::vec3(1.1f, -0.2f, 1.2f)); wheelModel2 glm::rotate(wheelModel2, wheelAngle, glm::vec3(0.0f, 0.0f, 1.0f));车轮形状用圆柱体或拉伸的立方体都可以。圆柱体的默认轴向是 Y 轴但车轮需要绕 Z 轴滚动所以要么在建模时把圆柱的轴向改成 Z 轴要么用一个额外的旋转矩阵把 Y 轴对齐到 Z 轴。我通常的做法是先把圆柱绕 X 轴旋转 90 度再让它的高度轴与 Z 轴对齐这样后面控制轮子滚动时只需要累加 wheelAngle 即可不用反复处理坐标轴偏移。4.2 轮子旋转的两种做法顶点绕车轴旋转与模型矩阵累积轮子旋转有两种实现方式效果看起来一样但代码结构和后续扩展性差别很大。第一种是在 CPU 端直接修改轮子顶点数据每帧重建圆柱顶点第二种是保持顶点数据不变每帧更新轮子的模型矩阵让它绕自身轴线旋转。绝大多数情况下应该选第二种。第一种做法在小车上会很快暴露问题四个轮子各有独立的旋转状态如果每帧都修改顶点数据CPU 要重新计算几十个顶点并重新上传虽然数量不多但后续如果加大车体细节、加入车轮辐条性能会立刻吃紧。第二种做法只需要定义一个 wheelAngle 变量每帧增加增量四个轮子分别用这个角度乘上各自的位置矩阵即可。// 帧循环内更新轮子角度speed 由键盘状态决定 wheelAngle wheelSpeed * deltaTime; // 轮子滚动的距离 轮子半径 × 转角反过来也能推算车速这里有个细节轮子转向方向盘拐弯和轮子滚动是两回事。前轮的“转向”是绕 Y 轴旋转让轮子朝向改变滚动是绕 Z 轴旋转模拟前进。这两个旋转要分别维护顺序必须先是转向再是滚动否则转向后滚动的轴就错了轮子会歪着转。4.3 键盘交互的输入状态管理按住W时轮子持续转而不是按一下转一下新手最容易犯的错误是在按键回调里直接改变小车的位移或轮子角度结果按住 W 时小车只是“走了一步”然后就停了。原因是 GLFW 的按键回调是“按下瞬间触发一次”而不是“按住期间持续触发”。正确做法是维护一个布尔状态数组在回调里只更新状态在帧循环里根据状态持续修改运动和朝向。bool keys[1024]; void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action GLFW_PRESS) keys[key] true; else if (action GLFW_RELEASE) keys[key] false; } // 帧循环里 float speed 5.0f; // 小车前进速度单位/秒 float turnSpeed 40.0f; // 转向速度度/秒 if (keys[GLFW_KEY_W]) { carZ speed * deltaTime; wheelSpeed speed / wheelRadius; // 由线速度换算角速度 } else if (keys[GLFW_KEY_S]) { carZ - speed * deltaTime; wheelSpeed -speed / wheelRadius; } else { wheelSpeed 0.0f; }这里借用了物理里的线速度与角速度关系轮子滚动一圈车前进的距离等于轮子的周长。已知车速和轮子半径就能算出轮子的角速度。这个方法不仅让视觉上轮子与车身同步更重要的是让小车加速和减速时不会出现“轮子不转但车在动”这种让人瞬间出戏的问题。转向时同理把 yaw 角度增量乘以 deltaTime让转向速度独立于帧率。5. 避坑从黑屏到破面的7个常见问题排查5.1 深度测试没开先画车身还是先画轮子结果完全不同现象小车的轮子被车身挡住时却能透过车身看到轮子或者车身和轮子交叠的部分出现闪烁的碎面。原因OpenGL 默认不启用深度测试后绘制的物体总是覆盖先绘制的物体与距离无关。解决在初始化时调用glEnable(GL_DEPTH_TEST)并在每帧清屏时同时清除深度缓冲glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)。这是所有 3D 项目首先要确认的一件事也是新手问“为什么我画出来的东西前后关系不对”时最常见的答案。5.2 矩阵乘法的行主/列主混淆为什么模型会“漂移”而不是“旋转”现象小车转动时不是绕自身中心旋转而是绕场景原点做大半径圆周运动。原因模型矩阵的乘法顺序写反了可能是 translate 写在了 rotate 之前也可能是 glm 的矩阵被误当成行主序处理。解决牢记 glm 是列主序矩阵构造时先 rotate 再 translate代码里读作“先旋转再移动”。如果先移动后旋转旋转的中心就变成了原点而非小车自身中心。同样的坑在轮子转向时也会出现轮子会绕着车身原点画弧线而不是原地转向。5.3 法线没有用模型矩阵的逆矩阵转置变换光照“阴阳脸”现象小车的某个面在光照下出现明显的明暗分割或者整个面看起来像被油污覆盖光照方向完全不对。原因模型矩阵里有非等比缩放时直接变换法线会改变法线的方向。解决顶点着色器里用mat3(transpose(inverse(model)))变换法线。这个操作每一帧都在做矩阵求逆性能有开销但小车场景顶多几十个顶点完全不用在意。如果以后做大场景再把逆矩阵预计算后传入 uniform。5.4 窗口缩放后物体被拉扯视口与宽高比不同步现象拉动窗口边缘时小车被横向或纵向拉长甚至超出窗口边界。原因投影矩阵里的宽高比和实际的 glViewport 尺寸不一致。解决注册 framebuffer size 回调在回调里同时调用 glViewport 并重新计算投影矩阵。不要只更新 glViewport 而忘记投影矩阵也不要只更新投影矩阵而忘记视口两个必须同步。5.5 键盘事件里直接加矩阵旋转小车“瞬间转头”的玄学现象按一下左键小车不是平滑转向而是突然跳到另一个角度甚至直接掉头。原因在按键回调里直接把 yaw 加一个固定值回调触发一次只转一次帧循环里没有持续状态。解决用布尔数组保存按键状态在帧循环里用yaw turnSpeed * deltaTime持续累加角度。这样控制键按住时每帧都会计算增量小车才能平滑转向。5.6 视口坐标与顶点坐标混用相机和灯光永远对不上现象灯光明明设在车头方向但光照效果却出现在侧面或者身后。原因灯光位置用了世界坐标但相机矩阵的观察空间坐标没有转换灯光位置或者灯光位置被错误地写成了视口坐标。解决灯光位置保持在世界坐标系中在片元着色器里使用 FragPos世界坐标计算光照方向不要用 gl_Position 的坐标。最简洁的做法是把所有坐标系统统一为世界坐标相机变换只在最后输出 gl_Position 时使用。5.7 循环里 glBindVertexArray 顺序错了部件之间“串数据”现象四个轮子的位置或颜色互相污染或者车身显示成轮子的样子。原因每次绘制不同部件时要么忘了绑定对应的 VAO要么绑定了 VAO 但没有绑定对应的着色器 uniform。解决绘制顺序固定为“绑定部件 VAO → 设置该部件模型矩阵 → 绘制”每绘制一个部件重复一遍。不要试图先绑定所有 VAO 再统一绘制那样你必须手动保存每个部件的矩阵反而更容易串。6. 让小车“活”起来平滑插值、轨迹尾迹与性能验证6.1 用时间差驱动轮子角速度而不是固定步长固定步长更新逻辑在小车里最容易出现的问题是帧率波动时小车的速度和轮子转速脱节。解决是用每帧的时间差 deltaTime 去乘角速度让每一帧的转动量与真实时间成正比。获取 deltaTime 的常见方式是用glfwGetTime()两次相减。注意 deltaTime 的单位是秒角速度的单位是弧度/秒别把角度制和弧度制混在一起否则把glm::rotate传入角度时小车可能一秒钟凭空多转 57 倍。6.2 给小车加一条尾迹线验证运动轨迹是否平滑一个性价比极高的调试技巧在场景地面画一条尾迹线记录小车每隔固定时间经过的位置。你立刻能看出小车直线是否走直、转弯是否平滑、是否发生滑移。实现上用一个环形数组保存最近 200 个位置点每隔 3 帧采样一次用 GL_LINE_STRIP 模式绘制成一条线。尾迹线还有一个价值当相机环绕时能确认小车是在原地转圈还是真的在朝前方移动这对旋转和位移顺序的排错特别有效。6.3 怎么看性能顶点数 vs 状态切换哪个才是瓶颈小车这么简单的场景顶点数几千个以内都不可能有性能问题。真正影响性能的是“状态切换”每次切换 VAO、切换着色器程序、切换纹理GPU 都会有一定开销。所以绘制小车时把轮子、车身、车窗分组先把同类型的部件排在一起绘制尽量减少状态切换次数。如果发现帧率有明显下降先用显卡工具看 Draw Call 数量而不是盯着顶点数发呆——小车的多边形数量远没有到让 GPU 吃不消的地步。以前我做这个小项目时光在轮子转向和车身移动的顺序上就折腾了两个晚上最后用尾迹线才看出小车是在“漂移”而不是“转弯”。后来养成一个习惯凡是涉及多个部件的坐标变换一律先在地面用线段画参考点再用真实模型叠加。这个习惯帮我排掉了后面很多更复杂的雷。希望这篇笔记能让你少走我走过的弯路做出一辆看得过去、跑得起来的小车。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑