Done is better than perfect

0%

UE5启动流程与结构解析

一、引擎启动流程详解

1.1 启动入口与平台特化

在 Windows 平台上,Unreal Engine 5 的执行始于标准 GUI 程序入口 WinMain。该入口函数首先调用平台特化的 LaunchWindowsStartup(位于 LaunchWindows.cpp),随后进入通用的跨平台启动函数 GuardedMain(定义于 Launch.cpp)。其调用链条如下:

1
WinMain → LaunchWindowsStartup → GuardedMainWrapper → GuardedMain → EnginePreInit → FEngineLoop::PreInit → FEngineLoop::PreInitPreStartupScreen

1.2 GuardedMain 函数结构

GuardedMain 函数作为整个引擎生命周期的主控流程,其主要职责包括平台抽象层初始化、引擎子系统注册、核心模块加载、主循环驱动以及资源清理。简化伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GuardedMain(const TCHAR* CmdLine)
{
// 初始化阶段
EnginePreInit();
EngineInit();

// 主执行循环
while (!IsEngineExitRequested())
{
EngineTick();
}

// 引擎析构与清理
EngineExit();
}

1.3 初始化子系统分层解析

  • 命令行与环境配置:通过 FCommandLine 解析参数,初始化全局环境变量如 GIsEditorGIsClient
  • 平台与IO子系统:包括 FPlatformFileManager 构建虚拟文件系统层,配置加载器(GConfig)以及多日志通道日志器(GLog)。
  • 模块加载与引擎实例化FEngineLoop::PreInit 会加载核心运行时模块(如 Core、CoreUObject、Engine),随后 FEngineLoop::Init 生成 UEngineUEditorEngine 实例。
  • 世界初始化:通过 UEngine::Start() 加载默认世界并生成 UWorld 实例,注册 GameModePlayerController 等运行时关键对象。

1.4 模式差异化执行路径

  • 编辑器模式:实例化 UEditorEngine,加载 Slate 编辑器模块和各种工具集。
  • 独立运行模式:使用 UGameEngine,仅保留运行时必要模块,避免加载额外编辑器负担。

1.5 原生窗口创建与生命周期管理

Unreal Engine 在 Windows 平台下使用 Win32 原生窗口进行渲染上下文承载和消息收集,其生命周期管理如下:

  • 创建时机:在 FWindowsPlatformApplicationMisc::InitializeWindow()(位于 WindowsApplication.cpp)被调用时创建原生窗口。该函数在 FEngineLoop::Init() 完成渲染器初始化(RHI)后执行。具体过程包括:

    1. 准备窗口类(WNDCLASSEX)并注册到系统。
    2. 调用 CreateWindowExCreateWindowW 创建窗口句柄(HWND)。
  • 更新机制:主循环内调用 FWindowsApplication::PumpMessages() 轮询 Win32 消息队列,通过 PeekMessage/GetMessage 接收消息;再在 TranslateMessageDispatchMessage 后,Win32 调用窗口过程 WndProc,进一步路由到 FWindowsApplication::ProcessMessage()

  • 交换链与渲染上下文:与原生窗口关联的 RHI 交换链(DX11/DX12/Vulkan SwapChain)在窗口创建后初始化,并在每帧通过 SwapBuffersPresent 交换前后缓冲区。

  • 销毁时机:当引擎接收到退出请求后,FWindowsApplication::DestroyWindows() 会依次遍历所有 HWND,调用 DestroyWindow 释放窗口资源;随后在 GuardedMain 的退出阶段调用 UnregisterClass 注销窗口类。

1.6 原生窗口与渲染管线关联

为了将 Win32 窗口与 GPU 渲染流程绑定,Unreal Engine 在 RHI 初始化阶段执行以下关键步骤:

  1. 创建渲染设备与命令队列:在 PlatformCreateDynamicRHI()(如 D3D12RHI.cpp 中)调用 D3D12CreateDevice 创建 ID3D12Device,并初始化图形和显示命令队列(ID3D12CommandQueue)。
  2. 窗口与交换链关联:随后调用 IDXGIFactory::CreateSwapChainForHwnd(封装在 D3D12RHI::CreateSwapChain())并传入之前创建的 HWND 与命令队列,生成 IDXGISwapChain3 对象。该交换链会管理前后缓冲区并负责与窗口表面同步。
  3. 渲染目标视图(RTV)绑定:在交换链创建后,RHI 会为每个缓冲区调用 CreateRenderTargetView(RTV),并在每帧 RHIPresent 时使用 OMSetRenderTargets 将 RTV 绑定到管线输出合并阶段。
  4. 帧呈现:每次 RHIPresent 调用时,通过 IDXGISwapChain3::Present 将渲染完成的后缓冲区呈现到原生窗口。

这样,游戏逻辑和 Slate 渲染均可通过 RHI 层透明地提交命令到与 HWND 绑定的交换链,实现最终画面输出。

二、模块化架构与加载机制

2.1 模块生命周期管理器 FModuleManager

FModuleManager 为模块化架构的核心调度器,负责模块的动态解析、生命周期控制与实例缓存。其提供以下能力:

  • 加载:LoadModule(), LoadModuleWithFailureReason()
  • 卸载:UnloadModule()
  • 访问:GetModule(), IsModuleLoaded()

模块以 IMPLEMENT_MODULE 宏形式注册,启动时触发 StartupModule(),终止时调用 ShutdownModule()

2.2 模块元描述与阶段控制

模块的描述由 FModuleDescriptor 承载,定义于 .uproject.uplugin 配置文件中,其关键字段如下:

  • Name:模块唯一标识
  • Type:模块分类(Runtime、Editor、Developer 等)
  • LoadingPhase:加载时机(PreDefault、Default、PostEngineInit 等)

引擎启动阶段通过 LoadModulesForPhase() 自动分阶段解析和装配模块。

2.3 静态与动态模块机制对比

  • 静态模块:构建时链接进可执行文件,通过 StaticallyLinkedModuleInitializers 注册初始化函数。
  • 动态模块(DLL):运行时使用 FPlatformProcess::GetDllHandle() 加载,调用 GetDllExport() 提取 InitializeModule() 等符号以完成动态注册。

模块卸载遵循逆序清理原则,保障依赖顺序的一致性与资源完整回收。

2.4 模块加载与调用流程

在引擎启动阶段,以及运行时需要动态引入或卸载功能时,Unreal Engine 依赖 FModuleManager 结合项目和插件描述完成模块的发现、加载、初始化及调用。

