IDRenderer的设计与实现
IDRenderer的设计与实现 IDGameEngine 分两层:引擎本体 ID 负责场景、ECS、渲染管线这些"上层逻辑";而所有真正碰 GPU 的活,都收拢在一个独立的渲染后端库 IDLib/IDRenderer 里。这篇笔记从底往上梳理 IDRenderer:先看它如何封装 GPU 资源,再看它如何暴露渲染命令(RenderCommand),顺便记录一些设计上的取舍。 引擎中的位置 IDRenderer 是一个动态库,夹在引擎本体和 OpenGL 之间: ┌────────────────────────────────────────────┐ │ ID(引擎本体) │ │ Renderer / RenderGraph / 各种 RenderPass │ └──────────────────┬─────────────────────────┘ │ 只 include 公开头文件 ┌──────────────────▼─...
GOC 架构
需要预先学习: 类与对象 多态 模版基础 智能指针与RAII GOC 架构 用继承树表达游戏对象,很快会撞上功能组合的墙——ECS 架构 开头详细描述了这个困境:继承表达 is-a(是一个)关系,而游戏实体需要 has-a(有一个)关系,一辆车有发动机、有轮子,而不是"是一类特殊的可驾驶物"。 对这条原则最直接的落地是 GOC 架构(GameObject-Component,游戏对象-组件):GameObject 是一个轻量的容器,本身只有身份与层级信息;所有功能——变换、网格渲染、刚体、光源、音源——做成一个个 Component,按需挂到 GameObject 上。想要一辆会飞的车?给同一个对象把 MeshRenderer、RigidBody、FlyingControl 都挂上即可,不需要任何新类型。 这套思路是商业引擎的主流选择:Unity 的 GameObject + MonoBehaviour、Unreal 的 Actor + Component、Godot 的 Node,内核都是 GOC。本篇以 ID ...
DrawCall 开销与优化
需要预先学习: RenderPass与RenderGraph 缓冲对象与顶点 DrawCall 是什么 Draw Call(绘制调用)是 CPU 向 GPU 提交的一次绘制命令——在 OpenGL 里就是 glDrawElements、glDrawArrays 这类调用。一次 draw call 绘制一个网格(或一批相同状态的物体),RenderPass与RenderGraph 里一个 Pass 内的每次 draw_batch 提交,对应一次 draw call。 一帧画面通常由成百上千次 draw call 组成:场景里每个物体一次、阴影贴图渲染一次、后处理若干次。现代游戏场景中物体数量动辄上万,draw call 数量是渲染性能的关键指标之一。 一次 draw call 的旅程 调用 glDrawElements 之后发生了什么: CPU 把渲染状态(绑定的 shader、纹理、缓冲、绘制参数)打包进一条命令; 命令写入命令缓冲区(command buffer),GPU 异步读取执行; GPU 按命令切换状态、发起顶点处理,执行绘制。 关键...
ECS 架构
需要预先学习: 类与对象 多态 模版基础 游戏对象的表示:继承的困境 写游戏时第一个遇到的问题往往不是渲染,而是"一个游戏里的东西怎么用代码表示"。最自然的想法来自面向对象:万物皆对象,公共的东西放进基类,特化的东西放进派生类: class GameObject { /* 位置、名字、Update() 虚函数 */ }; class Character : public GameObject { /* 生命值、移动 */ }; class Player : public Character { /* 输入控制 */ }; class Enemy : public Character { /* AI */ }; class Prop : public GameObject { /* 可交互物体 */ }; 游戏小的时候这套结构工作得很好。但游戏的本质是功能的自由组合:策划想要一辆会游泳的飞行汽车、一只会拾取道具的鸟、一个能被玩家附身的...
RenderPass与RenderGraph
需要预先了解: IDRenderer的设计与实现 RenderPass与RenderGraph 一帧画面不是"把物体画上去"一句话能概括的:先从光源视角渲染阴影贴图,再画不透明物体,然后天空盒、透明物体,最后后处理。每一步都是一个RenderPass(渲染工序);这些工序之间有依赖——透明混合必须等不透明物体和天空都画完才能开始。手动维护这个顺序,Pass 一多就容易错。 IDGameEngine 的做法是把这些工序和它们的依赖组织成一张图:RenderGraph(渲染图)。Pass 声明自己读写哪些资源,图负责推导依赖、排出执行顺序、剔除无用的 Pass。这篇笔记梳理这套机制的实现。 RenderPass:渲染工序的抽象 RenderPass 是一个抽象基类,子类只需要关心两件事: class RenderPass { public: RenderPass(const std::string& name = "RenderPass"); // 声明阶段:声明本 Pass 读/写哪些资源(add_pass 时...
光照相关逻辑
需要预先了解: IDRenderer的设计与实现 RenderPass与RenderGraph 光照相关逻辑 这篇笔记梳理 IDGameEngine 里所有和光照有关的逻辑:光源在 CPU 侧的封装与上传、前向与延迟两条渲染路径的组织,以及 Phong / Blinn-Phong / PBR 三种光照模型在 shader 里的实现。光照代码分布在前向的 geometry.fsl 和延迟的 deferred_lighting.fsl 两份 shader 里,两者公式逐段一致——任一路径的画面都可以作为另一条的验收基准。 光源的封装 引擎里一个光源就是一个 Light 结构体: enum class LightType : uint8_t { Directional = 0, // 平行光 Point, // 点光源 Spot, // 聚光灯 }; struct Light : public SerializableBase { Ligh...
阴影相关逻辑
需要预先了解: RenderPass与RenderGraph 光照相关逻辑 阴影相关逻辑 阴影是光照的"这道光被挡住了没"那一半。IDGameEngine 的阴影基于经典 Shadow Map(阴影贴图),在这之上实现了 Hard / PCF / PCSS 三档滤波和最多 4 层的 CSM 级联。这篇笔记按数据流梳理:CPU 侧的封装 → ShadowPass 的渲染流程 → GPU 侧 shader 的采样与滤波。 阴影贴图原理 Shadow Map 是两遍渲染: 深度遍:把相机换成光源,从光源位置看向场景,只把深度写进一张贴图——记录"沿这道光方向,最先碰到的东西有多远"; 着色遍:正常渲染时,把每个像素变换到光源视角,比较它的深度和贴图里的深度。比贴图近说明它就是挡光者(照亮),比贴图远说明它前面有东西(阴影)。 平行光的光源视角用正交投影(平行光没有透视衰减),这是后面 ShadowCamera 的核心工作。 CPU 侧的封装 ShadowConfig:参数配置 inline constexpr uin...
PostProcess相关逻辑
需要预先了解: RenderPass与RenderGraph IDRenderer的设计与实现 PostProcess相关逻辑 光照输出的是线性 HDR(High Dynamic Range,高动态范围)颜色——亮度可以超过 1.0,这是 Bloom 效果的前提。但显示器是 LDR 的 sRGB 设备,直接显示 HDR 数据会整体发灰发白。从"线性 HDR"到"可显示"之间隔着一整条后处理链:Bloom 提取辉光、ToneMapping 压缩动态范围、Gamma 校正转回 sRGB。这篇笔记梳理 IDGameEngine 的 PostProcessPass 如何组织这些效果。 HDR 渲染管线 先把整条链路铺开,后处理处于末端: ForwardPass / LightingPass PostProcessPass 窗口 ┌──────────────┐ 采样 ┌──────────────────┐ blit ┌─────────┐ │ sc...



