UE渲染源码剖析
首先,老规矩:
未经允许禁止转载(防止某些人乱转,转着转着就到蛮牛之类的地方去了)
B站:Heskey0
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() : [LevelTick.cpp]
*
单线程渲染: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
继承
基础概念
Shader :
- FGlobalShader
- 子类在内存中只有唯一实例
- FMaterialShader
- 由FMaterialShaderType指定的材质引用的Shader
- 材质蓝图实例化后的shader子集
ShaderType :
- FShaderType
- FGlobelShaderType
- 最简单的Shader
- 每个ShaderType只有一个实例
- FMaterialShaderType
- 与材质链接
- FMeshMaterialShaderType
- 与材质链接
- 使用Vertex Factory
- FGlobelShaderType
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的跨平台示意图如下:
注:
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,和其他来自编译的瞬态输出,这对异步着色器编译是必要的。
渲染
类型 | 解析 |
---|---|
UPrimitiveComponent | 图元组件,是所有可渲染或拥有物理模拟的物体父类。是CPU层裁剪的最小粒度单位。 |
FPrimitiveSceneProxy | 图元场景代理,是UPrimitiveComponent在渲染器的代表,镜像了UPrimitiveComponent在渲染线程的状态。 |
FPrimitiveSceneInfo | 渲染器内部状态(描述了FRendererModule的实现),相当于融合了UPrimitiveComponent and FPrimitiveSceneProxy。只存在渲染器模块,所以引擎模块无法感知到它的存在。 |
FScene | 是UWorld在渲染模块的代表。只有加入到FScene的物体才会被渲染器感知到。渲染线程拥有FScene的所有状态(游戏线程不可直接修改)。 |
FSceneView | 描述了FScene内的单个视图(view),同个FScene允许有多个view,换言之,一个场景可以被多个view绘制,或者多个view同时被绘制。每一帧都会创建新的view实例。 |
FViewInfo | view在渲染器的内部代表,只存在渲染器模块,引擎模块不可见。 |
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查找。
- FMaterialResoruce UMaterial实现的具体的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相关的参数。
最终,FMeshDrawCommand接入RHI层
2.3 静态与动态绘制路径
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
-
FMobileSceneRenderer
是用于移动平台的场景渲染器,默认采用了前向渲染的流程。 -
FDeferredShadingSceneRenderer
虽然名字叫做延迟着色场景渲染器,但其实集成了包含前向渲染和延迟渲染的两种渲染路径,是PC和主机平台的默认场景渲染器。
1. FSceneRenderer创建
FSceneRenderer
-
处理和渲染场景,生成RHI层的渲染指令
-
由游戏线程的
FRendererModule::BeginRenderingViewFamily
负责创建和初始化,然后传递给渲染线程。 -
渲染线程会调用
FSceneRenderer::Render()
,渲染完返回后,会删除FSceneRenderer
的实例。也就是说,FSceneRenderer
会被每帧创建和销毁。
class UGameEngine : public UEngine
UGameEngine::Tick()
- called UGameEngine::RedrawViewports()
- called UGameViewportClient::Draw()
- called FRendererModule::BeginRenderingViewFamily()
- called UGameViewportClient::Draw()
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
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
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)[6]。所以我们一般在Unity中使用CS,还是要求shader target4.5(也就是shader model 5)