Mecanim 发表于 2022-8-6 11:46

(渲染)(引擎)UE底层渲染架构

首先,老规矩:

<font color=red>**未经允许禁止转载**</font>(防止某些人乱转,转着转着就到蛮牛之类的地方去了)

<font color=red>B站:Heskey0</font>

<hr/>
《Gpu Gems》《Gpu Pro》《Gpu Zen》系列读书笔记
为方便阅读(再次吐槽知乎),本文已搬运至: 【深入解析UE】渲染篇 - Heskey0 - 博客园 (cnblogs.com)
虚幻5渲染编程专栏概述及目录 - 知乎 (zhihu.com)
UE基础

架构

[*]ULevel是UE的关卡,是场景中物体的集合,存储着一系列Actor
[*]UWorld是ULevel的容器。每个UWorld实例必须包含一个主关卡(Persistent Level),还可能包含若干个流式关卡(Streaming Level,可选,非必需,可按需动态加载和卸载)
[*]常见的WorldType有游戏(Game)、编辑器(Editor)、编辑器播放(PIE)以及预览模式(EditorPreview、GamePreview)等。编辑器内的场景其实也是个World,类型为Editor。
内存
GC

[*]UObject提供了GC
[*]标记清除法

[*]将可达对象标记为TRUE
[*]收集待清理对象(UE增加了一步)
[*]清理不可达对象



[*]原子性GC过程

[*]获取GC锁,防止GC被其它线程操作
[*]执行GC
[*]释放GC锁


多线程渲染

UGameEngine::Tick()

[*]UWorld::Tick() :
*
单线程渲染:CPU和GPU可能相互等待
UE的线程(FRunnableThread)

[*]游戏线程(Game Thread)(也称作主线程)(UGameEngine::Tick)
[*]渲染线程(FRenderingThread)

[*]专门用于生成渲染指令 (FRenderCommand) 和渲染逻辑的独立线程



[*]RHI线程(RHI Thread)

[*]转换渲染指令到指定图形API,创建、上传渲染资源到GPU
[*]RHI全称是Render Hardware Interface(渲染硬件接口), 封装了众多图形API(DirectX、OpenGL、Vulkan、Metal)之间的差异


线程同步

[*]渲染命令栅栏(FRendeLauchEngineLooprCommandFence)

[*]强制同步RHI和GPU交换链



[*]同步(FFrameEndSync)

[*]在引擎循环的帧末尾添加游戏线程和渲染线程的同步事件


渲染机制

剖析虚幻渲染体系(03)- 渲染机制 - 0向往0 - 博客园 (cnblogs.com)
Draw Call 的产生
UE5

[*]RHI层的变动主要在于将各种顶点、索引Buffer统一成了FRHIBuffer。
[*]Renderer层增强了光线追踪,特别是屏幕空间的光线追踪,加强了距离场的各种应用,同时删除了LPV。
[*]Engine层主要围绕着RHI、Renderer层的变动做了相应修改和调整
Nanite

渲染特性:

[*]不支持:

[*]Forward rendering
[*]MSAA


Component:

[*]支持:

[*]static mesh



[*]不支持:

[*]skeletal animation
[*]morph target / blend shape
[*]spline mesh


Preview

1. VSM (Virtual Shadow Map):一种新的阴影投射方法
UE5中VirtualShadowMap的简易实现原理(一) - 知乎 (zhihu.com)
UE5 虚拟阴影贴图 (VirtualShadowMaps)的优势和局限性 - 知乎 (zhihu.com)

[*]开启后,会替换传统阴影技术(CSM, SDF,......),GPU上实现
[*]开启后,SMRT (Shadow Map Ray Tracing) 可以利用VSM生成阴影
Algorithm Overview:

[*]假设有一张分辨率极高的 Virtual ShadowMap(16K)
[*]通过坐标转换,找到深度图中像素对应到 Virtual ShadowMap中的格子 (Virtual Tile),然后标记为Used,每个标记的格子挨个(紧密)存放到一张UAV (Tile Padding)
[*]利用UAV走正常的shadowmap流程,没有标记的 Virtual Tile 用不到
Mipmap适配:

[*]生成不同分辨率的Virtual ShadowMap(Mipmap)
[*]根据深度算出当前屏幕对应的Mipmap Level级别
裁剪图(Clipmap):

[*]单张虚拟阴影贴图无法提供足够的分辨率来覆盖大型区域
[*]定向光源使用裁剪图(Clipmap)结构来扩展摄像机周围的范围
[*]每个裁剪图级别都有其单独的16K VSM
2. SMRT (Shadow Map Ray Tracing):利用VSM生成软阴影
PCF过于模糊
Algorithm Overview:

[*]沿光源发射一些光线
[*]用VSM作测试
3. TSR (Temporal Super Resolution):替换传统TAA
Core

Cluster :

[*]一组相邻三角形的集合 ( 在早前已被育碧和寒霜引擎使用 )
[*]Cluster可以和相邻的Cluster动态合批
Lumen

Surface Cache

[*]Lumen为场景表面附近生成Surface Cache
[*]用于快速查找射线命中点的光照
屏幕追踪

[*]Lumen先对屏幕进行追踪
[*]追踪失败,则使用更可靠的方法
光线追踪

[*]软件光线追踪(ray marching)

[*]依赖于距离场,较大网格会有不良表现
[*]Detail Tracing(默认):前两米使用网格距离场,其它距离使用全局距离场
[*]Global Tracing:利用全局距离场快速追踪,损失画质效果



[*]硬件光线追踪
补充(非Lumen):DFAO

[*]DFAO中网格距离场没有合并到全局距离场
[*]所有的网格距离场atlas到一张体贴图上(300M显存)
[*]step1 : 光源出发Trace,剔除掉不会相交的距离场
[*]step2 : 从场景出发沿光源做Trace
Shader

第一梯队的shader模块是最底层最基础的模块,这些模块不会引用其它模块,但会被其它很多模块引用。这些模块主要有:

[*]BasePassCommon.ush
[*]BRDF.ush
[*]CapsuleLight.ush
[*]Common.ush

[*]图形API或Feature Level相关的宏、类型、局部变量、静态变量、基础工具接口

[*]FeatureLevel:Feature levels in Direct3D - Wikipedia
[*]// FEATURE_LEVEL的宏定义
#define FEATURE_LEVEL_ES2_REMOVED    1
#define FEATURE_LEVEL_ES3_1          2
#define FEATURE_LEVEL_SM3            3
#define FEATURE_LEVEL_SM4            4
#define FEATURE_LEVEL_SM5            5
#define FEATURE_LEVEL_MAX            6





[*]CommonViewUniformBuffer.ush
[*]Definitions.usf

[*]定义了一些常见的宏,防止其它模块引用时出现语法错误



[*]FP16Math.ush
[*]Platform.ush

[*]图形API(DirectX、OpenGL、Vulkan、Metal)和FEATURE_LEVEL相关的宏、变量及工具类接口



[*]ShaderVersion.ush
[*]LightGridCommon.ush
[*]LocalVertexFactoryCommon.ush

[*]Get/Set :
[*]Color
[*]LightMapCoordinates
[*]Tangent
[*]图元id(PrimitiveId)



[*]ShadingCommon.ush

[*]定义了材质所有着色模型ID,并提供了少量相关的工具类接口

[*]// 材质着色模型, 每种类型都有对应的光照算法和流程.
#define SHADINGMODELID_UNLIT                0
#define SHADINGMODELID_DEFAULT_LIT            1
#define SHADINGMODELID_SUBSURFACE            2
#define SHADINGMODELID_PREINTEGRATED_SKIN    3
#define SHADINGMODELID_CLEAR_COAT            4
#define SHADINGMODELID_SUBSURFACE_PROFILE    5
#define SHADINGMODELID_TWOSIDED_FOLIAGE      6
#define SHADINGMODELID_HAIR                  7
#define SHADINGMODELID_CLOTH                8
#define SHADINGMODELID_EYE                  9
#define SHADINGMODELID_SINGLELAYERWATER      10
#define SHADINGMODELID_THIN_TRANSLUCENT      11
#define SHADINGMODELID_NUM                  12
#define SHADINGMODELID_MASK                  0xF      // ShadingModelID只占用了GBuffer的4bit.





[*]ShadowDepthCommon.ush
[*]ShadowProjectionCommon.ush
[*]SHCommon.ush
[*](......)
第二梯队的重要或基础模块会引用第一梯队的基础模块,但也会被其它梯队或模块引用:

[*]BasePassVertexCommon.ush

[*]从VS传到PS/DS的结构体



[*]CapsuleLightIntegrate.ush
[*]DeferredLightingCommon.ush

[*]光源数据 struct FDeferredLightData
[*]阴影射线检测ShadowRayCast
[*]光照计算接口



[*]DeferredShadingCommon.ush

[*]颜色空间转换

[*]RGBToYCoCg
[*]YCoCgToRGB





[*]颜色,法线,GBuffer数据的编码解码
[*]GBuffer数据集合:struct FGBufferData
[*]屏幕空间数据:
//包含了GBuffer和AO.
struct FScreenSpaceData
{
    // GBuffer (material attributes from forward rendering pass)
    FGBufferData GBuffer;
    float AmbientOcclusion;
};