2.4.1 模块发现与注册

  • 启动时扫描:引擎启动时,FProjectManager 和 FPluginManager 分别读取 .uproject.uplugin 中的 FModuleDescriptor 列表,并将所有声明的模块按 LoadingPhase 分组。
  • 静态注册:对于单片(Monolithic)构建模式,所有模块在编译时通过 IMPLEMENT_MODULE 宏将初始化委托注册到 StaticallyLinkedModuleInitializers 映射中;插件和项目模块也以同样方式嵌入可执行文件或主 DLL。

2.4.2 动态加载流程

  1. 调用 LoadModulesForPhase(Phase) 时,FModuleManager 枚举本阶段所有 FModuleDescriptor。

  2. 对于每个模块名,FModuleManager::LoadModuleWithFailureReason:

    • 若为静态模块,直接从 StaticallyLinkedModuleInitializers 调用委托,返回 IModuleInterface 实例。
    • 若为动态模块,使用 FPlatformProcess::GetDllHandle 在预定义路径(Engine、Project、Plugin 二进制目录)加载对应 DLL;再通过 GetDllExport 查找符号 "InitializeModule",执行返回的新模块实例。
  3. 将生成的 IModuleInterface 指针保存在 ModuleNameToInfo 映射,调用 StartupModule() 完成模块自身初始化逻辑。

  4. 广播 ModulesChangedEvent 通知其他子系统,如 UObject 加载器,注册由模块提供的类或服务。

2.4.3 引擎对模块的调用

  • 接口查询:运行时代码可通过 FModuleManager::Get().GetModuleChecked(ModuleName) 获得已加载模块的接口引用。
  • 服务注入:模块通常在 StartupModule 中向全局子系统注册服务(如渲染模块注册渲染工厂,网络模块注册网络驱动),引擎通过静态或虚函数调用这些接口完成对应功能。
  • 生命周期管理:当模块完成其职责或需要热重载时,调用 UnloadModule(ModuleName) 会按逆序调用 ShutdownModule(),并释放 DLL 句柄。

2.4.4 热重载支持

  • 编辑器模式和某些运行时插件支持 Hot Reload:在源码或插件代码修改后,调用 LiveCoding 或 RecompileInEditor 可以卸载旧模块并重命名加载新 DLL,FModuleManager 确保在重载前调用所有模块的 ShutdownModule,再重新执行加载和 StartupModule 过程。

三、引擎主循环机制剖析

主循环逻辑位于 FEngineLoop::Tick(),该函数被 GuardedMain 持续调用,驱动引擎完成一帧游戏更新。其核心逻辑结构如下:

1
2
3
4
5
6
7
8
while (!IsEngineExitRequested())
{
// 处理窗口消息
FPlatformApplicationMisc::PumpMessages(true);

// 主游戏引擎tick (world, game objects, etc.)
GEngine->Tick(FApp::GetDeltaTime(), bIdleMode);
}

3.1 各子阶段功能拆解

  • 世界 Tick:依序更新每个 UWorld 实例,包括其内部所有 AActor 和组件。
  • 子系统更新:物理仿真、动画控制器、AI 系统、音频引擎、GC 系统等依照特定顺序被调度。

在编辑器模式下,还需驱动多视口、多编辑器对象 Tick,进一步增加主循环复杂度。

四、核心运行时对象结构

4.1 UWorld:多子系统集成容器

UWorld 是游戏世界的运行时表示,其核心职责包含:

  • 管理 ULevel 和动态加载的 Streaming Levels
  • 管理所有 AActor 实例的生命周期与调度
  • 提供时间流控制(World Time、DeltaTime、Pause 等)
  • 持有所有关键子系统的引用,如 UPhysicsSceneUNavigationSystemV1UAIController
  • 关联 AGameModeBaseAPlayerControllerAGameState 等游戏规则对象

其结构设计支持多个世界并行存在。

4.2 AActor:游戏对象原语单元

AActor 是所有可交互、可放置、可网络同步实体的基类。其设计支持以下关键能力:

  • 空间坐标系定义(Transform)
  • Tick 生命周期方法:BeginPlay()Tick()EndPlay()
  • 支持组件组合系统,通过 UActorComponent 实现功能模块化
  • 支持网络属性同步与远程函数调用(RPC)机制
  • 支持蓝图与 C++ 混合开发与扩展

常见子类如 APawnACharacterAStaticMeshActor 等均继承自 AActor

4.3 组件化与解耦设计

  • UActorComponent 是功能原子单元,可被多个 Actor 复用
  • SceneComponent 派生类(如 Mesh、Camera、Light)具备空间信息
  • 支持运行时动态添加、编辑器中组合,可提高模块内聚性与解耦能力

组件体系提升了可扩展性、降低逻辑冗余并促进代码复用,是 UE 面向数据驱动设计的重要体现。

五、外部输入处理机制(Windows 平台)

5.1 平台消息获取与分发

在 Windows 平台上,引擎通过 Win32 API 接口获取原生消息队列事件(如 WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, WM_INPUT 等)。FWindowsApplication::PumpMessages() 会在主循环中调用 TranslateMessageDispatchMessage,并在 WndProc 中将消息转发给 FWindowsApplication::ProcessMessage()

1
2
3
4
5
6
MSG Msg;
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}

5.2 消息处理与转换

ProcessMessage(HWND Window, uint32 Message, WPARAM wParam, LPARAM lParam) 会根据消息类型调用对应的处理函数,例如:

  • 鼠标事件:ProcessMouseButtonDown/Up, ProcessMouseMove, ProcessMouseWheel
  • 键盘事件:ProcessKeyDown/Up, ProcessKeyChar
  • 原始输入(Raw Input):WM_INPUT 对触摸、手柄等应用多平台统一处理。

每个处理函数会构建对应的 Slate 事件对象(FPointerEvent, FKeyEvent)并调用 MessageHandler->OnMouseButtonDown()OnKeyDown() 等接口。

5.3 Slate 层事件分发

FSlateApplication 作为统一 UI 框架入口,通过 FSlateApplication::ProcessDeferredEvents() 聚合并投递从平台层上报的事件。事件处理流:

  1. 平台层产生的原始事件通过 FGenericApplicationMessageHandler 回调注册到 Slate。
  2. FSlateApplication::PumpMessages() 中调用 ProcessMessageQueue(),将消息排入 Slate 内部队列。
  3. FSlateApplication::Tick() 阶段,遍历事件队列,调用 RoutePointerEventRouteKeyEvent,将事件分发给焦点窗口和对应 Widget。

