基金会:代表三维空间

在绘制单个像素之前,3D引擎必须有一个数学语言来描述空间中的物体。在核心是 vectors [ (代表位置、方向或颜色的三元图案)和 matrices (编码转换的4×4阵列) 。在C中,这些阵列通常被定义为简单的结构:

  • 指点和矢量.
  • ,用于按列主序存储的变换矩阵。

每个对象都由三角组成. 三角形由三个顶点定义,每个顶点都有位置,并且可选地有一个正常和颜色。将这些顶点作为连续的阵列(例如,用于立方体)来储存,提高了缓存效率——这是以后每个帧处理数千面时一个关键考虑因素。

图形管道: 从Vertices到像素

图形管道是将3D场景数据转换成2D图像的阶段序列。在基本的软件授权引擎中,您手动执行每个阶段。主要阶段是:

  1. Vertex处理[] –应用模型,视图,和投影变换.
  2. sterization – 将变形三角形转化为碎片(潜在像素).
  3. shading –根据照明和材料计算每个碎片的颜色.
  4. 输出合并[] – 将碎片与帧缓冲器混合,包括深度测试.

硬件加速引擎(使用OpenGL,Vulkan,或DirectX)在GPU上执行大部分这些步骤,但理解软件路径可以让你深入了解GPU在引擎盖下的工作方式.

Vertex 处理和转换

每个顶点都以 模型空间 (相对于物体的局部坐标) 开始. 要将其定位于世界,您应用一个 模型矩阵[ ,该矩阵编码翻译,旋转和缩放. 然后, 视图矩阵将世界坐标转换为相机相对坐标(视图空间). 最后, 预测矩阵 地图将空间显示为一个正常坐标系统( clip 空间),其中视角司和视图绘图生成屏幕坐标.

在C中,变换函数看起来像:

  • –返回身份矩阵.
  • – 构建翻译矩阵.
  • –围绕X轴旋转.
  • – 乘以4元素矢量(同位坐标)的4×4矩阵.

软件中的顶点阴影器等效于所有顶点,每个顶点乘以组合的矩阵,并将结果存储为变形顶点缓冲.

投影:视角与正体

投影控制屏幕上显示深度。 [[FLT: 0]] 透视投影[[[FLT: 1]] 使远方物体显得较小, 创造了现实主义。 它的矩阵是从视野、 宽度比、 近和远剪切平面上构建的。 经典公式涉及将 x 和 y 组件除以 z( 在矩阵乘法之后) 。 在 C 中, 您计算一次投影矩阵, 并重用它每个框架 :

  • – 返回标准视角矩阵.

正体投影保持平行线条,并且更为简单,用于UI或CAD工具,不涉及视角分隔.

牧场化:填充三角

投影后,您有2D屏幕坐标( 如像素位置) 和深度值 (z) 。 光刻化将每个三角块分割成碎片, 覆盖整数像素坐标 。 经典算法是 [ [FLT: 0]] 扫描线转换 [[FLT: 1] :

  1. 按 Y 坐标排序三角形的三个顶点 。
  2. 向下走左右边缘,计算每条扫描线的x边界.
  3. 对于横向跨度的每个像素,通过跨三角线插图计算碎片的深度.
  4. 执行 [[FLT: 0] 深度测试 [[FLT: 1] : 将碎片的z与已经存在于深度缓冲中的值进行比较。 如果更近, 更新深度缓冲并计算碎片的颜色 。

在 C 中高效实施边缘行走算法需要紧凑的循环和谨慎使用固定点算术以避免浮点高空. 许多爱好者引擎从野蛮力方法(检查边框中的每个像素)开始,后来优化边缘方程.

遮蔽和照明

简单的平面阴影引擎使用每个三角形的单一颜色,从面部正常和一个光源计算。 朗贝蒂安模型[ 给出了扩散强度: [。在C中,你计算正常是两个三角形边缘的交叉产物(在将顶点转换为世界空间之后),然后将光强度乘以三角形的底色。

增加深度,执行Gouraud 阴影(顶点正常,跨碎片插头)或Phong 阴影[](每像素正常),后者要求插上世界位置和正常,然后计算每个碎片的照明——成本更高但效果却明显更好。

C型发动机的建设:实际步骤

让我们从头开始构建一个最小的软件3D引擎。 目标是将一个带有环境及分散照明的旋转立方体制成一个640x480像素窗口。

步骤1 - 设置像素缓冲

定义帧和深度的内存缓冲器: [[FLT: 10]] 和 [[FLT: 11] ]。 将帧缓冲器清除为黑色, 将深度缓冲器清除为每个帧的1.0 (远方平面) 。

步骤2 - 定义场景数据

对于立方体,六面(12三角形)需要12×3=36个顶点。它们存储在一系列结构中,位置(3个浮点)和颜色(3个浮点),预先定义8个独特的立方体角和6个面色。使用索引缓冲器来避免重复顶点。

步骤3 - 转换管道

每个框架, 从一个角度( 如旋转Y( 时间) ) 计算模型矩阵, 从固定相机中查看源头, 以及投影矩阵。 将它们组合成一个 MVP 矩阵 : [[ FLT: 12] ] 。 通过乘以 [ [FLT: 13]] 来转换每个顶点。 然后执行视角分隔( divide x,y,z by w) 来获得 正常化的设备坐标 (− 1 至 + 1) 。 显示坐标的地图 : [ [ [ [FLT: 14]] , [ [FLT 15]] (反转) 。 保留 NDC z 进行深度测试 。

步骤4: 缩小和缩小

对每个三角形,请使用扫描线方法。在走像素时,插入深度,并插入一个像世界空间位置(对Phong)或顶点正常(对Gouraud)那样的反向属性。应用兰贝特扩散方程。如果深度测试通过,则将最终颜色写入帧缓冲器。

步骤5 - 显示

使用 SDL( 简单直接媒体层) 或类似库来创建窗口并 blit 帧缓冲器。 例如, SDL2: [[FLT: 16]], [[FLT: 17]]], [[FLT: 18]]], 然后是 [[FLT: 19] ] , 然后是 [[FLT: 20] ] 和 [[FLT: 21]]] 每个帧 。

这个软件路径运行在CPU上,所以性能每个帧都限制在几千个三角形上,一旦舒适,就可以用OpenGL取代光栅层,将繁重的工作卸载到GPU,同时保持你的变换和场景逻辑在C中.

优化您的引擎

基本引擎缓慢。 几个优化对实时性能至关重要 :

  • 后脸凸动:[ 跳过三角形,其正常面朝镜头方向方向方向移动. 计算投影三角形的签名区域(以2D计),或用视图方向检查面部正常面的点产品.
  • Frustum culling: 测试一个物体的边框,以对抗视图的六平面。如果完全在外,则跳过整个物体。
  • 精点算术:[]在紧凑的光栅化循环中用整数数学取代浮点操作,许多发动机制造者使用16.16固定点进行扫描线插值.
  • 变形前缓存: 避免通过一次变形将网格的所有顶点都变形,然后使用指数来变换同一个顶点多次.
  • 锁框缓冲写道:[ 将像素按内存顺序写成(左对右,上对下),以最大化缓存点击.

这些技术可以将软件渲染器从3–5 fps推到60 fps,用于中等复杂度的场景(如几百个三角形).

扩展引擎

一旦一个坚实的基点存在,您可以递增添加以下特性:

  • 透视映射:[ 存储2D图像,并跨三角插座紫外线坐标. 执行双线滤波,以取得平稳结果.
  • 高级阴影:[] 添加光谱亮度(Blinn-Phong),环境的隐蔽,甚至阴影图.
  • 场景管理:[] 使用一个octree或BSP树来快速确定哪些物体是可见的.
  • 用户交互: 处理键盘和鼠标输入,以绕相机或选择对象.
  • 皮佩线抽象: 设计一个遮蔽系统:将顶点和片段函数定义为函数指针,使得不进行重编译而能够进行灵活的渲染.

资源和进一步阅读

为了加深你的理解,探索激发这个引擎的经典文本和在线教程:

在 C 中构建一个 3D 引擎是图形程序员的通过仪式。 它不仅教你如何写出高效的代码, 而且也教你们如何在三个维度中思考, 并编曲性能关键系统。 开始是一个单旋转立方体, 然后添加更多的三角形,纹理, 最后把它交给 GPU。 从原始 C 到一个完全功能化的引擎的旅程揭示了屏幕上每个像素背后的魔法 。