[*]LocalVertexFactory.ush

[*]// 从绑定的顶点Buffer中获取的顶点输入数据.
struct FVertexFactoryInput{}



[*]MaterialTemplate.ush
[*]RectLight.ush
[*]RectLightIntegrate.ush
[*]ShadingModels.ush

[*]着色模型以及光照计算相关的类型和辅助接口
[*]Light结构体

[*]struct FAreaLight区域光
[*]struct FDirectLighting直接光
[*]struct FShadowTerms阴影数据





[*]光照接口

[*]能量归一化EnergyNormalization
[*]DualSpecularGGX
[*]RefractBlend





[*]ShadingModel光照

[*]SimpleShading
[*]DefaultLitBxDF
[*]HairBxDF
[*]ClearCoatBxDF
[*]SubsurfaceProfileBxDF
[*]ClothBxDF
[*]SubsurfaceBxDF
[*](......)
[*]//会根据ShadingModelID调用上面不同的接口
[*]IntegrateBxDF
[*]EvaluateBxDF





[*]ShadingModelsMaterial.ush

[*]提供了根据材质和指定参数设置GBuffe的接口



[*]VertexFactoryCommon.ush

[*]顶点变换接口

[*]TransformLocalToWorld
[*]TransformLocalToTranslatedWorld
[*]RotateLocalToWorld
[*]RotateWorldToLocal
[*]UnitToOct





[*](......)
最后是第三梯队的模块,重要模块的实现,会引用第一、第二梯队的模块:

[*]BasePassPixelShader.usf
[*]BasePassVertexShader.usf
[*]CapsuleShadowShaders.usf
[*]DeferredLightPixelShaders.usf
[*]DeferredLightVertexShaders.usf
[*]ShadowProjectionPixelShader.usf
[*]ShadowProjectionVertexShader.usf
[*](......)
FShader

继承
graph BT
        FGlobalShader-->FShader;
        FMaterialShader-->FShader;
基础概念

Shader :

[*]FGlobalShader

[*]子类在内存中只有唯一实例



[*]FMaterialShader

[*]由FMaterialShaderType指定的材质引用的Shader
[*]材质蓝图实例化后的shader子集


ShaderType :

[*]FShaderType

[*]FGlobelShaderType

[*]最简单的Shader
[*]每个ShaderType只有一个实例





[*]FMaterialShaderType

[*]与材质链接





[*]FMeshMaterialShaderType

[*]与材质链接
[*]使用Vertex Factory




ShaderParameter

[*]由CPU的C++层传入GPU Shader并存储于GPU寄存器或显存的数据,提供Bind()进行数据绑定。着色器参数可以绑定任何GPU类型的资源或数据 ,但不同的类只能绑定特定的着色器类型
[*]FShaderParameter

[*]float1/2/3/4,数组,UAV



[*]FShaderResourceParameter

[*]纹理,采样器



[*]FRWShaderParameter

[*]绑定了UAV或SRV资源


Vertex Factory

[*]支持网格类型( 静态网格、蒙皮骨骼、程序化网格以及地形等等)
[*]渲染线程包含了Vertex Factory对象,横跨CPU和GPU两端
[*]FVertexFactory封装了可以链接到顶点着色器的顶点数据资源
编译机制

usf文件编译成对应目标平台的shader代码【Shader: 一段文本,GPU硬件上执行】
Shader Map

[*]存储编译后的Shader代码(Shader Permuation)
[*]FGlobalShaderMap
[*]FMaterialShaderMap
[*]FMeshMaterialShaderMap
[*]可以把它理解成一个三维矩阵,长度为每个材质类型,宽度为每个渲染阶段Pass,高度为每个顶点工厂类型,矩阵的每一个方格都对应了一组着色器组合(顶点着色器,像素着色器),材质也不一定参与全部阶段,所以这个三维矩阵中是存在有很多空缺的
Uber Shader设计:Shader Permutation
编译与跨平台

1. 编译2. Shader跨平台
UE造了个轮子HLSLCC (HLSL Cross Compiler)
HLSLCC (以GLSL为例):