5.4 引擎输入子系统

Slate 处理完 UI 输入后,会根据配置将输入路由至引擎输入子系统(UPlayerInput),生成 FInputKey, FInputAxisFInputTouch 数据。主要流程:

  1. FSlateApplication 调用 FInputProcessorSlate::ProcessKeyDownEvent 或对应方法,将键盘/鼠标事件转为 UPlayerInput 的调用。
  2. UPlayerInput 根据项目 DefaultInput.ini 中的映射,将物理键或控制器按钮映射为游戏内抽象的输入动作(动作)与轴(Axis)。
  3. APlayerController::InputKeyInputAxis 接收这些事件,进一步调用绑定到 Actor 的 UInputComponent 中的委托。

5.5 控制器与触摸支持

  • 控制器:Windows 使用 XInput(WindowsApplication.cpp 中的 FWindowsControllerInterface)轮询手柄状态,产生 FControllerState 并转为 FInputKey
  • 触摸屏:触摸事件通过 Win32 的触摸输入 API(WM_TOUCH),并在 ProcessMessage 中解析为 FPointerEvent,最终传递给 Slate。

5.6 整体事件流示意

1
2
3
4
5
6
7
8
9
10
11
12
13
Win32 Message Queue
↓ (TranslateMessage/DispatchMessage)
FWindowsApplication::ProcessMessage
↓ (构建 FPointerEvent/FKeyEvent)
FGenericApplicationMessageHandler → FSlateApplication
↓ (Slate 内部队列)
FSlateApplication::Tick()
↓ (RoutePointerEvent/RouteKeyEvent)
焦点 Widget + Active Window
↓ (UI 处理完成)
FInputProcessorSlate → UPlayerInput
↓ (映射 动作/Axis)
APlayerController → UInputComponent → Actor

六、主要UE5模块概览

在整体架构中,UE5 由若干基础模块和功能模块组成,每个模块在引擎启动和运行时通过 FModuleManager 驱动加载,并在引擎生命周期内被相应子系统调用。以下为关键模块及其设计结构、核心职责和驱动方式:

6.1 核心基础模块

  • Core:提供跨平台底层功能,包括内存管理、字符串与容器模板、文件系统接口。以静态方式链接,最先被加载,由 FEngineLoop::PreInit 驱动。
  • CoreUObject:UObject 系统与反射框架实现,管理对象生命周期、序列化与垃圾回收。通过静态注册委托加载,StartupModule 中初始化反射元数据。

6.2 引擎功能模块

  • Engine:GameFramework 核心,管理世界(UWorld)、GameMode、Actor 生命周期和场景更新。依赖 CoreUObject,在 PreDefault 阶段加载,StartupModule 注册世界管理器。
  • RenderCore:封装渲染流水线基础接口,如命令缓冲、资源管理。作为渲染子系统前置模块,在 Default 阶段加载,渲染线程启动时被绑定。
  • RHI:渲染硬件抽象层,提供对 DirectX、Vulkan、Metal 等后端的统一接口。动态模块,根据平台在 Default 阶段载入,渲染初始化流程中调用 CreateRHI

6.3 UI 与输入模块

  • SlateCore:Defines 基本 UI 树结构、事件处理和布局算法。作为静态模块在 PreDefault 阶段加载,由 FSlateApplication 实例驱动。
  • Slate:UI 渲染与绘制实现,依赖 SlateCore 和 RHI,在 PostEngineInit 阶段加载,StartupModule 中注册渲染器。
  • UMG (UMGEditor):基于 Slate 的可视化 UI 编辑与运行时模块,Editor 版在 Editor 阶段加载,Runtime 版在 Default 阶段加载,由 WidgetReflector 驱动。
  • InputCore:定义键位与轴映射基础数据结构。静态加载,UPlayerInput 在世界 Tick 前自动初始化并回调映射。

6.4 网络与游戏系统模块

  • Networking:核心网络协议与封包实现(Sockets、LowLevelNet)。动态模块,Default 阶段加载,在 NetDriver 初始化时被调用。
  • OnlineSubsystem:平台在线服务接口(如 Steam、Epic Online Services)。插件形态,运行时根据配置载入,对应子系统在 WorldInit 时注册服务。
  • GameplayAbility:提供技能(Ability)与效果(Effect)系统框架。模块在 PostDefault 阶段加载,StartupModule 中注册 UAbilitySystemComponent 工厂,GameMode 或 Actor 在构造时创建组件实例。

6.5 工具与扩展模块

  • Editor:编辑器核心功能集合,在 Editor 阶段加载,为编辑器注入菜单、工具窗口与自定义命令。
  • BlueprintGraph:蓝图可视化脚本支持,Editor 模式下加载,由蓝图编译器和可视化编辑器驱动。
  • LiveCoding:支持热重载的模块,运行时监听文件变化,在插件重载流程中被 FModuleManager 调用 ShutdownModule 和 StartupModule。

七、UE5 多线程架构

UE5 在运行时启动时,会创建多个专职线程来处理不同的子系统,以保证性能与资源利用。以下是主要线程的创建、更新与销毁位置及职责说明:

7.1 主要线程列表

  • 游戏主线程 (Game Thread)
  • 渲染线程 (Render Thread)
  • RHI 线程 (RHI Thread)
  • 任务图线程 (Task Graph Threads)
  • 异步加载线程 (Async Loading Thread)
  • 额外子系统线程(物理、音频等)

7.2 游戏主线程

  • 创建时机:在 GuardedMain 内启动后,即进入 FEngineLoop::Init 后恢复到主线程上下文。
  • 更新:每帧由 FEngineLoop::Tick() 驱动,处理游戏逻辑、UWorld Tick、Actor Tick 等。
  • 销毁:当 IsEngineExitRequested() 为真退出主循环后,主线程在 GuardedMain 中执行 EngineExit 清理并终止进程。

7.3 渲染线程

  • 创建时机:在 RHI 初始化阶段(如 D3D12DynamicRHI::Init())调用 FRHICommandContext::InitializeResources() 时通过 FRunnableThread::Create 启动。
  • 更新:在每帧渲染提交阶段,由 FRenderCommandFenceFRHICommandList 在渲染线程上下文中提交绘制命令。
  • 销毁:在 D3DRHI::Shutdown() 或通用 RHIExit() 中调用 FRunnableThread::Kill 并释放线程对象。

