渲染管线
渲染管线特性支持
以下内容将包含前向渲染管线中的功能支持信息。 为了方便参考,功能分为以下类别:
- 平台支持
- 灯光
- 全局光照
- 相机
平台支持
| 平台 | 前向渲染管线 |
|---|---|
| Windows | 支持 |
| Linux | 支持 |
| Android平台 | 支持 |
| XR平台 | 支持 |
| OpenHarmony | 支持 |
灯光
| 光类型 | 前向渲染管线 |
|---|---|
| 平行光 | 支持 |
| 点光源 | 支持 |
| 聚光灯 | 支持 |
| 区域光 | 支持 |
实时全局光照
| 功能 | 前向渲染管线 |
|---|---|
| VXGI | 支持 |
| 烘焙全局光照 | 不支持 |
| 屏幕空间反射 | 不支持 |
| 光照探针 | 支持 |
相机
| 功能 | 前向渲染管线 |
|---|---|
| 多显示器 | 支持 |
| 硬件动态分辨率 | 不支持 |
| 软件动态分辨率 | 支持 |
| 深度学习超级采样DLSS | 不支持 |
| AMD 超级分辨率2.0 | 不支持 |
| TAA | 支持 |
| MASS | 支持 |
| FXAA | 支持 |
| 运动矢量 | 支持 |
前向渲染管线
前向渲染是一种简单直观的渲染管线,适用于光源数量较少的场景,它的基本思路是:对于每个像素,进行一次完整的光照计算,并结合所有的光源信息来生成最终的像素颜色。前向渲染的优势在于实现简单,且能够很好地处理透明度、抗锯齿等效果。尽管如此,在光源数量增多时,性能开销较大,尤其在大规模场景中可能会遇到瓶颈。
前向渲染管线渲染流程:
- 收集场景中的可渲染物队列
- 对可渲染队列进行分类和排序
- 遍历相机列表
- 通过脏标记机制,对可渲染物队列进行视锥剔除,Hiz剔除以及层过滤剔除
- 提前深度渲染
- 渲染不透明物体队列
- 渲染天空盒(如果相机配置需要渲染天空盒)
- 渲染半透明物体队列
- 渲染UI队列
- 后处理
- 上屏显示
渲染流程图如下:
性能注意事项: 对于所有像素,动态光照会为每个受影响的像素增加渲染工作,可能导致对象在多个 pass 中被渲染。避免在性能较弱的设备(如移动端或低端 PC GPU)上使用多个光照来照射单个对象,应使用光照贴图实现静态对象的光照,而不是每帧计算其光照。
在渲染过程中,像素着色器会遍历所有光源,进行像素着色计算,最后进行混合叠加,因此尽量减少场景中不必要的光源,同时要严格控制点光源,聚光灯的影响范围避免不必要的计算。
延迟渲染管线
延迟渲染是一种现代的高效渲染管线技术,适用于光源较多、场景复杂的应用。其通过将几何渲染与光照计算分开,提高了性能,但也带来了一些挑战,如 G-buffer 的内存开销、透明物体的渲染难度等。通过优化和后处理技术,延迟渲染在现代游戏和图形应用中发挥着重要作用。
延迟渲染管线的优点: 延迟渲染特别适合光源数量多的场景。由于光照计算与几何处理分离,渲染时光源的数量对性能的影响大大降低。在每次光照计算时,只需要将所有光源的影响加到片元上,而不是为每个像素分别处理每个光源。
延迟渲染管线的缺点:
- 延迟渲染需要存储多个 G-buffer(例如颜色、法线、深度、材质等),这会增加内存的开销,尤其在高分辨率下,G-buffer 的尺寸会显著增大。
- 延迟渲染不适合处理透明物体,因为透明物体的光照计算和合成通常需要在几何阶段进行,而延迟渲染将几何信息分开处理,导致透明物体的渲染比较困难。一般需要使用前向渲染或混合技术来处理透明物体。
- 由于光照计算发生在光照阶段,阴影的计算需要额外的处理。例如,计算阴影图时,光源的深度信息必须单独处理,这使得阴影处理较为复杂。
要求: 延迟着色要求显卡具有多渲染目标 (MRT)、着色器模型 3.0(或更高版本)并支持深度渲染纹理。从 GeForce 8xxx、Radeon X2400、Intel G45 开始,2006 年以后制造的大多数 PC 显卡都支持延迟着色。所有至少运行 OpenGL ES 3.0 的移动设备都支持延迟着色。
性能注意事项: 当然,有阴影的光源比没有阴影的光源的成本高得多。在延迟着色中,对于每个阴影投射光源,仍然需要将投射阴影的游戏对象渲染一次或多次。此外,应用阴影的光照着色器的渲染开销高于禁用阴影时的渲染开销。
管线渲染流程:
- 几何阶段:
- 渲染场景中的所有几何体,并将物体的几何信息(如颜色、法线、深度等)存储到多个纹理中,这些纹理通常被称为 G-buffer(几何缓冲区)。
- 在这一阶段,所有的光源计算都被延迟,光照不会直接应用到片元上,而是将每个物体的几何信息保存下来。
- 光照阶段:
- 通过 G-buffer 中的数据,对场景中所有的光源进行光照计算,并将光照结果合成到最终的图像中。
- 每个光源都会影响所有的像素,但由于几何信息已经存储在 G-buffer 中,光照计算可以统一进行,而不需要在每个像素中进行多次计算。
- 合成阶段:
- 在这一阶段,所有的光照结果、材质属性、阴影等都被合成到最终图像中,生成屏幕上的最终渲染结果。
渲染管线流程图如下: 
自定义渲染管线
可编程渲染管线(Scriptable Render Pipeline)是一种允许开发者自定义和控制渲染过程的架构。SRP提供了一种新的方式来替代内置的渲染管线(如传统的Forward Rendering、Deferred Rendering等),使得开发者可以根据项目需求灵活地控制渲染步骤,优化性能,并实现定制化的渲染效果。 注:可编程渲染管线只有运行时生效,编辑器模式不生效。
可编程渲染管线的优点:
- 高灵活性和可定制性,SRP使开发者能够完全控制渲染过程,包括每个渲染阶段的细节,能够针对特定的需求进行高度自定义。
- 性能优化,通过SRP,开发者可以去除不必要的渲染操作,减少GPU负担,优化渲染过程。SRP的灵活性使得在不同平台和硬件上可以实现最佳的性能表现。
可编程渲染管线的缺点:
- 复杂性,开发自定义渲染管线,开发者需要掌握更复杂的渲染管线知识,会增加开发的难度,对于没有渲染管线经验的开发者,理解和编写自定义渲染管线需要更多的时间学习。
自定义渲染管线核心类:
- NPipeLineRenderer:自定义渲染管线的一个基类,开发者通过继承此类,然后重写render方法,便创建了一个自定义渲染管线实例,Render方法是自定义渲染管线的入口方法。
- NRenderPassBase:渲染pass的基类,开发者通过继承此类,重写里面的render方法,便可以得到一个渲染pass,在这个渲染pass里面可以定制渲染内容,里面封装了很多常用的渲染接口,例如清除背景颜色和深度的接口,设置gl上下文状态的接口,以及剔除接口等。
NRenderPassBase常用接口定义:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| InitResources | void | 初始化资源 |
| Render | bool | 渲染函数 |
| RenderActor | void | 渲染控件 |
| DrawRenderables | void | 渲染可渲染队列 |
| GetDefualtRenderTarget | NRenderTexturePtr | 获取场景RT |
| SetRenderTarget | void | 设置渲染RT |
| BindDefualtRenderTarget | void | 绑定场景RT |
| ClearBackground | void | 清除背景 |
| Clear | void | 清除颜色,深度,模版缓冲区 |
| SetPassName | void | 设置pass名称 |
| GetPassName | const std::string& | 获取pass名称 |
| GetDepthState | DepthState* | 获取上下文深度状态 |
| GetStencilState | StencilState* | 获取上下文模版状态 |
| GetBlendingState | BlendingState* | 获取上下文颜色混合状态 |
| GetRasterState | RasterState* | 获取上下文光栅裁剪状态 |
| SetupScissorRectAndViewport | void | 设置视口裁剪矩形 |
| MarkRenderObjectContextDirty | void | 标记渲染物脏了的接口 |
| SetNeedFrustumCull | void | 设置是否需要视锥剔除 |
- 初始化资源 virtual void InitResources()override; 描述:此接口是渲染pass进行初始化一些资源的接口,例如初始化纹理,初始化材质实例等。 代码示例:
void VolumePass::InitResources()
{
Super::InitResources();
if (!m_BackMaterial)
{
m_BackMaterial = NResources::CreateMaterial("BackRenderMaterial", "BackRenderMaterial");
}
}- 每帧渲染回调接口 virtual bool Render()override; 描述:此接口每帧都会调用,用来进行可渲染物的渲染操作。 代码示例:
bool VolumePass::Render()
{
DrawRenderables(ERenderQueueRange::RQI_Skybox);
if (!m_BackMaterial || !m_FrontMaterial || !m_VolumeDataTex)return false;
auto viewport = NCamera::GetCurrent()->GetViewport();
if (!m_CubeAct)
{
m_CubeAct = NActorManager::GetActor("Cube_1");
}
if (!m_BackFaceRT)
{
m_BackFaceRT = NResources::GetTempRenderTexture(viewport.width, viewport.height, EPixelFormat::PF_R16G16B16A16, TextureType::TT_2D, ERenderTextureContentType::RT_Default, m_RenderTextureConfig, ERenderTextureRenderMode::Direct);
}
SetRenderTarget(m_BackFaceRT);
Clear(EClearFlags::CF_Color | EClearFlags::CF_Depth, 0, 1.0f, 0);
MarkRenderObjectContextDirty();
RenderActor(m_CubeAct, EPassLightMode::EPLM_ForwardBase, m_BackMaterial);
BindDefualtRenderTarget();
m_FrontMaterial->SetShaderProperty("ExitPoints", m_BackFaceRT->GetColorSurface());
MarkRenderObjectContextDirty();
RenderActor(m_CubeAct, EPassLightMode::EPLM_ForwardBase, m_FrontMaterial);
return true;
}- 指定控件渲染 void RenderActor(NActorPtr actor, EPassLightMode lightMode= EPassLightMode::EPLM_ForwardBase, NMaterialPtr material=nullptr);
void DrawRenderables(const std::vector< NRenderableInfo>& renderables);
参数:
| 参数 | 说明 |
|---|---|
| actor | 需要渲染的控件指针 |
| lightMode | 指定渲染的pass类型 |
| material | 指定渲染控件用到的材质(如果不指定就用actor自身的材质) |
描述:此接口可以用来渲染指定Actor,并且通过参数可以指定渲染actor所用的材质。 代码示例:
bool VolumePass::Render()
{
RenderActor(m_CubeAct, EPassLightMode::EPLM_ForwardBase, m_BackMaterial);
}- 渲染指定队列 void DrawRenderables(const ERenderQueueRange& renderqueue);
参数:
| 参数 | 说明 |
|---|---|
| renderqueue | 是一个枚举类型,目前枚举定义了RQI_Opaque不透明物体,RQI_Transparent半透明物体,RQI_UI 2d UI ,RQI_Skybox天空盒。 |
描述:此方法是用来渲染指定内置队列的 代码示例:
bool VolumePass::Render()
{
DrawRenderables(ERenderQueueRange::RQI_Skybox);//渲染天空盒
return true;
}- 设置和获取RT NRenderTexturePtr GetDefualtRenderTarget();
void SetRenderTarget(NRenderTexturePtr rt);
void BindDefualtRenderTarget();
参数:
| 参数 | 说明 |
|---|---|
| rt | 要设置的RT共享指针 |
描述: GetDefualtRenderTarget接口可以获取在调用Render时,场景渲染的RT, BindDefualtRenderTarget接口用来重行邦回场景渲染的RT, SetRenderTarget接口用来设置新的渲染RT
代码示例:
bool VolumePass::Render()
{
auto viewport = NCamera::GetCurrent()->GetViewport();
if (!m_BackFaceRT)
{
m_BackFaceRT = NResources::GetTempRenderTexture(viewport.width, viewport.height, EPixelFormat::PF_R16G16B16A16, TextureType::TT_2D, ERenderTextureContentType::RT_Default, m_RenderTextureConfig, ERenderTextureRenderMode::Direct);
}
SetRenderTarget(m_BackFaceRT);
return true;
}- 清除缓冲区 void ClearBackground();
void Clear(uint32_t flag, const Color& color, float depth, int stencil);
void Clear(EClearFlags flag, const Color& color, float depth, int stencil);
参数:
| 参数 | 说明 |
|---|---|
| flag | 清除缓冲区的类型,包括颜色,深度,模版测试等 |
| color | 清除颜色缓冲区给的默认值 |
| depth | 清除深度缓冲区给的默认值 |
| stencil | 清除模版测试缓冲区给的默认值 |
描述:以上方法主要是在渲染之前清除上一帧RT缓冲区内容的接口。 代码示例:
bool VolumePass::Render()
{
auto viewport = NCamera::GetCurrent()->GetViewport();
if (!m_BackFaceRT)
{
m_BackFaceRT = NResources::GetTempRenderTexture(viewport.width, viewport.height, EPixelFormat::PF_R16G16B16A16, TextureType::TT_2D, ERenderTextureContentType::RT_Default, m_RenderTextureConfig, ERenderTextureRenderMode::Direct);
}
SetRenderTarget(m_BackFaceRT);
Clear(EClearFlags::CF_Color | EClearFlags::CF_Depth, 0, 1.0f, 0);
return true;
}- 设置视口剪裁矩形 void SetupScissorRectAndViewport(const Rectf& rect, int rtWidth, int rtHeight);
void SetupScissorRectAndViewport(uint32 x, uint32 y, uint32 width, uint32 height);
参数:
| 参数 | 说明 |
|---|---|
| rect | 矩形对象 |
| rtWidth | 视口矩形的宽 |
| rtHeight | 视口矩形的高 |
| x | 视口矩形右上角x像素偏移量 |
| y | 视口矩形右上角y像素偏移量 |
| width | 视口矩形的宽 |
| height | 视口矩形的高 |
描述:通过上面接口可以设置渲染的视口大小以及偏移量。
代码示例:
bool VolumePass::Render()
{
auto viewport = NCamera::GetCurrent()->GetViewport();
if (!m_BackFaceRT)
{
m_BackFaceRT = NResources::GetTempRenderTexture(viewport.width, viewport.height, EPixelFormat::PF_R16G16B16A16, TextureType::TT_2D, ERenderTextureContentType::RT_Default, m_RenderTextureConfig, ERenderTextureRenderMode::Direct);
}
SetRenderTarget(m_BackFaceRT);
Clear(EClearFlags::CF_Color | EClearFlags::CF_Depth, 0, 1.0f, 0);
SetupScissorRectAndViewport(0,0, viewport.width, viewport.height);
return true;
}- 渲染物的脏标记 void MarkRenderObjectContextDirty();
描述:调用此接口可以标记渲染物脏了,然后底层就会更新渲染对象进行渲染。当渲染物的材质发生改变时需要主动调用,否则渲染还是用的之前的材质。
代码示例:
bool VolumePass::Render()
{
MarkRenderObjectContextDirty();
RenderActor(m_CubeAct, EPassLightMode::EPLM_ForwardBase, m_BackMaterial);
MarkRenderObjectContextDirty();
RenderActor(m_CubeAct, EPassLightMode::EPLM_ForwardBase, m_FrontMaterial);
return true;
}- 是否需要视锥剔除 void SetNeedFrustumCull(bool isneed);
参数:
| 参数 | 说明 |
|---|---|
| isneed | 是否需要进行视锥剔除,true进行视锥剔除,默认是不进行视锥剔除 |
描述:此方法用来设置渲染时是否进行视锥剔除,默认是不进行视锥剔除。此方法只对调用指定渲染控件RenderActor的逻辑生效。而且不需要每帧调用,在初始化pass是调用一次即可。
代码示例:
void VolumePass::InitResources()
{
Super::InitResources();
SetNeedFrustumCull(true);
}NRenderPassManager:这是一个渲染pass的管理类,此类负责增删改查渲染pass.
自定义渲染管线开发示例: 1. 设置相机走自定义渲染管线
2. 通过模版创建渲染通道和自定义渲染器组件。
3. 将渲染管线类绑定到相机上
4. 在自定义渲染管线类里面创建pass以及调用pass的render函数
5. 在RenderPass里面实现自定义渲染管线具体逻辑 

