unreal4渲染管线原理_unreal engine 4 渲染管线 - CSDN
  • 读过我前面一些博客的朋友对虚幻4渲染流程有了一些大致的了解之后,那么我们现在可以开始着手开始修改虚幻4引擎了·。我们有些时候需要实现某些效果,就需要动手自己改引擎了。这是customnode或者是纯material无法...


    读过我前面一些博客的朋友对虚幻4的渲染流程有了一些大致的了解之后,那么我们现在可以开始着手开始修改虚幻4引擎了·。我们有些时候需要实现某些效果,就需要动手自己改引擎了。这是customnode或者是纯material无法做到的。有些人可能就要反驳了,“我纯用材质编辑器也能实现卡通材质啊”。但是无法忽略的一点是:在材质编辑器里制作的卡通材质无法接受光影。这时候有些人就想出了骚操作,用蓝图自己实现一个光源,渲染一张depthmap的RT然后传到material里去。这种方法其实过于麻烦,这相当于你抛弃掉了虚幻4的光照系统。这是得不偿失的。

    阅读下面的内容你需要具备一下知识:
    (1)C++
    (2)对虚幻引擎及其渲染模块有一定了解,如果不了解可以先读读我之前的一些文章。
    (3)HLSL
    那么下面就以这个卡通材质为例,让我们一步步来定制自己的渲染管线。来看一下虚幻的GBuffer的数据有哪些吧:

    首先你得下载好一个源码版引擎,我使用的是4.19版引擎。也许在未来引擎会做更新,单应该变化不大。然后打开如下的文件:
    首先找到EngineTypes.h,找到EMaterialSamplerType这个枚举。这个枚举将会控制材质编辑器里的那个下展栏。然后我们加入我们的枚举成员MSM_MyShadingModel      UMETA(DisplayName="MyShadingModel")编译引擎后你将会看到如下的样子:
    你将能在材质编辑器里找到自己的ShadingModel材质的枚举类型。

    这一步只是让编辑器有了一个切换枚举,并不会让材质编辑的UI发生什么变化,比如material的节点上多出几个pin,禁用几个pin或者别的什么变化。现在我们需要激活几个pin接口,让我们能把变量“连上去”

    接下来找到Material.cpp 我们找到bool UMaterial::IsPropertyActive(EMaterialProperty InProperty)这个函数。然后做如下图所示修改
    这样就能让材质编辑器在我们切换shadingmodel枚举的时候,实时增加一个CustomData的pin接口给我们用啦。


    有了这个东西之后,我们还要让材质编辑器在切换瞬间告诉我们的shader:“我们应该用我们自定义的shader啦”。如何告诉shader应该跑我们MSM_MyShadingModel的shading分支逻辑呢?答案是使用宏。

    打开MaterialShared.cpp,找到FMaterial::SetupMaterialEnvironment(

     EShaderPlatform Platform,
     const FUniformExpressionSet& InUniformExpressionSet,
     FShaderCompilerEnvironment& OutEnvironment
     )
    这个函数我们做如下修改:添加如下代码
    case MSM_MyShadingModel: OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_MyShadingModel"),  TEXT("1")); break;


    这个字符串会被压进shader里,成为宏。

    做完这些我们就可以编译引擎了。可能会等待半小时左右。

    编译好引擎之后,我们开始对shader层进行修改。

    首先打开DefferedShadingCommon.ush文件,找到下图所示区域并做如下修改:在#define SHADINGMODELID_EYE     9后面添加#define SHADINGMODELID_MyShadingModel  10 并且把SHADINGMODELID_NUM后的数字改为11

    这个宏将作为我们shader跑哪根线路的判断依据。还是在这个文件里,翻到DefferedShadingCommon.ush的最底部,找到float3 GetShadingModelColor(uint ShadingModelID)函数,在case SHADINGMODELID_EYE: return float3(0.3f, 1.0f, 1.0f);后面添加 case SHADINGMODELID_MyShadingModel: return float3(0.4f, 0.0f, 0.8f);//紫色

    如下图所示:

    做完这个修改以后,我们就能在编辑器的ShadingModle预览模式下看到如下效果:

    你能看到我们定义的颜色已经能在debug模式下看到了。说明一切顺利!!

    下一步来到ShadingModelsMaterial.ush 找到void SetGBufferForShadingModel函数。我们在这个函数的最后面做如下修改:
    这里的MATERIAL_SHADINGMODEL_MyShadingModel是从c++层那里塞进来的,然后把这个ShadingModle的ID保存到GBuffer里作为后面管线使用的数据。这一步是给DefferedShadingCommon.ush使用的。
    完成之后我们需要打开BassPassCommon.ush,打开CustomData的写入权限

    这一步的目的是当我们的shadingmode为MyShadingModel的时候,将CustomData的信息写入GBuffer。

    然后来打DefferdLightingCommon.ush 找到GetDynamicLighting函数,我们对这个函数做如下修改:

    当然做了这个修改之后我们还需要对SurfaceShading函数再做修改:

    打开ShadingModels.ush做如下修改:

    完成这一步之后,我们还要对透明模式下的代码进行修改。打开BasePassPixelShader做如下修改:

    那么就完成了对引擎shader的定制啦!


    引擎的定制对项目还是很重要的,官方其实考虑更多的是通用和兼容,但是这往往就导致一点:用虚幻4做出来的游戏都长得差不多。不知道大家有没有发现这点。其实我对这点感受还是很明显的,现在的demo或者视频瞟一眼就知道是不是虚幻做的,因为大家的shader效果是一样的。这就会导致大量的同质化作品的出现,无法使你的项目脱颖而出。比如超龙珠斗士Z,那么精美的卡通渲染,肯定对引擎进行了大量的定制。

    2018/5/2更新
    有没有发现,我们自己激活的哪个CustomData0 这个接口的名字在你切换成Eye的时候,它会变成其他名字。鉴于强烈的强迫症,看这个CustomData0真的很不舒服,所以我们就来自己定义一下材质输入接口的名字吧:

    我们在Engine/Source/Editor/UnrealEd/Private/MaterialGraph.cpp的529行左右找到GetCustomDataPinName函数,然后做如下修改
    编译后你将会看到:

    Enjoy it!!!
    展开全文
  • http://m.manew.com/thread-46721-1-1.html 目标是通过10次左右的讲座,对图形学的基础知识和用法做一个梳理,把广大对图形图像有兴趣的爱好者带入门。 并提供演示程序,加深印象。...01.渲染管线概述和课程简介

    http://m.manew.com/thread-46721-1-1.html

    目标是通过10次左右的讲座,对图形学的基础知识和用法做一个梳理,把广大对图形图像有兴趣的爱好者带入门。


    并提供演示程序,加深印象。

    01.渲染管线概述和课程简介

    1.png 

    2.png 

    3.png 

    4.png 

    5.png 

    6.png 
    展开全文
  • 本篇继续Unreal搬山部分的渲染模块的Shader部分, 主要牵扯模块RenderCore, ShaderCore, RHI, Materia. 可能分成N篇。 (这里放入的UE的模块框) (下一篇主要是UE灯光和着色简要[ush以及对应结构,和UE代码和...

    @author: 白袍小道

    转载悄悄说明下

    随缘查看,施主开心就好

       

    说明:

    本篇继续Unreal搬山部分的渲染模块的Shader部分,

    主要牵扯模块RenderCore, ShaderCore, RHI, Materia.

    可能分成N篇。

    (这里放入的UE的模块框)

    (下一篇主要是UE灯光和着色简要[ush以及对应结构,和UE代码和DX部分],然后是巴拉巴拉)

       

    前言:

    部分算法和流程的实现原理,和细节(往往这部分会成为优化的处理口)。

    梳理UEShader的结构,底层的接入,分层。

    UE着色使用和细节部分。

    对比反观差距和原因

       

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    第一部分、渲染管线和着色

    Shading Models

    描述物体的颜色如何根据表面等因素而变化 (例如方向,视图方向,和照明)

    1、通常具有控制外观变化的属性。

    2、控制这些变化的属性而决定了变化。

       

    评估

    设计着色实现时,需要根据计算结果进行划分他们的评估频率。

    首先,确定一个给定的结果在整个绘制调用中,计算总是不变的。在这种情况下,计算应用程序可以通过GPU计算(通常在CPU上)执行吗。着色器可以用于特别昂贵的计算。结果被传递给图形API通过统一着色器输入。

       

    说明

    1、虚拟机

    a\ 目前的着色语言都是 C-likelike 的着色语言,比如 HLSL,CG 和 GLSL GLSL,其被编译成独立于机器的汇语言 语言,也称为中间( IL )。

    这些汇编语言在单独的阶段,通常是驱动中被转化成实际机器 )。

    这些汇编语言在单独的阶段,通常是驱动中被转化成实际机器 语言。这样的安排可以兼容不同硬件实现些汇编被看做是定义一个作为着色译器.

    b\ 着色语言虚拟机可以理解为一个处多种类型寄存器 和数据源、预编了一系列指令的处理器。

    2、单指令多数据( SIMD), 寄存器.

    a\ 一次DrawCall会通过API做一次(一系列)的图元绘制,从而驱使图形管线运行。[如Draw,DrawInstance, …..]

    b\ 输入,输出和交接。

    c\ Shader的语句、加载和编译。

    DX10 虚拟机

      

       

    语法树

    划分

    1、顶点着色

    发生在图元装配后

    顶点着色器(VS)阶段处理来自输入汇编器的顶点,执行每个顶点的操作,如转换、蒙皮、变形和每个顶点的光照,并编制到输出结构交付给下一个管道。

    (顶点着色器总是操作单个输入顶点并产生单个输出顶点。顶点着色器阶段必须始终处于活动状态,以便管道执行。

    如果不需要顶点修改或转换,则必须创建一个直通顶点着色器并将其设置为管道。)

       

    a\ 输入和输出:

    每个顶点着色器输入顶点可以由16个32位向量组成(每个顶点最多4个分量),每个输出顶点可以由16个32位4分量向量组成。

    所有顶点着色器都必须至少有一个输入和一个输出,可以是一个标量值。

     

    b\ vertexshader阶段可以使用输入汇编程序生成的两个系统值:VertexID和InstanceID(参见系统值和语义)。

    由于VertexID和InstanceID在顶点级别都是有意义的,并且硬件生成的ID只能被输入到理解它们的第一阶段,因此这些ID值只能被输入到vertexshader阶段。

     

    c\ 顶点着色器总是在所有顶点上运行,包括带有邻接的输入基元拓扑中的相邻顶点。

    顶点着色器执行的次数可以使用VSInvocations管道统计从CPU查询。

       

       

    2、可选镶嵌

    描述:

    Direct3D 11运行时支持三个实现镶嵌的新阶段,它将低细节细分曲面转换为GPU上的高细节原语。

    镶嵌瓷砖(或分解)高阶表面成适合渲染的结构。

    通过在硬件中实现镶嵌,图形管道可以评估较低的细节(较低的多边形计数)模型和渲染较高的细节。

    虽然可以进行软件镶嵌,但是由硬件实现的镶嵌可以生成大量的视觉细节(包括对位移映射的支持),而不需要向模型大小添加视觉细节和刷新率。

       

    概括:镶嵌使用GPU计算更详细的表面从一个由四边形斑块,三角形斑块或等值线构成的表面。

       

    HullShader:

    一个可编程的着色器阶段,它产生一个几何贴片(和贴片常量),对应于每个输入贴片(四边形、三角形或直线).

    外壳着色器(每个补加点调用一次)将定义低阶表面的输入控制点转换为构成补加的控制点。

    ID3D11Device::

    CreateHullShader

       

    ID3D11DeviceContext::HSSetShader

      

      

    Tessellator

    一个固定的函数管道阶段,它创建表示几何patch的域的抽样模式,并生成一组较小的对象(三角形、点或线)来连接这些样本

    将一个域(四边形、三边形或直线)细分为许多较小的对象(三角形、点或直线)。将uv(和可选的w)坐标和表面拓扑(归一化的坐标域)输出到域着色器阶段。

    域着色器在每个tessellator阶段点调用一次,并计算表面位置。

      

    Domain

    一个可编程着色器阶段,计算对应于每个域样本的顶点位置

    域着色器计算输出补丁中细分点的顶点位置,其中每个tesselator阶段输出点运行一次,并且对tesselators阶段输出UV坐标、船体着色器输出补丁和船体着色器输出补删常量具有只读访问权限。

    ID3D11Device::

    CreateDomainShader

      

      

     

       

       

    3Geometry Shader

    几何着色器(GS)阶段运行应用程序指定的着色器代码,将顶点作为输入,并能够在输出上生成顶点。

    (不像顶点着色器操作单个顶点,几何着色器的输入是一个完整语义的顶点(两个顶点表示线,三个顶点表示三角形,或者单个顶点表示点)。

    几何体着色器还可以将边缘相邻基元的顶点数据作为输入(对于一条直线,额外的两个顶点,对于三角形,额外的三个顶点)。

    下图显示了一个三角形和一条有相邻顶点的直线。)

       

    a\ 可以使用由IA自动生成的SV_PrimitiveID系统生成的值。

    b\ 能够输出多个顶点,形成单个选定的拓扑(可用的GS阶段输出拓扑有:tristrip、linestrip和pointlist)。

    c\ 输出可以通过流输出阶段反馈到光栅化阶段和/或内存中的顶点缓冲区。提供给内存的输出被扩展到单独的点/线/三角形列表(它们将被传递给光栅化器)。

       

    4Pixel Shader

    像素着色器阶段(PS)支持丰富的着色技术,例如逐像素光照和后期处理。

    像素着色器是一个程序,它结合了常量变量、纹理数据、每个顶点值的插值和其他数据来产生每个像素的输出

    对于一个原语覆盖的每个像素,rasterizer阶段只调用一次像素着色器,但是,可以指定一个空着色器来避免运行一个着色器。

       

    a\ 当对纹理进行多采样时,每个覆盖的像素只调用一次像素着色器,同时对每个覆盖的多采样进行深度/模板测试。通过深度/模版测试的样本将使用像素着色器输出颜色进行更新。(所以适当减少)

       

    b\ 像素着色器固有函数产生或使用屏幕空间x和y的数量导数,导数最常用的用途是计算纹理采样的详细程度计算,在各向异性滤波的情况下,选择沿各向异性轴的样本。

    通常,硬件实现同时在多个像素(例如2x2网格)上运行一个像素着色器,以便在像素着色器中计算的量的导数可以合理地近似为相邻像素中同一执行点上的值的增量。

       

    c\ 输入:

    输入将被限制(超过会截取)在16(无配置Geo着色)or 32、 32位、4个组件。

    关键词:顶点属性,插值,覆盖,抽样,

       

    d\ 输出

    像素着色器最多可以输出8 32位4分量的颜色,如果像素被丢弃,则不输出颜色。像素着色器输出寄存器组件在使用之前必须声明;每个寄存器都允许有一个不同的输出-写入掩码。

    使用depth-write-enable状态(在输出合并阶段)来控制是否将深度数据写入深度缓冲区(或使用丢弃指令丢弃该像素的数据)。

    像素着色器还可以输出可选的32位、1个组件、浮点数、深度值来进行深度测试(使用SV_Depth语义)。

    深度值在深度寄存器中输出,并替换深度测试的插值深度值(假设启用深度测试)。无法在使用固定函数深度和着色器深度之间动态变化。

    像素着色器不能输出模板值。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    第二部分、Unreal 着色器相关

    DX

    1、编译,读写

    D3DCompileFromFile(_In_ LPCWSTR pFileName,

    _In_reads_opt_(_Inexpressible_(pDefines->Name != NULL)) CONST D3D_SHADER_MACRO* pDefines,

    _In_opt_ ID3DInclude* pInclude,

    _In_ LPCSTR pEntrypoint,

    _In_ LPCSTR pTarget,

    _In_ UINT Flags1,

    _In_ UINT Flags2,

    _Out_ ID3DBlob** ppCode,

    _Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorMsgs);

    D3DCompile(_In_reads_bytes_(SrcDataSize) LPCVOID pSrcData,

    _In_ SIZE_T SrcDataSize,

    _In_opt_ LPCSTR pSourceName,

    _In_reads_opt_(_Inexpressible_(pDefines->Name != NULL)) CONST D3D_SHADER_MACRO* pDefines,

    _In_opt_ ID3DInclude* pInclude,

    _In_opt_ LPCSTR pEntrypoint,

    _In_ LPCSTR pTarget,

    _In_ UINT Flags1,

    _In_ UINT Flags2,

    _Out_ ID3DBlob** ppCode,

    _Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorMsgs);

       

    D3DCompressShaders(_In_ UINT uNumShaders,

    _In_reads_(uNumShaders) D3D_SHADER_DATA* pShaderData,

    _In_ UINT uFlags,

    _Out_ ID3DBlob** ppCompressedData)

       

    D3DReadFileToBlob(_In_ LPCWSTR pFileName,_Out_ ID3DBlob** ppContents)

    D3DWriteBlobToFile(_In_ ID3DBlob* pBlob,_In_ LPCWSTR pFileName, _In_ BOOL bOverwrite)

       

    2、创建

    CreateVertexShader(

    _In_reads_(BytecodeLength) const void *pShaderBytecode,

    _In_ SIZE_T BytecodeLength,

    _In_opt_ ID3D11ClassLinkage *pClassLinkage,

    _COM_Outptr_opt_ ID3D11VertexShader **ppVertexShader)

    CreateXXXShader类似

       

    Unreal

    UnrealShader处理包括了:ush的包含,编译得到转储的着色器usf,平台编译器编译,创建和使用(绑定)。

    1、Global Shaders

    全局shader是用于操作固定几何体(如:full screen quad)的shader,并且它不需要与materials产生交互,例如:阴影过滤、后处理。

    这种类型的shader在内存中只有一份实例.

       

    2Materia

    材质由一些控制材质如何渲染的状态和一些控制材质如何与渲染Pass shader交互的材质输入(material inputs)组成。

       

    3、Vertex Factories 和 MeshTypes

    Material必须能够赋给各种类型的Mesh Types, 这个是通过和Vertex Factories 配合完成的。 一个FVertexFactoryType表示唯一的Mesh Type。

    一个FVertexFactory的实例存有每个几何体的数据,以便支持特定的Mesh Type。

       

    引擎内Shader位于:Engine\Shaders\Private(Public)

    (详细分析和说明在下一篇)

    使用部分

       

       

    ShaderCore

    FShader、FGlobalShader、

    FShaderType, FVertexFactory

    FShaderPipeline, FShaderTarget

       

       

       

       

       

    材质的编译

       

    过程说明:

    1、生成材质的Shader代码【只是Shader文件】

    2、构建编译环境,且添加相关文件路径

    3、编译[可以异步],成功就缓存起来

    3.1 创建了一个临时引用计数指针[主要是确保操作一个引用的着色器映射[或查找]将导致这个着色器映射被删除]

    3.2 往ShaderMapsBeingCompiled添加[占个位置]

    3.3 分配唯一编译ID,配置编译环境

    3.4 迭代处理所有顶点工厂的类型,编译这个材质和顶点工厂类型组合的所有网格材质着色器。---------FVertexFactoryType

    3.5 迭代处理所有材质Shader的类型 ------FShaderType

    3.6 迭代处理所有材质Shader管线的类型 ------FShaderPipelineType

    3.7 进入映射编译着色器

       

    类关系:

       

       

    展开全文
  • 这是侑虎科技第581篇文章,感谢作者王文涛供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859) 作者主页:... 动机和目...

    这是侑虎科技第581篇文章,感谢作者王文涛供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

    作者主页:https://www.zhihu.com/people/wang-wen-tao-70,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!


    动机和目的

    之前写过一篇文章,讲的是UE4里的HISM的工作方法。

    UE4的HISM深入探究:https://zhuanlan.zhihu.com/p/58963258

    为了实现植被的遮挡剔除、Instance还有距离剔除的功能。HISM Component几乎成为了UE4里最庞大的一个文件,然而实际上HISM对于很多游戏场景里的需求还是很难满足,特别是HISM需要满足静态数据的前提,十分依赖美术的工作和参数调整,对于越来越大和高自由度的游戏世界非常难受。

    很多AAA级游戏引擎都有动态合并Instance的功能,也就是动态判断绘制的物体材质,Vertex Buffer(VB)等是否一样,一样的物体合并到一起绘制。但是对于4.21前的管线,动态物体和静态物体没法合并,然后运行时判断是否合并Instance的效率太低。

    为了解决像动态Instancing、渲染提交效率等等的问题,EPIC终于在4.22痛下杀手改动这个陈旧庞大的渲染管线代码。知乎上有很多讲UE4渲染的文章,看过一些代码的人应该也清楚,UE4.21之前的渲染数据是基于DrawingPolicy的框架,这样的框架的好处是面向对象的设计,思路很直接,但是它的缺点却是显而易见的:

    1、通过大量模板实现Shader的选择和permutation,代码难以阅读,对于现代CPU体系来说性能堪忧(非Data-oriented 设计)。

    2、分成Dynamic和Static两个draw list,Instancing/Batch等优化,只能对static物体做手脚。

    3、DrawPolicy直接决定渲染线程设置到GPU的数据,难以和GameThread数据解耦,冗余数据很多,对性能也是大问题。

     

    老的UE4一个Draw命令的数据流程

     

     

    除此之外,一些新技术的需求,这种DrawingPolicy的架构也是难以胜任的:

    1、DXR技术,Shader所需Constant等数据必须是全场景的,同时,Ray Tracing技术是不能简单地针对可见性作剔除,光线会反弹,同样需要全场景信息。

    2、GPU Driven Culling的技术,CPU必须在不知道可进性的情况下,把场景数据发送到GPU的Buffer,由GPU进行剔除计算。

    GPU-Driven Rendering Pipelines

    3、Rendering Graph或者说可编程的渲染管线技术,需要能够灵活的抽象出Render Pass等概念,而现在的DrawingPolicy代码去写一个简单的自定义pass可能就需要好几百行,太过沉重。

    总结起来就是,我们需要一种更简洁、更紧凑、更利于CPU访问的数据结构去表示场景数据,包括Transform、Material(可以理解成Shader参数)、RenderState、顶点Buffer等等。而它们最好是一个数组,并不需要包括太多Game Thread的信息。而我们的Renderer代码需要在渲染每一个Frame开始前cache全场景的数据。

    UE4给出的解决方案叫做MeshDrawCommand

    FMeshDrawCommand类缓存了一个DrawCall所需要的最少资源集合:

    • VertexBuffer和IndexBuffer
    • ShaderBindings(Constant,SRV,UAV等)
    • PipelineStateObject(VS/PS/HS/GS/CS 和 RenderState)

    FMeshDrawCommand的好处有:

    1、独立的结构体,不用依赖于DrawPolicy那一大堆模板;

    2、Data-oriented设计,在提交的时候就是一堆数组,效率高。

    基于FMeshDrawCommand的新管线:

     

     


    Caching MeshDrawCommand

    构造好PrimitiveSceneProxy之后,就可以用FMeshPassProcessor的AddMeshBatch去创建一个或者多个MeshDrawCommand。

    继承自己的FMeshPassProcessor就可以创建一个新的自定义MeshPass,创建一个MeshDrawCommand的流程如下:

     

     

    例如,阴影绘制里的DepthPass:

     

     

    注意原来UE4.21的Uniform那一套东西在MeshDrawCommand里被替换成了ShaderBinding。我们不用一个个去调用setParameters接口,而统一用SetShaderBindings的API即可。

    FMeshDrawShaderBindings是一个存放了若干个内存Buffer的数组,数组的大小和该Command用的Shader数量相同。Buffer里面的内容就是构造好的RHIUniformBuffer的refrence,包括SRV、Constantant等等,使用MeshDrawShaderBindings的好处就是可以快速比较两个DrawCommand的Uniforms是否数目一样,内容一致,从而实现DrawCall Merge。

    GetShaderBindings的例子:

    FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);
    ShaderBindings.Add(RenderOffsetParameter, ShaderElementData.RenderOffset);
    

    FMeshDrawShaderBindings里存放的是FRHIUniformBuffer结构体的指针,这是为了在update uniform时,只用更新一个UniformBuffer的内容即可,而不是更新引用到这个UniformBuffer的所有MeshDrawCommand。

     

     

    有了MeshDrawCommand,我们就可以只根据物体的动态或者静态属性区分每一帧需要更新的东西。对于场景大部分的静态物体,只需要判断Uniform是否与view相关去更新view即可,动态物体则一般需要每帧更新MeshBatch和MeshDrawCommand。


    Dynamic Instancing和GPUScene

    1、GPUScene

    上面这些Data-oriented向的优化只是提高了绘制的效率,但是要真正实现Dynamic Instancing,我们需要的一个个GPUSceneBuffer来存储PerInstance的场景数据。UE4用一个GPUTArray来表示PerInstance的数据,实际上它是个float4的数组,这样的方法在几年前Ubi的引擎里就实现过:

     

    Ubi的GPU Driven Pipeline

     

     

    只不过Ubi做的更彻底,包括Mesh信息VB IB等都全部扔到GPU去,然后用MultiDrawInstanceIndirect去绘制。UE4的GPUScene Buffer则主要包括世界矩阵、Lightmap参数、包围盒等PerInstance相关的Shader数据,4.23之后添加了一些CustomData可以用于你自定义的Instance算法。

     

     

    UE4的GPUTArray代码实现在ByteBuffer.cpp和ByteBuffer.usf中,在DirectX中用的是StructureBuffer来实现,OpenGL下则用的是TextureBuffer。

    Update/Resize一个Buffer的代码也很简单。Map一段CPU数据到GPU然后执行一个ComputeShader更新场景里已经绑定的Buffer:

    SetShaderValue(RHICmdList, ShaderRHI, ComputeShader->NumScatters, NumScatters);
    SetSRVParameter(RHICmdList, ShaderRHI, ComputeShader->ScatterBuffer, ScatterBuffer.SRV);
    SetSRVParameter(RHICmdList, ShaderRHI, ComputeShader->UploadBuffer, UploadBuffer.SRV);
    SetUAVParameter(RHICmdList, ShaderRHI, ComputeShader->DstBuffer, DstBuffer.UAV);
    RHICmdList.DispatchComputeShader(FMath::DivideAndRoundUp<uint32>(NumScatters, FScatterCopyCS::ThreadGroupSize), 1, 1);
    SetUAVParameter(RHICmdList, ShaderRHI, ComputeShader->DstBuffer, FUnorderedAccessViewRHIRef());
    

    这里UE4对GPU Buffer的代码实现是相对比较简单的,实际上对于动态更新的GPU数据可以用一个GPU RingBuffer去实现,可以节省每帧重新分配内存和map操作的时间:

     

    GPU RingBuffer示意

     

     

    2、MeshDrawCommand Merging

    有了PerInstance的数据,我们还需要知道哪些MeshDrawCommand可以merge,MeshDrawCommand可以简单的判断,去比较是否相等从而merge。

     

     

    注意因为MeshDrawShaderBindings都是指针,所以比较只要简单判断command绑定的bindings是否相等即可。

    Merge好的MeshDrawCommand存在场景里的一个HashSet里,如果两个command能merge,简单的把这个command的StateBucket+1即可:

        /** Instancing state buckets.  These are stored on the scene as they are precomputed at FPrimitiveSceneInfo::AddToScene time. */
        TSet<FMeshDrawCommandStateBucket, MeshDrawCommandKeyFuncs> CachedMeshDrawCommandStateBuckets;
    

    判断command是否能merge是比较费时的操作,因此只会在物体AddToScene的时候执行。此外,现在的MeshDrawCommand是逐个比较相等去判断能否merge的,之后也可以直接用Hash值去merge来提高效率。

    3、PrimitiveId Buffer

    UE4是用DrawIndexedInstanced最终去绘制物体的,那么我们需要按照某个物体的ID去索引GPUSceneBuffer中的内容,UE4的做法也很直接,在支持DynamicInstance的物体的VertexFactory中添加一个VertexStream作为PrimitiveId:

    if (GetType()->SupportsPrimitiveIdStream() && bCanUseGPUScene)
    {
        Elements.Add(AccessStreamComponent(FVertexStreamComponent(&GPrimitiveIdDummy, 0, 0, sizeof(uint32), VET_UInt, EVertexStreamUsage::Instancing), 13));
        PrimitiveIdStreamIndex[Index] = Elements.Last().StreamIndex;
    }
    

    每个MeshDrawCommand在构造时去初始化这个VertexBuffer即可:

    for (int32 DrawCommandIndex = 0; DrawCommandIndex < NumDrawCommands; DrawCommandIndex++)
    {
    const FVisibleMeshDrawCommand& VisibleMeshDrawCommand = VisibleMeshDrawCommands[DrawCommandIndex];
    PrimitiveIds[PrimitiveIdIndex] = VisibleMeshDrawCommand.DrawPrimitiveId;
    
    }
    

    例如场景的Scene Buffer有一千个物体的数据,我们只要知道我们的MeshDrawCommand在这一千个index中的offset,然后对command里的primitives ID依次加一即可。

    最后是SubmitMeshDrawCommands:

    RHICmdList.SetStreamSource(Stream.StreamIndex, ScenePrimitiveIdsBuffer, PrimitiveIdOffset);
    RHICmdList.SetStreamSource(Stream.StreamIndex, Stream.VertexBuffer, Stream.Offset);
    RHICmdList.DrawIndexedPrimitive(MeshDrawCommand.IndexBuffer,MeshDrawCommand.VertexParams.BaseVertexIndex,0,MeshDrawCommand.VertexParams.NumVertices,MeshDrawCommand.FirstIndex,MeshDrawCommand.NumPrimitives,MeshDrawCommand.NumInstances * InstanceFactor
    

    4、新管线的优势和发展方向

    4.22的新管线在场景复杂度较高的情况下有非常好的减少CPU overhead的效果,收益主要来自于Data-Oriented设计带来的cache miss的减少和动态Instancing实现。而且这个收益不仅限于PC端,据Epic的工程师说,在堡垒之夜上使用Depth Prepass和新的Dynamic Instancing管线,让渲染效率提升了30%左右。

    将更多的工作交给GPU来完成也是游戏引擎发展的一个趋势,下一篇计划用4.22这些新轮子去实现一些新的东西。

    参考文档:

    [1]GPU Driven pipeline
    [2]GPU Dynamic resource management


    文末,再次感谢王文涛的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

    也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!

    展开全文
  • unreal 抓mobile 管线

    2019-09-28 22:34:50
    把renderdoc挂到生成的exe上 用命令行 “路径\xx.uproject” scenename -game -FeatureLevelES31...这样能看到unreal上层管线按照mobile走的样子 但rhi那层走 pc 转载于:https://www.cnblogs.com/minggoddess/...
  • 因为虚幻的架构,我们美术做一个效果只能使用材质编辑器。这让我很不舒服,稍微想做点特别点的效果就没办法了。我又是天天在公司用自研引擎的人,这种限制让人很不舒服。所以我开始修改了虚幻的shadingmode之类的。...
  • OpenGL 渲染管线理论

    2013-08-07 07:29:32
    原文地址http://www.cppblog.com/acmiyou/archive/2009/08/23/94191.html 阶段1. 指定几何对象.  如:点 线 三角形.等一些几何图元..OpenGL绘制几何图元的方法有以下三种:  一次一个顶点.... 使用顶点数组..如
  • @author: 白袍小道 查看随意,转载随缘 ...1、渲染模块这里有个主要任务需要完成:将需要在屏幕上(或者某设备)显示出来的Primitives(几何体,或者绘制图元)输入到pipeline的下一阶段。 2、渲染的每帧,...
  • 虚幻引擎 4 渲染流程分析http://www.52vr.com/article-2104-1.html UE4渲染系统结构解析...Unreal Engine 4渲染流程总复习http://gad.qq.com/article/detail/22342 虚幻引擎 4 渲染流...
  • 随着Unity3D 2018的面世,Scriptable Rendering Pipeline,也就是可编程渲染管线这项新技术变得家喻户晓。官方在推出这项技术的时候,着重强调了他的各种优点,而笔者总结了一下官方的解释,认为SRP有以下三个优点:...
  • 想要更改引擎的渲染管线,为自定义的渲染模型添加宏,进行到设置渲染模型这一步却卡住了 ![参考代码](https://pic4.zhimg.com/80/v2-a2414e1a2f2ac128edfc3c482e0bc57b_720w.jpg) 原本在引擎的MaterialShared....
  • 本文由@浅墨_毛星云 出品,首发于知乎专栏,转载请注明出处。...它将会是这个系列文章主线的最后一篇。不知不觉中,专栏中【《Real-Time ...从2017年写到了2018年,从渲染管线、高级着色、延迟渲染,一路写到全局光照
  • 我的专栏目录:小IVan:专题概述及目录​zhuanlan.zhihu.com章节概述: 作为一个美术,一般遇到...为了加快工作效率,我觉得美术有必要能自己完成整套渲染管线的搭建,优化和游戏跨平台发布。 这章将会从零开始,在...
  • Unreal Engine 4渲染系统是无限可编辑并且支持多种平台的绝大多数的最新渲染技术。感谢节点式的编辑器,在Unreal中编写meterial是非常可行的,但是人们往往因为渲染系统的复杂性和缺乏可用的教学内容而踌躇。 ...
  • 那么下面进入正题,我们以虚幻的CableComponent为例,来深入研究一下虚幻的渲染管线的图源汇编阶段。下面分几个小节(1)CableComponent的原理(2)CableComponent在虚幻里的实现步骤代码实现(...
  • 我的专栏目录:小IVan:专题概述及目录​zhuanlan.zhihu.com简介:因为虚幻的架构,我们美术做一个效果只能使用材质编辑器。这让我很不舒服,稍微想做点特别点的效果就没办法了。我又是天天在公司用自研引擎的人,...
  • 原文地址:https://medium.com/@lordned/unreal-engine-4-rendering-overview-part-1-c47f2da65346 翻译:yarswang 转载请保留   你必须使用从GitHub下载的源码来构建引擎。你可以直接去更改Launcher版本的现有...
1 2 3 4 5 ... 20
收藏数 489
精华内容 195
关键字:

unreal4渲染管线原理