7.4 RHI 线程

  • 创建时机:与渲染线程类似,部分平台(如 Vulkan)在 CreateRHI 后为异步命令提交启动独立 RHI 线程。
  • 更新:负责管理底层驱动命令队列、Fence 同步与交换链 Present 调用。
  • 销毁:在 RHI 退出流程中依次停止并销毁。

7.5 任务图线程

  • 创建时机:在 FTaskGraphInterface::Startup() 中,通过 FTaskGraphInterface::Get().Startup() 启动一组后台线程。
  • 更新:按需执行 FGraphEvent 调度的任务节点,如资源加载、AI 逻辑、物理仿真子任务。
  • 销毁:在引擎退出阶段 FTaskGraphInterface::Shutdown() 中回收所有任务线程。

7.6 异步加载线程

  • 创建时机:在引擎初始化阶段 FAsyncLoadingThread::Init() 中,通过 FRunnableThread::Create 启动用于包/资源加载。
  • 更新:不断读取 FAsyncLoadingThread 的请求队列,在后台加载资产并在完成时通知主线程。
  • 销毁:在 FAsyncLoadingThread::Shutdown() 中停止线程并清理队列。

7.7 其他子系统线程

  • 物理线程:如 FPhysScene::InitPhysScene() 可创建用于并行物理仿真的线程。
  • 音频线程:在 FAudioDevice::Init() 中创建,用于音频混合与解码。

八、反射系统

Unreal Engine 的反射系统(Reflection System,又称 Property System)是 C++ 语言在运行时缺乏本地支持的情况下,为实现运行时类型查询、序列化、垃圾回收、网络复制及蓝图交互而设计的通用机制 。该系统由 Unreal Header Tool(UHT)在编译时生成元数据,并在运行时通过静态注册与 FArchive 等组件提供完整的元信息访问。

8. 1 核心元类:UObject 与 UClass

  • UObject 是所有受反射支持对象的基类,定义于Engine/Source/Runtime/CoreUObject/Public/UObject/Object.h。它承载了 GetClass()、Serialize()、垃圾回收标记等核心方法。
  • UClass 是 UObject 类的元类,描述一个具体 UObject 派生类型的属性、函数列表和构造器。每个反射类在运行时都对应一个唯一的 UClass 实例,并保存于全局类注册表中。

8. 2 注解与 UHT 生成

  • UCLASS():标记一个类使其参与反射,生成对应的 UClass 元数据。
  • USTRUCT():标记一个 struct 参与反射,生成 UStruct 元数据。
  • UPROPERTY(...):标记成员变量参与属性反射,生成 FProperty 元数据。
  • UFUNCTION(...):标记成员函数参与方法反射,生成 UFunction 元数据。

所有宏定义与 UHT 针对这些标记生成的头文件,位于 .generated.h,并在编译时被包含于源文件末尾

8. 3 反射元数据对象

  • UStruct / UClass:分别保存结构体和类的字段(PropertyLink 链表)、父类指针、元数据(MetaData)等。
  • FProperty:所有属性的基类,子类如 FIntProperty、FStructProperty、FArrayProperty 等实现具体序列化与访问接口,定义于 Property.cpp。
  • UFunction:保存函数签名、参数列表与可调用指针,用于在蓝图或网络复制时动态调用。
  • UEnum:保存枚举类型信息,实现编辑器下枚举面板数据填充。

8. 4 反射注册机制

  1. 编译时生成

UHT 根据注解宏解析 C++ 源码,生成 .generated.h,其中包含:

1
2
3
static void StaticRegisterNativesUMyClass();
UClass* Z_Construct_UClass_UMyClass();
template<> MYMODULE_API UClass* StaticClass<UMyClass>();

这些函数最终注册到 Z_CompiledInDeferFile 数组中,延迟于运行时统一调用。

  1. 静态初始化

在模块加载(IMPLEMENT_MODULE)时,FModuleManager 调用 StartupModule,触发 Z_CompiledInDeferFile 中的 FRegisterCompiledInInfo,自动注册所有 UClass、UStruct、UEnum 等到全局注册表。

  1. 运行时访问
  • UMyClass::StaticClass() 返回对应的 UClass*,可用于动态创建实例或做类型判断。
  • MyObject->GetClass() 返回实例的 UClass*,支持 IsA()、Cast<>() 等运行时安全转换。

8.5 运行时类型信息

  • StaticClass / GetClass
1
2
UClass* AMyActorClass = AMyActorClass::StaticClass();
UClass* RuntimeClass = MyActorInstance->GetClass();

前者通过模板实现,后者从 UObject 基类获取实例类型指针,二者均依赖全局注册表。

  • 类型查询与转换

UObject::IsA(UClass*) 和 Cast(Object) 在底层调用 GetClass()->IsChildOf(DesiredClass),实现安全的继承链检查与指针转换

  • 动态创建对象

UE5中动态创建对象主要分为两类场景:

  1. 创建普通UObject派生类(非Actor对象)

使用 NewObject() 模板函数,直接通过UClass信息创建对象。 代码示例:

1
2
3
// 假设存在一个反射类 UMyObject : public UObject
UClass* MyClass = UMyObject::StaticClass(); // 获取UClass
UMyObject* MyObj = NewObject<UMyObject>(GetTransientPackage(), MyClass);

  1. 创建Actor派生类(需存在于游戏场景中)

Actor必须通过UWorld::SpawnActor()方法生成,且需要指定位置和旋转信息。 代码示例:

1
2
3
4
5
// 假设存在一个反射类 AMyActor : public AActor
UClass* MyActorClass = AMyActor::StaticClass(); // 获取UClass
FVector SpawnLocation = FVector(100.0f, 100.0f, 100.0f);
FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
AMyActor* MyActor = GetWorld()->SpawnActor<AMyActor>(MyActorClass, SpawnLocation, SpawnRotation);
- 关键点: - 必须通过UWorld上下文调用(通常在Actor或Component中使用GetWorld())。 - Actor会自动注册到游戏场景中,并受引擎生命周期管理。

  • 动态获取UClass的三种方法

若需通过字符串类名动态获取UClass,需结合反射系统:

  1. 使用 FindClass() 函数
1
2
FString ClassName = TEXT("MyProject.MyObject");
UClass* TargetClass = FindObject<UClass>(ANY_PACKAGE, *ClassName);
  • 限制:类必须已在内存中加载(如被蓝图引用或代码显式加载)
  1. 使用 FSoftClassPath(推荐)
