尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

DirectX11简单3D引擎开发实战:从零构建渲染系统

发布时间:2026/9/8 14:58:07

资讯中心
01
ARTICLE

DirectX11简单3D引擎开发实战:从零构建渲染系统

DirectX11简单3D引擎开发实战:从零构建渲染系统
简介基于DirectX 11实现的轻量级3D引擎面向具备C图形编程基础、希望深入学习渲染管线与引擎工具链的开发者。引擎采用自定义的.mesh网格格式和.terrain地形格式并附带面向Blender的网格导出插件、Python编写的地形生成脚本可将位图高度图一键转换为引擎可用的地形文件同时支持DDS纹理加载、顶点着色器与像素着色器的加载应用提供基于DirectInput的输入处理、DirectWrite文本渲染以及定向光、法线贴图和计时器等实用功能模块代码组织较为清晰。压缩包共包含116个文件大小约10.39MB内部以h/cpp程序源码为主辅以dds纹理、mesh/obj模型、vsh/psh着色器、py工具脚本和Visual Studio工程文件便于读者按模块研读和二次开发。目前已有55人学习下载适合希望通过一个完整小型引擎实例掌握DirectX 11程序框架、自定义资源格式设计以及Blender与引擎管线的对接方法。1. 项目概述这个“简单3D引擎”到底做了什么拿到这个标题“DirectX11 上的简单 3D 引擎.zip”我先说结论这不是一个能用现成引擎搞开发的项目而是你自己从零搭一套基于 DirectX 11 的小型渲染系统最后打包成 zip 分享给别人的那种练手作品。里面通常包括源码、编译好的 exe、可能还有一两个模型和贴图资源。如果你正在学图形学想弄明白“屏幕上那个三角形到底是怎么画出来的”那这个东西就非常适合你拆开研究。这个项目解决的痛点很明确现在主流引擎要么太重UE5 拉一次工程几个 GB要么封装太狠Unity 让你拖个 Cube 就完事底层一概不知。而 DirectX 11 恰好是一个“足够底层但又不至于折磨人”的 API。你既要自己管理设备、交换链、渲染状态、着色器编译又不需要像 DX12 那样手动搞描述符堆和同步机制。用 DX11 写一个引擎本质上是把“渲染一棵物体”这件事的全部环节亲手过一遍。适合什么人看两类一类是刚学完 C、想进入图形学领域的初学者可以把这个项目当作图形学第一个大作业另一类是用过 Unity/UE、想了解引擎内部机制的人。这篇博文我会按自己做这个项目的完整思路来讲从整体设计、核心模块、实操流程到踩坑记录尽量把“为什么这么做”也讲清楚。2. 整体设计与方案选型2.1 为什么选 DirectX 11而不是 Vulkan 或 DX12先说一个观点如果你只是做学习用引擎DX11 是性价比最高的选择。Vulkan 和 DX12 虽然在现代显卡上性能更好但它们把内存管理、屏障同步、队列提交这种底层细节全部甩给你。我见过不少新手一上来就写 Vulkan结果光一个管线和提交逻辑就折腾了两个月最后连个三角形都没出来。DX11 里有ID3D11DeviceContext这样一个“上帝对象”你绑定什么它就记什么绘制时直接提交即可免掉了大量心智负担。另一个重要原因是资料丰富。从微软官方文档到各种博客、GDC 分享DX11 相关的方案非常成熟。遇到问题搜一下基本都有解这在你独立开发一个引擎时是很大的优势。而且现代显卡驱动对 DX11 的兼容性打磨了十几年你不用担心“为什么我的显卡跑不出来”这种硬件层面的问题。硬件要求也很亲民只要支持 DirectX 11 的显卡2010 年以后的 GPU 基本都行就能跑不需要专门的高端卡。对于追求“低门槛快速验证渲染思路”的人来说DX11 就是最优解。2.2 引擎骨架别上来就拆成几十个模块很多人在设计引擎时容易犯一个毛病一上来就按商业化标准设计搞一堆 DLL、插件系统、资源热更新。我觉得做“简单 3D 引擎”就应该有简单的样子。我自己的项目把代码分成五个部分平台层窗口创建、消息循环、输入事件就是 Win32 的部分这部分没法避免。核心层数学库封装、日志系统、时间管理。渲染层D3D11 设备管理、渲染状态管理、着色器编译管理。资源层模型加载、纹理加载、材质描述。场景层物体节点、相机、灯光、简单的 Update 循环。这里有一个重要的设计取舍不要纠结设计模式。网格加载器就是一个函数相机就是一个结构体加几个方法渲染器就是一个大类。等你真的需要抽象的时候再抽象否则就是过度设计。我做这个项目的时候就犯过错——把网格、材质、渲染器之间搞了五层继承结果改一个顶点格式要动六个文件。2.3 数学库自研还是第三方数学库是引擎基础但我建议直接用微软官方的 DirectXMath。它用 SIMD 指令优化过矩阵变换、向量计算性能很好而且它和 DX11 的常量缓冲区要求完全对齐——结构体要 16 字节对齐这类问题它是天然满足的。自己手写矩阵乘法当然可以你想加深理解的话甚至在纸上推一遍都行。但放到引擎里求解逆矩阵、透视投影矩阵这种操作手写版本容易出坑比如除零、行主序列主序混淆。DirectXMath 里的XMMatrixPerspectiveFovLH、XMMatrixLookAtLH这些函数名字直接告诉你用的是左手坐标系还是右手坐标系不容易搞混。3. 核心模块拆解与实现要点3.1 初始化流程设备、交换链、深度缓冲这是引擎最基础的“铁三角”。创建流程大致如下先用D3D11CreateDevice创建设备和上下文再用IDXGIFactory创建交换链。这里有个顺序问题我强调一下标准的创建顺序是先用 D3D11CreateDevice 拿设备再用设备所在的 DXGI 适配器创建交换链而不是反过来。如果你直接创建交换链再创建设备某些显卡上会拿到一个匿名的适配器性能状态不对。深度缓冲这东西新手特别容易漏。没有深度缓冲区你的模型就会出现“后面的面把前面的面挡住了”这种透视错误。深度缓冲的本质是一张和后备缓冲尺寸相同的纹理保存每个像素的深度值。你要给这张纹理创建对应的深度模板视图DSV并且在每帧绘制前调用OMSetRenderTargets把渲染目标和 DSV 一起绑定。// 创建深度贴图纹理 D3D11_TEXTURE2D_DESC depthDesc {}; depthDesc.Width width; depthDesc.Height height; depthDesc.MipLevels 1; depthDesc.ArraySize 1; depthDesc.Format DXGI_FORMAT_D24_UNORM_S8_UINT; depthDesc.SampleDesc.Count 1; depthDesc.BindFlags D3D11_BIND_DEPTH_STENCIL; device-CreateTexture2D(depthDesc, nullptr, depthTexture); device-CreateDepthStencilView(depthTexture, nullptr, depthView);这里格式用D24_UNORM_S8_UINT前 24 位存深度后 8 位存模板。如果你的场景不用模板测试可以换成只读深度的用法来提升性能但入门阶段没这个必要。3.2 数学库与变换系统世界矩阵、视图矩阵、投影矩阵3D 引擎的整个渲染过程可以简单理解成“把一个三维坐标经过三次矩阵变换变成屏幕上的二维坐标”。这三个矩阵分别是世界矩阵、视图矩阵、投影矩阵。世界矩阵把物体从模型空间搬到世界空间你旋转、缩放、移动物体就是在改这个矩阵。视图矩阵以相机为基准的世界坐标系转换本质是把相机放到原点并让它的观察方向指向负 Z 轴。投影矩阵把视锥体内的顶点压到裁剪空间这个矩阵里包含了 FOV、宽高比、近远裁剪面信息。很多人在写引擎时会弄混“矩阵乘法顺序”的问题。在 DX11 里顶点变换的顺序是World * View * Projection也就是列向量从右往左乘。代码里通常是WVP World * View * Proj。我踩过这个坑方向反了之后物体就不见了因为你把它变换到了完全错误的空间。组件设计上我给每个可渲染物体挂一个 Transform 结构体里面存位置、旋转四元数、缩放三个量。每次需要矩阵时现算而不是缓存矩阵。对于简单引擎这完全够用还能少处理“矩阵失效更新”的bug。3.3 渲染管线的核心状态设置D3D11 的渲染状态主要分三类光栅化状态Rasterizer State、深度模板状态Depth-Stencil State、混合状态Blend State。在简单引擎里光栅化状态至少要配两个——一个画实心三角形一个画线框模式方便调试。深度状态有一个常见的坑如果你没有创建深度状态对象默认深度测试是关闭的就会出现“透明”的模型互相穿插。混合状态如果不设置默认是覆盖写做透明物体时就会失效所以至少要做两个混合状态——不透明和 Alpha Blend。边界很重要。光栅化状态里DepthBias和SlopeScaledDepthBias这两个参数很少人动但当你处理 Z-fighting两个面几乎平行叠在一起时的不停闪烁时非常有用。比如画地面棋盘格和贴花的时候给其中一个设一点小的 DepthBias 就能解决。3.4 网格与模型加载从写死三角形开始写引擎的第一天我建议先不要接任何模型文件直接在顶点缓冲区里写死三个顶点画三角形。确认这条链路通了之后再考虑加载模型。加载 OBJ 是一个不错的选择格式简单纯文本网上资源一大堆。OBJ 本身只存顶点位置、法线、UV、三角形索引。但注意 OBJ 的坐标可能是右手系DX11 是左手系加载进来可能要翻转 Z 轴或调整索引顺序否则模型法线会反。顶点格式可以先定死位置float3 法线float3 纹理坐标float2。不用搞什么灵活的顶点声明描述简单引擎里一套就够了。一个顶点 32 字节对齐也方便。网格上传 GPU 的关键是把顶点数据拷贝到默认堆Default Pool再创建一个上传堆Staging Pool中途搬运。这里有个新手常犯的错误每帧更新动态顶点缓冲区时频繁 Map/Unmap 整个缓冲导致性能极低。正确的做法是给动态顶点数据创建一个足够大的缓冲区每帧只更新一部分区域用Map的D3D11_MAP_WRITE_DISCARD或者用多个缓冲区轮换。3.5 光照与材质让画面不再是纯色一个只有深色的引擎没意思光照系统是让场景“活起来”的关键。简单引擎里光照可以先用经典的 Phong 模型环境光 漫反射 高光。漫反射的计算公式是max(dot(N, L), 0) * lightColor * baseColor这里 N 是法线、L 是光线方向从顶点指向光源。高光项pow(max(dot(R, V), 0), shininess)R 是反射方向、V 是视线方向。这个模型虽然旧但实现简单、效果直观。为了实现光照你需要把相似的数据打包进常量缓冲区。我建议的布局struct ConstantBufferPerFrame { DirectX::XMMATRIX viewProj; // 视图*投影 矩阵 DirectX::XMFLOAT4 lightDir; // 平行光方向 DirectX::XMFLOAT4 lightColor; DirectX::XMFLOAT4 ambientColor; }; struct ConstantBufferPerObject { DirectX::XMMATRIX world; // 世界矩阵 DirectX::XMFLOAT4 baseColor; // 基础颜色 };这里有个细节常量缓冲区的最小大小是 16 字节的倍数如果你的结构体不是 16 的倍数D3D11 会报错或者出奇怪的 bug。所以即便你只需要一个 float也最好用一个XMFLOAT4把它塞满。4. 实操环节从零写到能转起来4.1 开发环境与项目配置我的环境是 Visual Studio 2022 Windows 11 SDK。创建项目时直接选“桌面应用程序”然后在链接器输入里加上d3d11.lib、dxgi.lib、d3dcompiler.lib。这三个库是缺一不可的d3d11 是核心 APIdxgi 负责交换链和枚举适配器d3dcompiler 用来编译 HLSL 着色器。还有一个容易遇到的问题编译出来的程序在其他电脑上运行提示“找不到 d3dcompiler_47.dll”。这个 DLL 是系统自带的但老版本 Windows 7 可能没有。解决办法有两个一是把d3dcompiler_47.dll复制到 exe 同目录二是用静态链接的方式不推荐体积会变大。我后来选择了在正式发布时做一个小安装包把这些环境依赖一起打包。4.2 用 HLSL 写一个最简着色器DX11 的着色器用 HLSL 编写分顶点着色器和像素着色器两个阶段。最基础的顶点着色器做的事就是“顶点变换 顶点颜色传递”cbuffer VSCBuffer : register(b0) { float4x4 worldViewProj; }; struct VS_IN { float3 position : POSITION; }; struct VS_OUT { float4 position : SV_POSITION; }; VS_OUT VS(VS_IN input) { VS_OUT output; output.position mul(float4(input.position, 1.0f), worldViewProj); return output; }这里有一个核心知识点mul的矩阵乘法顺序。我上面写的是mul(vec, matrix)这是行向量左乘矩阵矩阵按行主序存储。而如果你在 C 里用 DirectXMath默认是列主序存储XMMatrix本质是 4 个行向量。两边很容易搞混。我的经验是在 HLSL 里一律用mul(vector, matrix)这种写法并且 C 端把矩阵转置一次传进去这样逻辑最简单不容易出错。像素着色器更简单就是返回颜色float4 PS(VS_OUT input) : SV_TARGET { return float4(1.0f, 0.5f, 0.0f, 1.0f); }看到SV_POSITION这个语义要特别注意这是 Direct3D 规定的“系统值语义”顶点着色器的输出位置必须用它标记。如果写错了渲染结果会变成乱七八糟的坐标空间。我在第一次接触 HLSL 时就没分清POSITION和SV_POSITION结果画面全是乱的——一个表示顶点的输入位置一个表示变换后的裁剪坐标输出。4.3 绘制一个三角形的完整流程画一个三角形的步骤可以总结成创建顶点缓冲区把三个顶点拷进去。创建顶点着色器和像素着色器编译 HLSL。创建输入布局Input Layout告诉 DX11 顶点缓冲区里每个元素怎么解释。在渲染循环里绑定这些资源调用Draw(3, 0)。调用Present(1, 0)把画面送显。第三步的输入布局是新手最容易忘记的。你创建了顶点缓冲区但如果没告诉管线“这个缓冲里第一个 12 字节是位置”它根本不知道该怎么解析。输入布局的格式要和顶点结构体严格一致D3D11_INPUT_ELEMENT_DESC layout[] { { POSITION, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, };如果位置在结构体里偏移是 12 字节比如在法线后面那你必须把AlignedByteOffset设为 12。这个偏移量和 HLSL 里声明的顺序是两回事——它只取决于你 C 端顶点结构体的内存布局。4.4 加入相机控制和模型矩阵一旦三角形能画出来就可以开始做交互了。我做的第一个交互是“按住鼠标右键旋转视角滚轮缩放”这是最简单的编辑器风格相机。相机类核心就三个方法Update根据输入更新位置和旋转、GetViewMatrix返回视图矩阵、GetProjectionMatrix返回投影矩阵。视图矩阵你可以用XMMatrixLookAtLH生成也可以用四元数自己构造旋转矩阵再加平移。用XMMatrixLookAtLH时要注意参数里 eyePosition 和 focusPosition 不能太近否则矩阵会不稳定。模型矩阵的构造是S * R * T缩放 × 旋转 × 平移。旋转用四元数你可以从欧拉角转换得到也可以用轴角。我强烈建议不要直接存欧拉角在 Transform 里因为万向锁问题真的会让你崩溃。我试验过相机只能上下各转 90 度超过就翻——这问题只有切换到四元数才根治。4.5 断点调试和实时调参图形学开发没有调试工具会非常痛苦。我常用的几个手段第一在顶点着色器和像素着色器里临时输出调试颜色。比如把法线当作颜色输出return float4(normal, 1.0);如果模型显示是五彩斑斓的说明法线数据加载正常如果全是同一种颜色说明法线变换有问题。第二用Graphics DebuggerVS 自带截帧。它能看每一帧的渲染状态、纹理内容、着色器输入输出。遇到“画面黑屏”第一件事不是检查代码逻辑而是截一帧看看蓝色三角形到底在哪个环节被裁剪的。第三在窗口标题栏实时显示 FPS 和三角面数。这个非常简单但在性能调优时很有用。有时候你以为瓶颈在渲染实际上是你每帧重新编译了着色器这种蠢事我确实干过。5. 常见问题与排查技巧实录以下这些问题是我做这个项目时几乎都踩过一遍的真坑整理出来供你对照排查。问题现象可能原因排查建议黑屏但程序没崩溃交换链没有 RenderTargetView或后台缓冲被清成全黑断点检查OMSetRenderTargets是否调用成功三角形看不到顶点变换顺序错了或顶点被裁剪掉先尝试把顶点位置直接设为float4(pos, 1)绕过矩阵看看是否出现物体像“透明”深度缓冲区缺失或深度测试未启用检查是否创建了深度纹理和深度模板视图画面闪烁Z-fighting 或深度精度不够调整近裁剪面增大远近比不能解决要调近裁剪面纹理全黑或全紫没有为纹理生成 SRV或者 UV 坐标没赋值检查纹理加载函数是否返回有效的 ShaderResourceView模型法线方向全反OBJ 是右手系加载到左手系没有做翻转加载时翻转 Z 轴或调整索引绕序运行时崩溃“D3D11 ERROR”常量缓冲区大小不足或格式对齐错误把所有 CB 结构体设置为 16 字节倍数其他电脑跑不起来缺少d3dcompiler_47.dll或 VC 运行库写一个.bat检测系统环境或者把依赖 DLL 放同目录GPU 占用 100% 但 FPS 很低可能每帧上传大块动态数据检查是否有 Map/Unmap 大缓冲区考虑拆分更新还有一个非常隐蔽的问题窗口系统是 Win32 的如果你在WM_SIZE消息里调整交换链大小但忘记同时重建深度缓冲画面会出现“只有部分区域正确”。所以交换链大小改变时必须重建深度纹理而且要在IDXGISwapChain::ResizeBuffers之后做。这个坑我折腾了一天才意识到因为单独跑起来没问题一拖动窗口确定损坏。6. 后续扩展方向与实际使用心得引擎写完之后我觉得最有价值的扩展方向有三个第一是纹理采样和材质系统。有了纹理后场景立刻变得丰富。你可以先用DirectXTex库做万能格式加载这是微软出品的库支持 DDS、PNG、JPG省去自己写解码器的麻烦。注意创建纹理时绑定 SRV 的方式sRGB 格式和非 sRGB 格式的颜色表现差别很大简单说就是画面“发灰”还是“鲜艳”的问题。第二是模型文件格式升级。OBJ 没有动画没有骨骼如果你以后想导入带蒙皮动画的资源至少要换到 FBX 格式。DX11 加载 FBX 可以用官方的 FBX SDK只是 API 比较老、文档少。这一步的复杂度会上升一个数量级所以建议把基础引擎打磨好了再碰。第三是后处理效果。DX11 的后处理流程是场景渲染到离屏纹理再用一张全屏三角形做二次绘制最终把后处理结果输出到后备缓冲。拿到离屏纹理后模糊、Bloom、色彩校正、泛光都可以做。而且这一步能帮你彻底理解“渲染到纹理”这套核心机制对未来做延迟渲染也有帮助。我实际操作中体会到的一点是“简单 3D 引擎”里最不值一提的是画三角形最耗功夫的是所有“边界情况处理”——分辨率变化时的重建、窗口失去焦点时的状态恢复、资源释放时的顺序问题。做引擎不必追求功能多而是要把每一条路径都走通。另外建议你在代码里写一个Reset函数把所有资源和状态恢复到初始化状态这在反复测试时特别省心。最后再分享一个我个人的小习惯刚开始做这个引擎时我坚持每个功能先跑一个最简验证再集成到主程序。画三角形配过一遍、贴图配过一遍、材质配过一遍、后处理配过一遍。这样做虽然麻烦但每次出问题都能立刻缩小到“最近改动的代码里”根本不需要从头排查。你要是准备动手做自己的引擎这个习惯也值得一试。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。