[*]Preprocessing,预处理阶段。通过类似C风格的预处理器运行,在编译之前,UE使用MCPP进行预处理,因此跳过了这一步。
[*]Parsing,语法分析阶段。通过Mesa的**_mesa_hlsl_parse**接口,HLSL将被分析成抽象语法树,Lexer(语法分析)和Parser分别由flex和bison生成。
[*]Compilation,编译阶段。利用 _mesa_ast_to_hir,将AST(抽象语法树)编译为Mesa IR。在此阶段,编译器执行隐式转换、函数重载解析、生成内部函数的指令等功能,也将生成 GLSL 主入口点,会将输入及输出变量的全局声明添加到IR,同时计算HLSL入口点的输入,调用HLSL入口点,并将输出写入全局输出变量。
[*]Optimization,优化阶段。主要通过do_optimization_pass对IR执行多遍优化,包括直接插入函数、消除无用代码、传播常量、消除公共的子表达式等等。
[*]Uniform packing,全局变量打包。将全局统一变量打包成数组并保留映射信息,以便引擎可将参数与一致变量数组的相关部分绑定。
[*]Final optimization,最终优化阶段。打包统一变量之后,将对IR运行第二遍优化,以简化打包统一变量时生成的代码。
[*]Generate GLSL,生成GLSL。最后步骤,将已经优化的IR转换为GLSL源代码。除了生成所有构造及统一变量缓冲区的定义以及源代码本身以外,还会在文件开头的注释中写入一个映射表。
在UE4.25,Shader的跨平台示意图如下:



img

注:
Direct3D和OpenGL虽然在标准化设备坐标一致,但在UV空间的坐标是不一致的:

UE为了不让shader的开发人员察觉到这一差异,采用了翻转的图片,强制使得UV坐标用统一的范式:

这样做的后果就是OpenGL的纹理实际上是垂直翻转的(从RenderDoc截取的UE在OpenGL平台下的应用也可佐证),不过渲染后期可以再次翻转就行了。但是,UE采用颠倒(Upside down)的渲染方式,并且将颠倒的参数集成到投影矩阵:

因此,看起来标准化设备坐标和D3D下的纹理都是垂直翻转的。
调试

虚幻5渲染编程专栏概述及目录 - 知乎 (zhihu.com)
step1: 开启指令,方便RenderDoc调试
Engine\Config\ConsoleVariables.ini
命令行解析r.ShaderDevelopmentMode=1获得关于着色器编译的详细日志和错误重试的机会。r.DumpShaderDebugInfo=1将编译的所有着色器的文件保存到磁盘ProjectName/Saved/ShaderDebugInfo的目录。包含源文件、预处理后的版本、一个批处理文件(用于使用编译器等效的命令行选项来编译预处理版本)。r.DumpShaderDebugShortNames=1保存的Shader路径将被精简。r.Shaders.Optimize=0禁用着色器优化,使得shader的调试信息被保留。r.Shaders.KeepDebugInfo=1保留调试信息,配合RenderDoc等截帧工具时特别有用。r.Shaders.SkipCompression=1忽略shader压缩,可以节省调试shader的时间。修改了某些Shader文件, 控制台输入RecompileShaders即可重新编译
Material

FMaterialRenderProxy负责接收游戏线程的数据,然后传递给渲染器去处理和渲染
FMaterial有3个功能:

[*]表示材质到材质的编译过程,并提供可扩展性钩子(CompileProperty等) 。
[*]将材质数据传递到渲染器,并使用函数访问材质属性。
[*]存储缓存的shader map,和其他来自编译的瞬态输出,这对异步着色器编译是必要的。
渲染

UE4渲染模块分析 - 简书 (jianshu.com)
类型解析UPrimitiveComponent图元组件,是所有可渲染或拥有物理模拟的物体父类。是CPU层裁剪的最小粒度单位。FPrimitiveSceneProxy图元场景代理,是UPrimitiveComponent在渲染器的代表,镜像了UPrimitiveComponent在渲染线程的状态。FPrimitiveSceneInfo渲染器内部状态(描述了FRendererModule的实现),相当于融合了UPrimitiveComponent and FPrimitiveSceneProxy。只存在渲染器模块,所以引擎模块无法感知到它的存在。FScene是UWorld在渲染模块的代表。只有加入到FScene的物体才会被渲染器感知到。渲染线程拥有FScene的所有状态(游戏线程不可直接修改)。FSceneView描述了FScene内的单个视图(view),同个FScene允许有多个view,换言之,一个场景可以被多个view绘制,或者多个view同时被绘制。每一帧都会创建新的view实例。FViewInfoview在渲染器的内部代表,只存在渲染器模块,引擎模块不可见。FSceneRenderer每帧都会被创建,封装帧间临时数据。下派生FDeferredShadingSceneRenderer(延迟着色场景渲染器)和FMobileSceneRenderer(移动端场景渲染器),分别代表PC和移动端的默认渲染器。FMeshBatchElement单个网格模型的数据,包含网格渲染中所需的部分数据,如顶点、索引、UniformBuffer及各种标识等。FMeshBatch存着一组FMeshBatchElement的数据,这组FMeshBatchElement的数据拥有相同的材质和顶点缓冲。FMeshDrawCommand完整地描述了一个Pass Draw Call的所有状态和数据,如shader绑定、顶点数据、索引数据、PSO缓存等。FMeshPassProcessor网格渲染Pass处理器,负责将场景中感兴趣的网格对象执行处理,将其由FMeshBatch对象转成一个或多个FMeshDrawCommand。渲染模块由如下几个部分组成:

[*]场景的描述
[*]场景遍历和拣选
[*]渲染的执行
1. 场景的描述

UE4场景管理相关的数据结构如下:

[*]FScene 场景类
[*]FPrimitiveSceneProxy 场景里的几何体类
[*]FPrimitiveSceneInfo 场景里的结点(拥有几何体和状态信息)
每个几何体具有材质属性,相关的数据结构如下:

[*]FMaterial 材质接口类,提供材质属性的查询(eg. blend mode)和shader查找。
[*]FMaterialResoruceUMaterial实现的具体的FMaterial
[*]FMaterialRenderProxy 渲染线程用的Material对象,提供FMaterial的访问和材质参数的访问(eg. scaler, vector, texture parameter等参数)。
2. 场景的遍历和拣选

可见性判断
2.1 从FPrimitiveSceneProxy到FMeshBatch

基本概念:

[*]UPrimitiveComponent是图元组件,是所有可渲染或拥有物理模拟的物体父类。是CPU层裁剪的最小粒度单位。
[*]FPrimitiveSceneProxy是游戏线程UPrimitiveComponent在渲染线程的镜像数据
[*]FMeshBatch包含了绘制Pass所需的所有信息,解耦了网格Pass和FPrimitiveSceneProxy(FMeshBatch作为中间层),所以FPrimitiveSceneProxy并不知道会被哪些Pass绘制
[*]FMeshBatch存着一组FMeshBatchElement,这组FMeshBatchElement的数据拥有相同的材质和顶点缓冲
FMeshBatch记录了一组拥有相同材质和顶点工厂的FMeshBatchElement数据. 一个FMeshBatch拥有一组FMeshBatchElement、一个顶点工厂和一个材质实例,同一个FMeshBatch的所有FMeshBatchElement共享着相同的材质和顶点缓冲 (大部分情况一个FMeshBatch只有一个FMeshBatchElement)
FMeshElementCollector和FSceneRenderer是一一对应关系,每个FSceneRenderer拥有一个收集器。
2.2 从FMeshBatch到FMeshDrawCommand

【MeshPassProcessor.h】

[*]收集完动态的MeshElement,紧接着会调用SetupMeshPass来创建FMeshPassProcessor , FMeshPassProcessor充当了将FMeshBatch转换成FMeshDrawCommands的角色。
[*]FMeshBatch转换成FMeshDrawCommand后,每个Pass都对应了一个FMeshPassProcessor,每个FMeshPassProcessor保存了该Pass需要绘制的所有FMeshDrawCommand,以便渲染器在合适的时间触发并渲染。
FMeshPassProcessor的主要作用是:

[*]Pass过滤。将该Pass无关的MeshBatch给过滤掉,比如深度Pass过滤掉透明物体。( 不同的MeshPass处理FMeshBatch会有所不同 )
[*]选择绘制命令所需的Shader及渲染状态(深度、模板、混合状态、光栅化状态等)。
[*]收集绘制命令涉及的Shader资源绑定。

[*]Pass的Uniform Buffer,如ViewUniformBuffer、DepthPassUniformBuffer。
[*]顶点工厂绑定(顶点数据和索引)。
[*]材质绑定。
[*]Pass的与绘制指令相关的绑定。



[*]收集Draw Call相关的参数。




img

最终,FMeshDrawCommand接入RHI层
2.3 静态与动态绘制路径





img

UE存在3种网格绘制路径(橙色为每帧动态生成,蓝色为只生成一次后缓存)

[*]第1种是动态绘制路径,从FPrimitiveSceneProxy到RHICommandList每帧都会动态创建,效率最低,但可控性最强
[*]第2种是需要View的静态路径,可以缓存FMeshBatch数据,效率中,可控性中
[*]第3种是不需要view的静态绘制路径,可以缓存FMeshBatch和FMeshDrawCommand,效率最高,但可控性差,需满足的条件多。
(1) 动态
通过GetDynamicMeshElements接口来收集FMeshBatch
3. 渲染的执行

1)多Pass绘制
Pass_0: PrePass/Depth Only Pass
Pass_1: BassPass
Pass_2: Issue Occlusion Queries / BeginOcclusionTests

[*]深度测试
Pass_3: ShadowMap

[*]每个光源渲染相应的Shadowmap
Pass_4: Lighting

[*]预处理组合型光照(SSAO)
[*]光照计算
Pass_5: Draw atmosphere

[*]对非透明表面绘制大气
Pass_6: Draw Fog