1
2
FSoftClassPath ClassPath(TEXT("/Game/Blueprints/MyActor.MyActor_C")); // 蓝图类路径
UClass* TargetClass = ClassPath.TryLoadClass<UObject>();
  • 优势:支持异步加载和热重载,适用于蓝图类。
  • 路径格式:/Game/Path/To/Asset.AssetName_C(蓝图类需加_C后缀)。
  1. 通过静态类名直接获取
1
UClass* TargetClass = LoadClass<UObject>(nullptr, TEXT("/Script/MyProject.MyObject"));
  • 适用场景:已知类的完整名称(C++原生类的路径格式为/Script/ProjectName.ClassName)。

九、串行化系统

9.1 什么是串行化?

串行化(Serialization)是将对象数据转换为字节流(用于存储或传输)的过程,反串行化(Deserialization)是将字节流还原为对象的过程。在 UE5 中,串行化是构建以下系统的基石:

  • 关卡和资源的加载与保存(.uasset、.umap)
  • 对象的网络复制(Replication)
  • 蓝图与编辑器属性持久化
  • SaveGame 系统
  • GC 跟踪对象引用

9.2 核心组件概览

模块 作用 核心类/结构
Archive 系统 底层读写抽象 FArchive(抽象基类)
Property System 属性元数据反射与逐成员序列化 FProperty 及其子类
Linker 系统 资源级别的加载/保存管理器 FLinkerLoad, FLinkerSave
Package 系统 uasset/umap 资源的封装与版本控制 UPackage, FPackageFileSummary
Object Serializer 对象级别的序列化逻辑 UObject::Serialize()、FStructuredArchive

9.3 底层核心类详解

  1. FArchive - 串行化的抽象基类 定义于:Runtime/Core/Public/Serialization/Archive.h
1
2
3
4
5
6
7
8
9
class CORE_API FArchive
{
public:
virtual FArchive& operator<<(class UObject*& Value);
virtual FArchive& operator<<(class FName& Value);
virtual FArchive& operator<<(int32& Value);
virtual FArchive& operator<<(FString& Value);
...
};

特点: - 所有读写行为都通过重载 << 操作符完成 - 可被继承形成不同上下文的读写器,如: - FMemoryReader / FMemoryWriter:对内存块操作 - FArchiveFileReader / FArchiveFileWriter:对文件操作 - FStructuredArchive:支持结构化序列化(分组、字段名等)

  1. FProperty - 元属性序列化

每个 UCLASS / USTRUCT 中声明了 UPROPERTY 的变量,会对应一个 FProperty 对象,自动遍历并串行化。

核心接口:

1
virtual void SerializeItem(FArchive& Ar, void* Value, void const* Defaults) const;

派生类示例:

  • FIntProperty:int 类型字段
  • FStructProperty:嵌套结构体字段
  • FArrayProperty:TArray 类型字段
1
2
3
FProperty* Property = ...;
void* DataPtr = ...;
Property->SerializeItem(Ar, DataPtr);
  1. UObject::Serialize() - 对象级别的自定义序列化

定义于 Object.cpp:

1
2
3
4
5
virtual void UObject::Serialize(FArchive& Ar)
{
// 引擎默认会遍历属性链表并调用 SerializeItem
Super::Serialize(Ar);
}
每个 UCLASS 都可以覆写此函数,实现自定义的写入逻辑(但仍应调用 Super::Serialize 保证基础属性被序列化)。

  1. FLinkerLoad / FLinkerSave - 资源串行化入口

FLinkerLoad:用于从 .uasset/.umap 文件中加载对象 FLinkerSave:用于保存对象到磁盘文件

关键函数:

1
2
void FLinkerLoad::SerializeExport(UObject* Object);
void FLinkerSave::SavePackage();
这些类通常与 UPackage 关联,管理该包中所有对象的加载、名字查找、资源依赖。

9.4 结构化序列化格式:FStructuredArchive

UE5 引入了新的结构化序列化 API,替代传统的线性 FArchive,提供更强的稳定性与版本支持。

核心结构:

1
2
3
FStructuredArchive Archive(UnderlyingArchive);
FStructuredArchive::FSlot Slot = Archive.Open();
Slot.EnterRecord()->EnterField(TEXT("Health")) << MyHealth;

优势:

  • 支持字段名
  • 支持嵌套记录
  • 更好的人类可读性(对 JSON/YAML 友好)
  • 支持版本差异(Field Skipping)

9.5 序列化文件格式:.uasset 与 FPackageFileSummary

所有资源(蓝图、纹理、关卡等)最终都被存储为 .uasset 或 .umap 文件。其头部格式由以下结构描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct FPackageFileSummary
{
int32 Tag;
int32 LegacyFileVersion;
FGuid CustomVersionContainer;
int32 NameCount;
FPackageIndex ExportCount;
...
};

这些字段由 FLinkerLoad 加载,决定后续如何解析对象及其依赖。

定义位置:
Runtime/CoreUObject/Public/UObject/PackageFileSummary.h

9.6 实际序列化流程(加载流程图)

1
2
3
4
5
6
LoadPackage()
└──> Create FLinkerLoad
└──> Read FPackageFileSummary
└──> Load Name Map, Export Map, Import Map
└──> Call UObject::Serialize for each export
└──> FProperty::SerializeItem() 逐字段读取

9.7 SaveGame 系统

SaveGame 系统 是一套用于将游戏中的状态(如玩家属性、关卡信息、物品等)序列化为磁盘文件,并在需要时恢复(反序列化)这些状态的机制。它提供了一个 高层封装的方式来保存和加载游戏数据,通常用于存档、存盘、断点续玩等场景

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
USTRUCT(BlueprintType)
struct FPlayerSaveData
{
GENERATED_BODY()

UPROPERTY()
int32 Level;

UPROPERTY()
float Health;

UPROPERTY()
FString PlayerName;
};
这段结构体在 SaveGame 中保存时,会通过 FStructProperty 自动进行字段遍历与序列化。

9.8 版本控制:FCustomVersion

UE 支持多版本资源兼容,通过 FArchive::CustomVer() 查询:

1
2
3
4
5
int32 Version = Ar.CustomVer(FMyPluginVersion::GUID);
if (Version < SOME_VERSION)
{
// 使用旧的反序列化方式
}

9.10 字节序

  1. 什么是字节序(Endian)?
  • Little Endian(小端):低位字节在前(低地址)
  • Big Endian(大端):高位字节在前(低地址)

