花了一个多月写了一个mini版的游戏引擎麻雀虽小 五脏俱全。前言Unity、Unreal 这类商业引擎功能强大但对于想深入理解「游戏引擎到底是怎么跑起来的」开发者来说亲手写一个迷你引擎往往比看十遍文档更有收获。本文记录了我从零实现MyEngine的完整过程——一个基于C17 OpenGL 3.3 GLFW Dear ImGui的 3D 游戏引擎编辑器。它具备多面板专业编辑器 UI层级、视口、属性、控制台、资源3D 场景实时预览Phong 光照 网格地面实体管理与 Transform 编辑播放 / 停止运行模式完整中文界面全文将围绕架构设计、渲染管线、编辑器集成、踩坑记录四个维度展开希望能给正在做引擎或图形学入门的朋友一些参考。一、技术选型为什么这样组合模块选型理由语言C17性能与生态兼顾现代特性足够用窗口 / 输入GLFW 3.4轻量、稳定、OpenGL 上下文管理成熟图形 APIOpenGL 3.3 Core教程多、ImGui 后端完善、学习曲线友好编辑器 UIDear ImGui 1.91游戏行业事实标准Immediate Mode 适合工具链构建CMake FetchContent零手动配依赖克隆仓库即可编译没有引入 GLM、GLAD、Assimp 等库而是自研轻量数学库和 OpenGL 函数加载器——目的是控制依赖数量让项目能在「Visual Studio CMake」环境下一键编译运行。二、整体架构三层分离MyEngine 采用经典的分层设计核心原则是Engine 层不依赖 Editor 层。┌─────────────────────────────────────────┐ │ Editor 编辑器层 │ │ EditorApp / Theme / ImGui 面板 │ ├─────────────────────────────────────────┤ │ Renderer 渲染层 │ │ Renderer / Shader / GLLoader │ ├─────────────────────────────────────────┤ │ Engine 引擎层 │ │ Application / Scene / Entity │ └─────────────────────────────────────────┘ │ │ ▼ ▼ GLFW OpenGL 3.32.1 目录结构src/ ├── main.cpp # 入口 ├── EngineMath.h # Vec3 / Mat4 数学库 ├── Engine/ │ ├── Application.cpp # 主循环、窗口、日志 │ └── Scene.cpp # 场景与实体管理 ├── Renderer/ │ ├── GLLoader.cpp # OpenGL 函数动态加载 │ ├── Shader.cpp # 着色器编译 │ └── Renderer.cpp # FBO 离屏渲染、网格绘制 └── Editor/ ├── EditorApp.cpp # 编辑器 UI 与交互 └── Theme.cpp # ImGui 深色主题2.2 主循环数据流main() └─ Application::init() ├─ glfwCreateWindow() ├─ loadOpenGL() └─ EditorApp::init() └─ Application::run() // 每帧 ├─ glfwPollEvents() ├─ EditorApp::onUpdate(dt) ├─ EditorApp::onRender() │ ├─ Renderer::renderScene() → FBO 离屏绘制 │ └─ ImGui 绘制 UI 视口贴图 └─ glfwSwapBuffers()这种结构的好处是将来把 Editor 去掉Engine Renderer 可以直接嵌入「纯运行时」游戏程序。三、核心实现一FBO 离屏渲染 ImGui 视口商业引擎编辑器Unity、Unreal的 Scene 视口都不是「直接画到屏幕」而是先渲染到纹理再贴到 UI 面板上。MyEngine 也采用了这一标准方案。3.1 创建帧缓冲voidRenderer::resize(intw,inth){glBindFramebuffer(GL_FRAMEBUFFER,m_fbo);// 颜色附件 → 纹理glBindTexture(GL_TEXTURE_2D,m_colorTex);glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,m_colorTex,0);// 深度附件 → RenderbufferglRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,w,h);glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,m_depthRbo);}3.2 在 ImGui 面板中显示 3D 画面voidEditorApp::drawViewport(){ImVec2 sizeImGui::GetContentRegionAvail();m_renderer.resize((int)size.x,(int)size.y);m_renderer.renderScene(m_scene,m_time);m_renderer.endFrame();// 将 FBO 颜色纹理显示在 ImGui 控件中ImGui::Image((ImTextureID)(intptr_t)m_renderer.colorTexture(),size,ImVec2(0,1),ImVec2(1,0));}注意ImVec2(0, 1), ImVec2(1, 0)是为了翻转 Y 轴——OpenGL 纹理原点在左下ImGui 显示原点在左上。3.3 轨道相机视口支持右键拖拽旋转、滚轮缩放实现了一个简单的 Orbit CamerastructOrbitCamera{floatyaw45.0f;// 水平角floatpitch25.0f;// 俯仰角floatdist8.0f;// 距离目标点Vec3 target{0,0.5f,0};};通过Mat4::lookAt(eye, target, up)计算 View 矩阵配合Mat4::perspective(45°, aspect, 0.1, 200)得到 VP 矩阵。四、核心实现二Phong 光照着色器片元着色器实现了简化 Phong 模型并加了 Rim Light 边缘光让 demo 场景更有「引擎感」// 环境光 漫反射 高光 边缘光 vec3 ambient uColor * 0.25; vec3 diffuse max(dot(norm, lightDir), 0.0) * uLightColor; vec3 specular pow(max(dot(norm, halfDir), 0.0), 32.0) * uLightColor * 0.4; float rim 1.0 - max(dot(viewDir, norm), 0.0); vec3 result ambient (diffuse specular) * uColor uColor * rim * rim * 0.15;几何体方面立方体和地面是硬编码顶点球体通过 UV 参数方程动态生成网格网格线则是 XZ 平面上的GL_LINES绘制。五、核心实现三场景与实体系统当前版本采用轻量级 Entity 模型每个实体包含structEntity{uint32_tid;std::string name;Transform transform;// position / rotation / scaleboolactive;MeshType meshType;// Cube / Sphere / Plane / Lightfloatcolor[3];};Scene 负责增删查选Entity*Scene::addEntity(conststd::stringname,MeshType type);voidScene::selectEntity(uint32_tid);Entity*Scene::selected();编辑器侧通过 ImGui 的DragFloat3直接绑定transform字段实现所见即所得的 Transform 编辑。这是后续扩展为 ECSEntity-Component-System的基础。六、核心实现四ImGui 中文界面ImGui 默认字体不含中文。很多初学者会遇到界面中文乱码或方框的问题根因通常是先加载了AddFontDefault()作为默认字体中文字体虽然加载了但没有设为FontDefault。正确做法ImFontConfig fontCfg;fontCfg.FontNo0;// 微软雅黑 TTC 字体集索引staticconstImWchar kUiGlyphRanges[]{0x0020,0x00FF,// 拉丁字符0x4E00,0x9FFF,// CJK 统一汉字0x3000,0x303F,// CJK 标点0,// 结束标记};ImFont*uiFontio.Fonts-AddFontFromFileTTF(C:\\Windows\\Fonts\\msyh.ttc,18.0f,fontCfg,kUiGlyphRanges);io.FontDefaultuiFont;// 关键设为默认字体同时MSVC 编译需加/utf-8选项保证源码中的 UTF-8 字符串正确编译。七、踩坑记录血泪经验7.1Math.h遮蔽系统math.h这是 Windows 上最隐蔽的坑之一。项目最初有一个src/Math.hCMake 将其加入 include 路径后MSVC 编译cmath时会优先找到Math.h导致acosf、sinf等函数全部报错error C2039: acosf: 不是 global namespace 的成员原因Windows 文件系统大小写不敏感#include math.h会匹配到Math.h。解决重命名为EngineMath.h避免与系统头文件冲突。7.2 GLFW 与 OpenGL 头文件冲突必须在使用 GLFW 前定义#defineGLFW_INCLUDE_NONE#includeGLFW/glfw3.h否则 GLFW 会拉入 Windows 旧版GL/gl.h与 C 标准库产生宏冲突。7.3 自研 GLLoader 替代 GLADGLAD 的 CMake 与新版 CMake 4.x 存在兼容问题。最终选择通过glfwGetProcAddress按需加载 OpenGL 3.3 Core 函数ImGui 后端则使用其内置的imgui_impl_opengl3_loader.h两者互不干扰。八、CMake 一键构建依赖管理全部交给 FetchContent开发者无需手动下载 GLFW / ImGuiFetchContent_Declare(glfw GIT_REPOSITORY https://github.com/glfw/glfw.git GIT_TAG 3.4 ) FetchContent_Declare(imgui GIT_REPOSITORY https://github.com/ocornut/imgui.git GIT_TAG v1.91.6 ) FetchContent_MakeAvailable(glfw)编译build.bat # 自动调用 VS2022 CMake run.bat # 启动 MyEngine.exe九、编辑器 UI 设计UI 采用深空暗色主题配色方案用途色值说明背景#12121F深空暗色强调#A855F7紫色按钮 / 选中态高亮#22D3EE青色标题 / 勾选框文字#EBEDF7高对比度正文面板布局参考 Unity 编辑器左侧层级、中间视口、右侧属性、底部控制台与资源底部状态栏显示 FPS 和作者信息。十、运行效果启动后默认场景包含10×10 地面平面紫色 Player Cube选中时自动旋转青色 Sphere相机 / 光源占位对象操作方式操作效果右键拖拽视口旋转相机滚轮缩放视距点击层级对象选中并在属性面板编辑工具栏「播放」球体开始旋转十一、后续扩展方向当前 MyEngine 是「能跑、能看、能编辑」的 MVP 版本。如果要往正式引擎演进建议按以下顺序扩展组件系统ECS— 将 Entity 拆为 Transform / Mesh / Light 等 Component资源管理— AssetManager 加载外部.obj/.png文件场景序列化— JSON 格式保存 / 加载.scene文件脚本运行时— 集成 LuaPlay 模式下执行Update()物理引擎— 接入 Bullet / JoltImGui Docking— 切换 docking 分支支持面板自由拖拽十二、总结MyEngine 虽然功能有限但完整走通了「窗口 → OpenGL 上下文 → 离屏渲染 → ImGui 视口 → 场景编辑」这条引擎编辑器核心链路。几个关键收获FBO 离屏渲染是编辑器视口的标准方案必须掌握分层架构让 Engine 与 Editor 解耦便于后续剥离运行时ImGui 中文字体必须设为FontDefault否则必然乱码Windows 头文件命名是大坑Math.h这种名字绝对不能用CMake FetchContent能极大降低 C 项目依赖管理成本游戏引擎是一个可以持续迭代数年的项目。从一个能转动的立方体开始就已经踏上了正确的路。附录项目信息项目名称MyEngine版本1.0.0作者CaiGuiHou技术栈C17 / OpenGL 3.3 / GLFW / Dear ImGui / CMake环境Windows 10 / Visual Studio 2022 / CMake 3.20如果这篇文章对你有帮助欢迎点赞、收藏、关注有问题欢迎在评论区交流。