[*]对非透明表面逐像素计算Fog
Pass_7: Draw translucency
Pass_8: Post Processing
延迟渲染

简介


[*]1988年提出
[*]基于屏幕空间
1. Overview:
两个Pass

[*]Geometry Pass (UE称为Base Pass):生成 GBuffer

[*]所有的不透明物体(Opaque)和Masked物体 使用Unlit材质进行渲染
[*]将几何信息写入RT中

[*]Depth
[*]Normal
[*]材质参数(Diffuse Color, Emissive Color, Roughness, , Shading Model, AO, ......)





[*]Lighting Pass:使用 GBuffer
Coding in Unreal Engine:
void RenderBasePass()
{
    SetupGBuffer(); // 设置几何数据缓冲区。
   
    // 遍历场景的非半透明物体
    for each(Object in OpaqueAndMaskedObjectsInScene)
    {
      SetUnlitMaterial(Object);    // 设置无光照的材质
      DrawObjectToGBuffer(Object); // 渲染Object的几何信息到GBuffer,通常在GPU光栅化中完成。
    }
}

void RenderLightingPass()
{
    BindGBuffer(); // 绑定几何数据缓冲区。
    SetupRenderTarget(); // 设置渲染纹理。
   
    // 遍历RT所有的像素
    for each(pixel in RenderTargetPixels)
    {
      // 获取GBuffer数据。
      pixelData = GetPixelDataFromGBuffer(pixel.uv);
      // 清空累计颜色
      color = 0;   
      // 遍历所有灯光,将每个灯光的光照计算结果累加到颜色中。
      for each(light in Lights)
      {
            color += CalculateLightColor(light, pixelData);
      }
      // 写入颜色到RT。
      WriteColorToRenderTarget(color);
    }
}
2. 优劣:


[*]复杂度 O(N_{light}\times W_{RT}\times H_{RT}) ,跟场景的物体数量解耦,只跟Render Targe尺寸相关
[*]避免了很多本被遮挡的物体光照计算


[*]多一个通道来绘制几何信息,需要多渲染纹理(MRT)的支持,更多的CPU和GPU显存占用,更高的带宽要求
[*]有限的材质呈现类型
[*]难以使用MSAA等硬件抗锯齿,存在画面较糊的情况等等
[*]应对简单场景时,可能反而得不到渲染性能方面的提升
Unreal Engine Implementation

graph BT
        FMobileSceneRenderer-->FSceneRenderer
        FDeferredShadingSceneRenderer-->FSceneRenderer

[*]FMobileSceneRenderer是用于移动平台的场景渲染器,默认采用了前向渲染的流程。
[*]FDeferredShadingSceneRenderer虽然名字叫做延迟着色场景渲染器,但其实集成了包含前向渲染和延迟渲染的两种渲染路径,是PC和主机平台的默认场景渲染器。
1. FSceneRenderer创建
FSceneRenderer

[*]处理和渲染场景,生成RHI层的渲染指令
[*]由游戏线程的 FRendererModule::BeginRenderingViewFamily负责创建和初始化,然后传递给渲染线程。
[*]渲染线程会调用FSceneRenderer::Render(),渲染完返回后,会删除FSceneRenderer的实例。也就是说,FSceneRenderer会被每帧创建和销毁。
class UGameEngine : public UEngine
UGameEngine::Tick()

[*]calledUGameEngine::RedrawViewports()

[*]called UGameViewportClient::Draw()

[*]called FRendererModule::BeginRenderingViewFamily()




FRendererModule::BeginRenderingViewFamily(..., FSceneViewFamily* ViewFamily)
{
FScene* const Scene = ViewFamily->Scene->GetRenderScene();
FSceneRenderer* SceneRenderer = FSceneRenderer::CreateSceneRenderer(ViewFamily, ...);
}
创建FSceneRenderer之后发送指令给渲染线程

[*]调用FSceneRenderer->Render(RHICmdList);
[*]等待SceneRenderer的任务完成然后清理并删除实例
2. FDeferredShadingSceneRenderer
In deferred shader, the SSAO uses the GBuffer and must be executed after base pass. Otherwise, async compute runs the shader in RenderHzb()
3. PrePass
浅谈HiZ-buffer - DeepDream
PrePass又被称为提前深度Pass、Depth Only Pass、Early-Z Pass,用来渲染不透明物体的深度。
此Pass只会写入深度而不会写入颜色,写入深度时有disabled、occlusion only、complete depths三种模式,视不同的平台和Feature Level决定。
通常用来建立Hierarchical-Z,以便能够开启硬件的Early-Z技术,还可被用于遮挡剔除,提升Base Pass的渲染效率
绘制深度时,由于不需要写入颜色,那么渲染物体时使用的材质肯定不应该是物体本身的材质,而是某种简单的材质 UMaterial::GetDefaultMaterial(MD_Surface)( 在engine.ini配置文件中指定 )
ResolvedPath = L"/Engine/EngineMaterials/WorldGridMaterial.WorldGridMaterial"
非常值得一提的是:WorldGridMaterial使用的Shading Model是Default Lit,材质中也存在冗余的节点。如果想要做极致的优化,建议在配置文件中更改此材质,删除冗余的材质节点,改成Unlit模式更佳,以最大化地缩减shader指令,提升渲染效率。
4. BasePass