UE 的主机平台(如 Windows 和 Linux)一般使用 小端 存储,因此 .uasset 文件默认也使用小端格式

  1. 核心处理类:FArchive

UE 中字节序的读写是通过 FArchive 抽象类处理的。派生类中会根据平台和目标字节序做转换。

关键成员变量:

1
2
bool FArchive::ForceByteSwapping;
bool FArchive::IsPersistent; // 读写的是磁盘文件

核心逻辑:

每个派生类在读写整数等多字节数据时,都会调用如下代码来决定是否做字节翻转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void FArchive::ByteSwap(void* V, int32 Length)
{
uint8* Ptr = (uint8*)V;
int32 Top = Length - 1;
int32 Bottom = 0;
while (Bottom < Top)
{
Swap(Ptr[Top--], Ptr[Bottom++]);
}
}

FArchive& FArchive::SerializeByteOrderSwapped(void* V, int32 Length)
{
if (IsLoading())
{
Serialize(V, Length); // Read.
ByteSwap(V, Length); // Swap.
}
else // Writing
{
ByteSwap(V, Length); // Swap V.
Serialize(V, Length); // Write V.
ByteSwap(V, Length); // Swap V back to its original byte order to prevent caller from observing V swapped.
}

return *this;
}
  1. 保存时的字节序

默认行为:

  • UE 保存 .uasset 文件时通常 不做字节翻转(即保存为主机平台字节序,小端)。
  • 例如 Windows 保存时使用小端格式,且不会设置 ForceByteSwapping 为 true。

可选配置:

UE 支持强制以大端格式保存资源(用于跨平台),但一般只在构建某些平台的资源包时启用,比如:

1
2
FArchive& Ar;
Ar.SetByteSwapping(true); // 强制切换字节序
  1. 加载时的字节序检测

文件头中的魔数(Magic Number) UE 使用 FPackageFileSummary::Tag 字段中的魔数来判断文件是否需要字节翻转。

1
2
#define PACKAGE_FILE_TAG			0x9E2A83C1
#define PACKAGE_FILE_TAG_SWAPPED 0xC1832A9E

当加载 .uasset 时,UE 首先读入前 4 字节作为 Tag,然后判断是否为正常魔数或反转魔数, 这个魔数存在于文件最前面,即 FPackageFileSummary 的开头部分。

十、总结

通过以上模块划分与驱动说明,可以看到 UE5 通过 FModuleManager 实现高度模块化架构,各模块在不同加载阶段注册初始化,并在引擎生命周期中由相应子系统调用,保证了功能隔离与灵活扩展。 通过上述分析,我们梳理了 UE5 在 Windows 平台下从操作系统原生消息到游戏逻辑回调的完整输入处理管道。

什么是国债

国债(Government Bonds)是指国家发行的债券,由政府作为借款人向社会筹集资金,并承诺在未来某个时间偿还本金并支付利息。由于国债由国家信用担保,因此一般被认为是最安全的债券。

国债的基本特点

  1. 发行主体:政府
  • 由财政部或中央银行代表政府发行。
  • 在中国,国债由财政部发行,在银行间市场和交易所市场流通。
  1. 风险极低,接近“无风险”
  • 由于政府有税收和货币政策支持,违约风险极低。
  • 通常被视为无风险利率(Risk-Free Rate)的基准。
  1. 收益相对较低
  • 安全性高,收益率低于企业债、高收益债等。
  • 但对于长期稳健投资者来说,国债是重要资产。
  1. 用途:政府融资、调控市场
  • 主要用于填补财政赤字、基础设施建设、调节市场流动性。
    阅读全文 »

设计需求

  1. 解决大量(1000个)HUD创建,更新的性能问题

    • 创建1000个的开销控制在3ms
    • 更新1000个的开销控制在2ms
  2. 有多种HUD的排序策略

    • 通过x,y,z排序
    • 通过指定顺序
    • 通过函数排序
  3. 支持动静分离

  4. 支持三种控件,Image, Text和Slider

  5. 需要支持动态图集(可选)

  6. 文字支持描边和投影(可选)

  7. 支持布局器(可选)

阅读全文 »

本文将通《世界金融史》了解金融的发展,金融是如何驱动世界运转,以及相关的理财产品。

世界金融简史

世界金融的发展经历了多个重要阶段,每个阶段都标志着金融体系的重大变革和进步:

  1. 古代金融的萌芽(公元前3000年 - 公元5世纪)
  • 标志:货币的出现和早期借贷行为。
  • 发展:最早的金融活动可以追溯到古代美索不达米亚和埃及,当时人们使用谷物、牲畜等作为交换媒介。随着金属货币的出现(如中国的铜钱、希腊的银币),金融活动逐渐规范化。古代罗马和希腊还出现了早期的银行和借贷行为。
  1. 中世纪金融的兴起(5世纪 - 15世纪)
  • 标志:汇票和早期银行的诞生。
  • 发展:中世纪欧洲的贸易繁荣催生了金融工具的创新,如汇票(Bill of Exchange)的出现,解决了长途贸易中的支付问题。意大利的佛罗伦萨、威尼斯等城市成为金融中心,诞生了最早的银行(如美第奇银行)。
  1. 近代金融体系的形成(16世纪 - 18世纪)
  • 标志:股票市场和中央银行的建立。
  • 发展:随着大航海时代的到来,全球贸易扩张,金融需求激增。1602年,荷兰东印度公司成立了世界上第一家股票交易所——阿姆斯特丹证券交易所。1694年,英格兰银行成立,标志着现代中央银行的诞生。
  1. 工业革命与金融全球化(19世纪 - 20世纪初)
  • 标志:工业资本与金融资本的结合。
  • 发展:工业革命推动了生产力的飞跃,金融资本成为工业化的重要支撑。伦敦成为全球金融中心,金本位制被广泛采用,国际资本流动加速。同时,投资银行和保险公司等新型金融机构兴起。
    阅读全文 »

TODO:分析程序的资源,代码逻辑,渲染逻辑

闪存优化

闪存用于存储游戏数据和资源,包括游戏数据、纹理、音频等。闪存的读写速度比内存慢得多,因此在游戏中,我们需要尽可能地减少闪存的读写次数,以提高游戏的性能。

闪存结构和文件操作流程

性能优化-基础 中,介绍了相关的基础知识,为了文章的完整性,简单回顾一下。

  1. 闪存结构
  • SOC系统中的闪存一般采用NAND Flash或NOR Flash,作为非易失性存储器,用于存储操作系统、应用程序、资源文件等。
  • 闪存通过总线(如SPI、eMMC、UFS等)与SOC主控芯片连接。
  1. 文件读写流程
  • 文件读取:

    1. CPU发起文件读取请求,操作系统通过文件系统(如FAT、EXT4等)定位文件在闪存中的物理地址。
    2. 文件系统驱动将读取命令通过总线发送到闪存控制器。
    3. 闪存控制器根据地址从闪存芯片中读取数据,经过总线传输到SOC的内存(RAM)中。
    4. CPU从内存中获取数据进行处理。
  • 文件写入:

    1. CPU将需要写入的数据放入内存缓冲区。
    2. 操作系统通过文件系统分配闪存空间,并生成写入命令。
    3. 写入命令和数据通过总线传递给闪存控制器。
    4. 闪存控制器将数据写入指定的闪存地址。
    5. 写入完成后,文件系统更新元数据,保证数据一致性。
  • 应用层API:

    1. fopen, fread, fwrite, fclose等函数封装了文件操作的底层细节,但最终都是调用到操作系统API。
    2. 也可以使用内存映射的方式,将文件映射到内存中,直接操作内存,避免了文件直接调用API读取的过程, 映射后可以直接操作指针的方式读取和写入内存。

序列化(Serialization)

序列化的概念

Unity 的序列化(Serialization)体系分为编辑器写盘阶段和运行时读盘阶段,它同时支持 二进制格式(Binary SerializedFile)和 文本(YAML)格式。编辑器在构建场景、AssetBundle 或玩家(Player)时,将所有 UnityEngine.Object 派生的对象及其字段按照 “Type Tree + 对象数据” 的方式写入磁盘;运行时则根据磁盘上的 Type Tree 快速定位并重构内存中的 C++ 对象,并通过隐藏指针 m_CachedPtr 将之挂载到对应的 C# 托管对象上。二进制格式在读写时采用专门的 C++ 引擎代码和内存拷贝技术,支持内存映射 (.resS/.resource) 与多线程解压,是极高效的;而文本(YAML)格式则使用文本解析器和反射,仅在编辑器中针对小规模场景或开启 “Force Text” 时使用

序列化数据结构

AssetBundle

AssetBundle 是一个各种资源序列化后的集合,包括脚本、纹理、模型、音频等资源。 #### AssetBundle文件结构

AssetBundle打包流程

AssetBundle加载流程

AssetBundle卸载流程

Addressables

Resources

StreamingAssets

AssetDatabase

常用资源

内存优化

代码优化(CPU时间)

渲染优化(CPU端)

网络优化

总结

参考

组成原理

在计算机科学中,组成原理通常指的是计算机系统的基本组成部分,比如主板 (Motherboard)、中央处理器(CPU)、存储器(内存)、输入/输出设备等,并且涉及到它们之间的连接和交互。本节主要介绍一下PC和手机的组成部件,以及它们的作用。

PC

主板 (Motherboard)

主板 (Motherboard)主要由BIOS、总线、扩展插槽、芯片组和I/O端口等组成。

主板图
阅读全文 »

后处理是指,在正常渲染管线结束后,对渲染出来的结果进行加工,以此来模拟各种效果。

颜色

颜色(color) 对应电磁波的可见光波段,是被后期处理的波长信息。颜色既是物体的客观属性——确定的波长,又带有大脑的主观属性——不同的个体对特定波长的电磁波敏感程度不同,感受的颜色也有差异。 为了表示色彩,人们建立了一维、二维、三维甚至四维空间坐标模型,这些色彩模型称为颜色空间。颜色空间多达百种,常见的有如下5种。

颜色数据表示(Linear,LogC)

在影像制作和后期处理中,Linear、LogC和Gamma是三种关键的概念,它们描述了不同的图像数据处理和表示方法。理解它们之间的区别对于正确处理图像和视频数据非常重要。

Linear(线性): Linear指的是一种线性响应的色彩空间,其中记录的图像亮度值直接对应于场景中的实际光照强度。在线性色彩空间中,如果场景中一个区域的光照强度是另一个区域的两倍,那么记录的数值也会是两倍。这种表示方式使得图像的色彩混合和处理在数学上更加直接和简单,但由于人眼对亮度的感知是非线性的,线性空间通常不适用于最终图像的显示。

LogC(对数) LogC是ARRI摄影机特有的一种对数色彩空间,它旨在通过对数曲线来模拟人眼对亮度的非线性感知,使得在有限的比特深度下能够捕获更宽的动态范围。LogC色彩空间特别适合于记录高动态范围的场景,因为它能够有效地保留高光和阴影中的细节。然而,LogC图像在没有经过适当的色彩校正或应用LUT(查找表)之前,看起来会显得非常低饱和和低对比度。

Gamma Gamma校正是一种用于调整图像亮度的非线性操作,旨在使图像在特定显示设备上的显示更符合人眼的感知特性。Gamma校正可以被视为在图像数据和最终显示之间的一个桥梁,用于调整图像的整体亮度和对比度。不同的显示设备和媒体标准可能会使用不同的Gamma值,如sRGB标准使用大约2.2的Gamma值。

总结

Linear色彩空间: 最适合图像的处理和合成,需要在最终输出前转换到适合观看的色彩空间。

LogC色彩空间: 用于捕获和记录高动态范围的图像,需要在后期处理中进行色彩校正。

Gamma校正: 用于调整图像的显示,以符合人眼对亮度的非线性感知和特定显示设备的要求。

阅读全文 »

本文的目的是通过Shader代码实现Photoshop的图层混合模式,图层混合模式是将上图层中的颜色与下层图层中的颜色,通过一个公式计算出最总的颜色。

颜色混合公式

具体的公式如下:

混合模式 公式 说明
透明度 alpha * foregroundCol + bgCol * (1.0 - alpha) 实现Alpha混合
变暗 min(bgCol, foregroundCol) 上下图层中,取最小的颜色值
正片叠底 bgCol * foregroundCol 混合后颜色整体压暗,最常用的混合模式
颜色加深 1 - ((1-bgCol) / foregroundCol) -
线性加深 bgCol + foregroundCol - 1 -
变亮 max(bgCol, foregroundCol) 上下图层中,取最大的颜色值
滤色 1 - (1-bgCol) * (1-foregroundCol) 和正片叠底真好向反,两图层取反后再乘,再取反,也是比较常用的混合模式
颜色减淡 bgCol / (1-foregroundCol) -
线性减淡 bgCol + foregroundCol -
叠加 当bgCol<0.5时, 2(bgColforegroundCol); 当bgCol >= 0.5时, 1-2(1-bgCol)(1-foregroundCol) 当bgCol小于0.5时,使用2倍的正片叠底;当bgColr大于等于0.5时,使用2倍的滤色,使用2倍是为了当bgCol=0.5时不断层.
柔光 当foregroundCol<0.5时, 2bgColforegroundCol+pow(bgCol,2)(1-2foregroundCol); 当foregroundCol >= 0.5时, (2bgCol(1-foregroundCol)+sqrt(bgCol)(2foregroundCol - 1)) -
强光 当foregroundCol<0.5时, bgCol * (2foregroundCol); 当foregroundCol >= 0.5时, 1-((1-bgCol) (1-2*(foregroundCol - 0.5))) -
亮光 当foregroundCol<0.5时, 1-(1-bgCol)/(2foregroundCol); 当foregroundCol >= 0.5时, bgCol/(1-2(foregroundCol-0.5)) -
线性光 当foregroundCol<0.5时, bgCol+2foregroundCol-1; 当foregroundCol >= 0.5时, bgCol+2(foregroundCol-0.5) -
点光 当foregroundCol<0.5时, min(bgCol, 2foregroundCol); 当foregroundCol >= 0.5时, max(bgCol, 2(foregroundCol-0.5)) -
差值 abs(bgCol-foregroundCol) -
排除 0.5-2(bgCol-0.5)(foregroundCol-0.5) -
减去 bgCol-foregroundCol -
阅读全文 »

要在屏幕上绘制几何体时,Unity会调用底层的图形API的Draw命令进行绘制。一个Draw命令高数图形API绘制什么以及如何绘制。每个Draw命令都包含图形API所需的所有信息,其中包括texture,shader和buffers数据。绘图调用可能是资源密集型的,但绘图调用的准备工作通常比绘图调用本身更耗费资源。

准备绘制调用时,CPU去构建资源并通过图形API改变GPU的内部设置。这些设置统称为渲染状态。改变这些渲染状态通常时资源密集型的操作,比如切换不同的材质时。 因为渲染状态的改变是资源密集型的,所有减少渲染状态改变的次数是主要的优化方法,这有两种方法能到达此目的:

  • 减少总的绘制调用
  • 有效组织绘制调用,以减少渲染状态的切换。

优化绘制调用和渲染状态改变数量,主要减少每帧的时间,它也能:

  • 减少应用程序的电池的消耗。
  • 提高应用程序未来开发的可维护性。当你更早的优化绘制调用和渲染状态改变,那么它将长期维持在一个相对优化的级别。

Unity提供了如下几种优化绘制调用和渲染状态的方法,一些方法只适用于特定的场景。

  • Static Batching : 静态合并Mesh来减少绘制调用和渲染状态的改变。需要将对象标记为static。Unity将组合数据发送到GPU,但单独渲染组合中的每个网格。Unity仍然可以单独剔除网格,但每次绘制调用占用的资源较少,因为数据状态永远不会改变。(减少DrawCall)
  • Dynamic Batching : 在CPU动态上转换网格顶点,将相同配置的顶点分组,并在一次绘制调用中渲染它们。 比如顶点存储相同数量和类型的属性,则它们共享相同的配置。例如,位置和法线。 (减少DrawCall)
  • Manually combining meshes : 通过调用Mesh.CombineMeshes函数将多个Mesh合并为一个。 (减少DrawCall)
  • GPU Instancing : 渲染相同的mesh多次。GPU 实例化对于绘制在场景中多次出现的几何图形非常有用,例如树木或灌木丛。(减少DrawCall)
  • SRP Batcher : 在SRP项目中,可以使用SRP Batcher减少相同着色器变体的材质准备和绘制调用所需的CPU时间。 (不减少DrawCall,减少状态改变次数)

您可以在同一场景中使用多个绘制调用优化方法,但请注意,Unity会按特定顺序对绘制调用优化方法进行优先排序。如果您将一个游戏对象标记为使用多种绘制调用优化方法,Unity将使用优先级最高的方法。唯一的例外是SRP Batcher。当您使用SRP Batcher时,Unity还支持对与SRP Batcher兼容的游戏对象进行静态批处理。 Unity 按以下顺序对绘制调用优化进行优先排序:

  1. SRP Batcher and Static batching
  2. GPU Instancing
  3. Dynamic Batching

如果您将GameObject标记为静态批处理并且Unity成功对其进行了批处理,Unity会禁用该GameObject的GPU实例化,即使渲染器使用实例化着色器也是如此。发生这种情况时,Inspector 窗口显示一条警告消息,建议您禁用静态批处理。 同样,如果Unity可以对网格使用GPU实例化,Unity会禁用该网格的动态批处理。

阅读全文 »

通用渲染管线(URP)是Unity官方创建的可编程渲染管线(SRP)。URP提供了艺术家友好的工作流程,能够快速且简单创建跨平台的图形,从移动端到高端控制台和PC。SRP的内容可以参见:Unity渲染管线介绍

Unity将渲染管线的代码分为了几个包,分别如下:

  • com.unity.render-pipelines.core 此包包含了一些公共的可重用代码,URP和HDRP都会使用此包。
  • com.unity.render-pipelines.high-definition-config HDRP配置包。
  • com.unity.render-pipelines.high-definition HDRP的核心包。
  • com.unity.render-pipelines.universal URP的核心包。

应用阶段

从名字我们可以看出,这个阶段是由我们的应用主导的,因此通常由CPU负责实现。换句话说,我们这些开发者具有这个阶段的绝对控制权。 在这阶段中,开发者有3个主要任务:

  1. 我们要准备好场景数据,例如摄像机的位置、视椎体、场景中包含了哪些模型、使用了哪些光源等等;
  2. 为了渲染性能,我们往往需要做一个粗粒度剔除(culling)工作,以把哪些不可见的物体踢出去,这样就不需要再移交给几何阶段进行处理;
  3. 最后我们要设置好每个模型的渲染状态,这些渲染状态包括但不限于它们使用的材质(漫反射颜色、高光反色颜色)、使用的纹理、使用的Shader等。

这一阶段最重要的输出是渲染所需的几何信息,即渲染图元(rendering primitives)。通俗来讲,渲染图元可以是点、线、三角面等。这些渲染图元将会被传递给下一个阶段——几何阶段。

阅读全文 »