[*]用来渲染不透明物体的几何信息,包含法线、深度、颜色、AO、粗糙度、金属度等等,这些几何信息会写入若干张GBuffer中
[*]Base Pass正常情况下使用的是FMeshBatch收集到的材质,也就是网格自身的材质,唯一不同的是shader中不会启用光照计算,类似于Unlit模式。
[*]遍历所有view, 每个view渲染一次Base Pass.
OverView : O(n^3)
foreach(dscene in scenes)
{
    foreach(view in views)
    {
      foreach(mesh in meshes)
      {
            DrawMesh(...) // 每次调用渲染就执行一次BasePassVertexShader和BasePassPixelShader的代码.
      }
    }
}
RenderBasePass

[*]RenderBasePassInternal

[*]SetupBasePassState: 初始化BassPass所用的渲染状态(渲染设置)


执行Shader
BasePass绘制时使用的VS和PS分别是TBasePassVS和TBasePassPS (BasePassRendering.h)
template <typename LightMapPolicyType>
void GetBasePassShaders(const FMaterial& Material, ...)
{
    (......)

    VertexShader = Material.GetShader<TBasePassVS<LightMapPolicyType, false> >(VertexFactoryType);
    PixelShader = Material.GetShader<TBasePassPS<LightMapPolicyType, false> >(VertexFactoryType);
   
    (......)
}

[*]TBasePassVS和TBasePassPS 提供了获取Shader绑定、更改编译环境和只编译指定的排列组合shader等接口,此外还拥有反射球、光照图类型等属性
执行Shader文件:BasePassVertexShader.usf ,BasePassPixelShader.usf
BasePassPixelShader的主要步骤:

[*]初始化ResolvedView、GBuffer等数据。
[*]GetMaterialPixelParameters:获取材质的参数,从材质编辑器编辑的材质节点和插值数据而来。详细数据由FMaterialPixelParameters定义
// Engine\Shaders\Private\MaterialTemplate.ush
struct FMaterialPixelParameters{}

[*]CalcMaterialParametersEx:根据FMaterialPixelParameters计算额外的材质参数。
[*]根据FMaterialPixelParameters的数据和从顶点插值而来的数据计算GBuffer数据。
[*]计算次表面散射、贴花、体积光照图等数据。
[*]SetGBufferForShadingModel:设置前面步骤收集到的参数到GBuffer。
[*]计算或处理速度缓冲、高光颜色、漫反射颜色、环境法线、AO,叠加次表面散射颜色到漫反射中。
[*]处理预计算的非直接光照和天空光、非直接AO,计算最终的漫反射项。
[*]处理前向渲染光照或平行光,处理透明物体光照。
[*]计算雾效果,包含高度雾、指数雾、体积雾、逐像素雾、云体雾,以及天空大气效果。
[*]处理自发光。
[*]EncodeGBuffer:将GBuffer基础数据编码到MRT中。
[*]将GBuffer的非基础数据写入到后面几个RT,如速度缓冲、逐物体阴影等。
[*]处理预曝光。
即:
5. LightingPass
负责间接阴影、AO、透明体积光照、光源计算、LPV、天空光、SSS等
FDeferredShadingSceneRenderer::RenderLights
graph TB
        FinalColor-->LightAccumnulator;
        FinalColor-->View.PreExposure;
        LightAccumnulator-->Color;
        Color-->Fogging.a;
        Color-->+;
        +-->Fogging.rgb;
        +-->DiffuseColor;
        +-->Emissive;
        DiffuseColor-->AOMultiBounce;
        AOMultiBounce-->GBuffer.BaseColor;
        AOMultiBounce-->DiffOcclusion;
        DiffuseColor-->\(+);
        \(+)-->*;
        *-->DiffuseIndirectLighting;
        *-->GBuffer.DiffuseColor;
        \(+)-->**;
        **-->SubsurfaceIndirectLighting;
        **-->SubsurfaceColor;
RHI

RDG

RDG的理念不在GPU上立即执行Pass,而是先收集所有需要渲染的Pass,然后按照依赖的顺序对图表进行编译和执行,期间会执行各类裁剪和优化。(Pass的管理者)
UE5


[*]增加实例化裁剪模块:InstanceCulling、InstanceCullingManager等,包含FInstanceCullingRdgParams、EInstanceCullingMode、FInstanceCullingContext、FInstanceCullingManager等类型,主要用于Nanite技术。
[*]虚拟纹理增加或完善了数据读写和FVirtualTextureFeedbackBuffer、RenderPages、RenderPagesStandAlone等接口。
[*]全局距离场数据(FGlobalDistanceFieldParameterData)增加了Mipmap和VT数据和接口。
[*]HairStrand增加EHairBindingType、EHairInterpolationType、FHairStrandsInstance等类型。
[*]增加或完善FVertexFactoryShaderPermutationParameters的类型。
[*]MeshPassProcessor:

[*]EMeshPass增加VSMShadowDepth、LumenCardCapture、EditorLevelInstance等专用通道。
[*]增加EFVisibleMeshDrawCommandFlags、FCompareFMeshDrawCommands、FMeshPassProcessorRenderState等类型。
[*]增加FSimpleMeshDrawCommandPass类型,用于处理不需要并行处理绘制命令的地方,减少开销。
[*]FVisibleMeshDrawCommand增加EFVisibleMeshDrawCommandFlags以及用于GPUCull的FMeshDrawCommandSortKey、InRunArray等。



[*]FDynamicPassMeshDrawListContext的FinalizeCommand阶段增加了NewVisibleMeshDrawCommand.Setup阶段。
[*]摒弃SetInstancedViewUniformBuffer、SetPassUniformBuffer、GetInstancedViewUniformBuffer等UniformBuffer接口。
[*]新增Nanite模块:

[*]新增NaniteRender:FNaniteCommandInfo、ENaniteMeshPass、FNaniteDrawListContext、FCullingContext、FRasterContext、FRasterResults、FNaniteShader、FNaniteMaterialVS、FNaniteMeshProcessor、FNaniteMaterialTables、ERasterTechnique、ERasterScheduling、EOutputBufferMode、FPackedView等等类型及处理接口。
[*]FPrimitiveFlagsCompact增加bIsNaniteMesh标记。
[*]FPrimitiveSceneInfo增加NaniteCommandInfos、NaniteMaterialIds、LumenPrimitiveIndex以及CachedRayTracingMeshCommandsHashPerLOD、bRegisteredWithVelocityData、InstanceDataOffset、NumInstanceDataEntries实例化和光追相关的等数据和处理接口。



[*]新增Lumen模块:

[*]涉及的模块非常多,总结起来有DiffuseIndirect、Scene、HardwareRayTracing、Mesh、Probe、Radiance、Radiosity、TranslucencyVolume、Voxel以及数据结构、工具箱等。



[*]替换传统Shader绑定接口到RDG,如SHADER_PARAMETER_TEXTURE改成SHADER_PARAMETER_RDG_TEXTURE。
[*]增加或完善RuntimeVirtualTextureProducer、FSceneTextureExtracts、EMobileSceneTextureSetupMode、Strata地层。
[*]增强后处理效果,如增加了Temporal Super Resolution(TSR)。
[*]增强了光照追踪模块,如RTGI、RTAO、RTR、RTShadow、RTSkyLight等。
[*]DeferredShadingRenderer:

[*]增加Lumen、IndirectLightRender、SSRT、TranslucencyLightingVolume等相关的模块、类型和步骤。
[*]增加FLumenCardRenderer、TPipelineState、EDiffuseIndirectMethod、EAmbientOcclusionMethod、EReflectionsMethod、FPerViewPipelineState、FFamilyPipelineState等类型。



[*]增加或增强了BasePass、DepthPass、SDF、GPUScene、GlobalDistanceField、BVH、GenerateConservativeDepthBuffer、LightRendering、InrectLightRendering、MeshDrawCommands、Shader、ScreenSpace、Shadow、MobileRender等等渲染模块。
从UE4.26到5.0EA,重要和基础的渲染模块都做了大大小小的修改。
Appendix

Compute Shader

Compute Shader : Optimize your game using compute - 知乎
为了执行通用计算,NV推出了CUDA,Khronos推出了OpenCL,Microsoft推出了DirectCompute,也就是后来的Compute Shader,然后,各种图形API也相继推出了CS
        DX虽然从10开始支持Compute Shader/Direct Compute,但是限制比较大。DX11的Compute Shader拥有更强大的功能(当然肯定还有DX12)。所以我们一般在Unity中使用CS,还是要求shader target4.5(也就是shader model 5)
页: [1]
查看完整版本: (渲染)(引擎)UE底层渲染架构