|
原文链接:https://medium.com/@lordned/unreal-engine-4-rendering-overview-part-1-c47f2da65346
知乎有许多大佬已经搬运了,这里只是简单的笔记。初学ue4,可能存在诸多错误,望看到的读者大大们指正qaq
Part1 Introduction
这个部分介绍了这个教程的一些基本内容,并且推荐了一些常用的插件以及配置。
Engine Setup
下载源码编译,设置模式为“Debug Editor”而非“develop editor”。(两者差别日后查阅更多资料之后补充)
HLSL Tools for Visual Studio
"工具 -> 扩展和更新… -> 联机" 搜索“HLSL Tools for Visual Studio",下载并根据提示安装。
在"工具 -> 选项 -> 文本编辑器 -> 文件扩展名"中做如下编辑:
RenderDoc
截帧工具。在UE4里面使用的相关教程:https://blog.csdn.net/u013412391/article/details/108504585
github:https://github.com/baldurk/renderdoc
Modify ConsoleVariables.ini
"\Engine\Config"路径下。(以下变量貌似默认情况下都是设置好的)
主要关注参数:
r.ShaderDevelopmentMode = 1 :当材质缺省导致编译失败时可以重新编译。
r.Shaders.Optimize = 0 & r.Shaders.KeepDebugInfo=1 :让Unreal使用低优化编译器,保留调试信息。这样可以降低编译时间,并且保存debug信息,这样可以更好地在RenderDoc里面debug。
r.DumpShaderDebugInfo = 1 & r.DumpShaderDebugShortNames = 1 :在某些情况下很有用。DumpShaderDebugInfo会将生成HLSL写入硬盘(占用两个G的内存),但可以帮助RenderDoc调试;第二个变量可以缩短变量名称,以符合OS的最大路径长度限制。
Modify BaseEngine.ini
[DevOptions.Shaders]
bAllowCompilingThroughWorker = True & bAllowAsynchronousShaderCompiling = True, 这两个变量可以让UE4进行多线程编译所有需要的shader。如果你需要只调试某个C++ shader管线,把这两个变量设置为False会非常有用,但这会使shader的编译时间变长。
Part2 Shader and Vertex Data
Vertex Factory
Unreal使用Vertex Factory来控制数据从Vertex Shader上传到GPU中。
Shaders
- 所有Shader的基类:FShader --- FGlobalShader(只有一个实例存在) & FMaterialShader(与材质绑定)。
- FShader与FShaderResource成对出现,FShaderResource追踪一个Shader在GPU上的相关资源。如果FShader编译的结果与已经存在的结果一致,那么它们就可以共享一个FShaderResource。
- FGlobalShader////
- FMaterialShader:使用SetParamaters函数来用C++代码修改HLSL中的参数。参数的绑定是通过FShaderParameter / FShaderResourceParameter类在shader的构造函数中完成。
- FMeshMaterialShader:可以将Mesh的顶点数据引入到Shader端。(from UE4渲染代码逻辑总结(上) | 风蚀之月 (ch-wind.com))
Caching and Compilation Environments
- ShouldCache函数:如果Shader、Material、Vertex Factory全都同意某个特定组合被缓存,那么FShader才会创建这个特定组合;相反,只要有其中之一不同意,那么这个组合就会被跳过。
- Unreal会自动为一个Shader编译大量可能的组合,这个有它的好处在,但同时会生成许多无用的Shader。
- ShaderCache函数是一个可以在FShader、FMaterial、FVertexFactory中实现的静态函数。(按官方文档的示例说明,就是返回值是true时编译当前shader,否则就不编译。
FVertexFactory
- 包含顶点数据,并且被链接到顶点着色器中(Vertex Shader)。
- FLocalVertexFactory:提供从局部空间到世界空间的转换;Static Mesh、Cables、Procedual Mesh使用。
- FGPUSkinVertexFactory:用于Skeletal Mesh(需要更多的顶点数据)。
- FLandscapeVertexFactory:Landscape是基于VTF(Vertex Texture Fetch),使用高度图来修改顶点位置实现的,所以需要额外的处理。
- FParticleVertexFactoryBase:粒子系统使用。
FPrimitiveSceneProxy
- 渲染线程版本的UPrimitiveComponent(虽然我也不知道这个东西是个啥),负责维护每个Component在渲染线程上需要的数据。
- Unreal存在游戏线程和渲染线程,两者不能直接访问对方线程中的数据(除非使用一些特定的异步宏)。为了解决这个问题,Unreal在游戏线程中使用UPrimitiveComponent,通过重写CreateSceneProxy()函数来创建自己的FPrimitiveProxy类。
- UCableComponent / FCableSceneProxy:在CreateSceneProxy()函数中创建FCableSceneProxy并进行初始化,然后在SendRenderDynamicData_Concurrent()中将数据发送到渲染线程并借由SetDynamicData_RenderThread()进行数据构造。
- UImagePlateFrustrumComponent / FImagePlateFrustrumSceneProxy:由于是用于渲染2D材质,不需要VertexFactory。它仅使用渲染线程的回调函数来计算一些数据画线。FImagePlateFrustumSceneProxy只是在GetDynamicMeshElements时返回演算的结果。
Binding C++ to HLSL
- C++宏
- IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix, ShaderClass, SourceFilename, FunctionName, Frequency)
- 例如:IMPLEMENT_MATERIAL_SHADER_TYPE(,FDepthOnlyPS,TEXT("/Engine/Private/DepthOnlyPixelShader.usf"),TEXT("Main"),SF_Pixel)
- 这个宏将FDepthOnlyPS绑定到DepthOnlyPixelShader.usf中,Main是shader端的入口函数。
- Unreal使用“Frequency”来区分shader的类型:
enum EShaderFrequency {
SF_Vertex = 0,
SF_Hull = 1,
SF_Domain = 2,
SF_Pixel = 3,
SF_Geometry = 4,
SF_Compute = 5,
SF_NumFrequencies = 6,
SF_NumBits = 3,
};
- 第一个参数“TemplatePrefix”用于模板类与shader的绑定。有些情况下,宏指定一个模板类,然后使用另一个宏实例化来创建特定的实现。(例如为每个可能的lighting type创建变体)
IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass, ShaderFilename, bUsedWithmaterials, bSupportsStaticLighting, bSupportsDynamicLighting, bPrecisePrevWorldPos, bSupportsPositionOnly)
- 将C++形式的VertexFactory绑定到一个具体的HLSL文件(Shader)中。例如:
IMPLEMENT_VERTEX_FACTORY_TYPE(FLocalVertexFactory,”/Engine/Private/LocalVertexFactory.ush”,true,true,true,true,true);
- 这个宏将FLocalVertexFactory与LocalVertexFactory.ush进行关联。
- 因为MeshShader有许多实例,每一个实例会根据自己的光照类型、数据类型进行不同的绑定。shader与FVertexFactory不是一一对应的关系,使用相同渲染路径的类会在这里共用shader。
Part3 Drawing Policies
Drawing Policies
- 并不是继承于一个基类。决定哪个shader变体用于绘制物体。
FDepthDrawingPolicy
VertexShader = InMaterialResource.GetShader<TDepthOnlyVS<false> >(VertexFactory->GetType());
PixelShader = InMaterialResource.GetShader<FDepthOnlyPS>(InVertexFactory->GetType());
- 如果是用于tessellation(曲面细分)则还需要Hull Shader和Domain Shader(?)
- 使用SetSharedState()和SetMeshRenderState()来传递参数
FBasePassDrawingPolicy
- 继承模板类:template<typenameLightMapPolicyType> classTBasePassDrawingPolicy : public FBasePassDrawingPolicy
- template <ELightMapPolicyType Policy> void GetUniformBasePassShaders (...)
Drawing Policy Factory
- 检查材质或者VertexFactory的状态,然后创建正确的Drawing Policy。
FdepthDrawingPolicyFactory
void FDepthDrawingPolicyFactory::AddStaticMesh(FScene* Scene, FStaticMesh* StaticMesh)
- 获取static mesh,根据属性和设置确定对应的Drawing Policy(在这里有FDepthDrawingPolicy和FPositionOnlyDepthDrawingPolicy),然后将其加入到FScene的列表中。
- bool FDepthDrawingPolicyFactory::DrawMesh(...) / bool FDepthDrawingPolicyFactory::DrawDynamicMesh(...) / boolFDepthDrawingPolicyFactory::DrawStaticMesh(...)
- 也可以绘制mesh batch。但mesh batch不会被加入到列表中,而是通过RHI layer建立GPU状态,并带哦用其他的drawing policy来绘制mesh。
Part4 The Deferred Shading Pipeline
相比前向渲染,可以支持多光源,复杂度为n+m。n场景物体数量,m场景光源数量。
Vertex Factories
Changing Input Data
- CPU:顶点数据保存在FVertexFactory中;GPU:FVertexFactoryInput --- 来自C++端的数据输入,在不同的Shader头文件中会有不同的定义,使用方式也不同。
- 存在通用的函数被定义来处理输入数据:GetVertexFactoryIntermediates, VertexFactoryGetWorldPosition, GetMaterialVertexParameters。
Changing Output Data
- FBasePassVSOutput:可以根据是否使用Tessellation将其定义为FBasePassVSToDS或FBasePassVSToPS。(曲面细分会需要在VS和PS之间引入Hull Shader和Domain Shader两个过程)=> 使用#define,
- 这里我们可以看到FVertexFactoryInterpolants*和FBasePassInterpolants*,这些定义的数据保存了顶点插值相关的内容。
Base Pass Pixel Shader
Material Graph to HLSL
- MaterialTemplates.ush
- %s => string format。由引擎将材质编辑器中的节点填充到这里。结构体、函数中都会存在。结构体中存放材质编辑器中需要的数据,函数用于计算(调用在材质编辑器中使用到的节点)。
The &#34;Primitive&#34; Variable
在C++端用宏声明。在每个图元(primitive)在GPU上被绘制前,这些宏会定义一个由渲染器设置的结构体。
Creating the GBuffer
- GBuffer(几何缓冲区):存储关于几何的不同信息,如world normal、base color、roughness…在计算照时,Unreal会采样这些缓冲来计算最终的shading。在这之前,Unreal会通过几个步骤创建并填充GBuffer。
- GBuffer的内容时根据项目设置变化的。通常的情况是5纹理Gbuffer(5 texture GBuffer)(后缀A-E):
- GBufferA.rgb = World Normal, GbufferA.a = PerObjectGBufferData;
- GBufferB.rgba = Metallic, Specular, Roughness, ShadingModelID;
- GBufferC.rgb = BaseColor, GBufferC.a = GBufferAO;
- GBufferD = custom data;
- GBufferE = precomputed shadow factors。
- 在入口函数FPixelShaderInOut_MainPS中,先调用MaterialTemplate.ush中声明的函数来完成获取BaseColor、Metallic、Specular、AO、Roughness等值;如果使用了次表面散射,则会继续获取数据计算此表面散射的颜色,否则默认值会被设置为0;接着如果项目中启用了DBuffer,Unreal会允许DBuffer Decals(贴花)来修改GBuffer中的结果;这之后,Unreal会计算Opacity,并进行体积光照贴图的计算;最后创建FGBufferData结构,并将数据打包其中,每个FGBufferData实例代表一个像素。
Setting the GBuffer Shading Model
- SetGBufferShadingModel(...)
- 函数声明在ShadingModelsMaterial.ush中。
- 获取GBuffer的数据,并将其按照shading model的需要重新分配。
- 大部分模型只是简单地分配模型数据,而不进行修改;
- 某些模型(次表面散射等)将自定义数据通道附加到GBuffer中。
- 将ShadingModelID写入GBuffer --- 每像素存储的整数值,允许延迟传递查找每个像素稍后应当使用的shading model。
- 如果需要使用GBuffer的CustomData通道,则需要修改BasePassCommon.ush中的WRITES_CUSTOMDATA_TO_GBUFFER,如果未在此处添加shading model,自定义数据会被丢弃。
#define WRITES_CUSTOMDATA_TO_GBUFFER (USES_GBUFFER && (MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_CLEAR_COAT || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_HAIR || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE))Using the Data
Deferred Light Pixel Shader
- DeferredLightPixelShaders.usf 计算光对像素影响的地方。
- Non shadow-casting lights --- indirect lighting --- shadow casting lights
- Unreal使用类似的Shader来处理Shadow casting lights 和 non shadow-casting lights,唯一不同的是预处理阶段,Unreal会计算一张ScreenShadowMaskTexture,它是场景中阴影像素的屏幕空间表示。
- 为了计算这个,Unreal会渲染几何图形。这并不是重新渲染物体,而是对GBuffer进行采样,结合给定像素的深度,来查看它是否会出现投射光影。
Part6 Adding a New Shading Model
1、在EngineType.h中找到EMaterialShadingModel,在其中添加MSM_xxxx(在MSM_MAX前)--- 这个会反映在材质编辑器的shading model下拉菜单里。
2、给材质编辑器添加引脚:在Material.cpp中找到UMaterial :: IsPropertyActive(...),给switch中添加判断使用自定义的shading model的语句。(这个步骤仅仅只是修改了UI,需要确保着色器使用提供给这些引脚的数据)
- 修改引脚名字则可以在MaterialGraph.cpp中寻找对应的函数(命名为GetxxxPinName(...)),在其中加入判断shadingmodel以及对应名称的语句。对于Specular、Roughness等没有定义函数的,可以自己添加函数,并在文件开头处调用。
3、修改HLSL预处理器定义:MaterialShared.cpp:FMaterial::SetupMaterialEnvironment(...),修改OutEnvironment变量。在GetShadingModel的switch语句中添加MSM_xxxx的case。(4.25版本在HLSLMaterialTranslator.cpp的void FHLSLMaterialTranslator::GetMaterialEnvironment(...)中)
------------------以上c++部分修改完成,可以重新编译,但不要打开编辑器-----------------
4、更新GBuffer的ShadingModelID
- 在shader中定义关键字:DeferredShadingCommon.ush中添加关键字。并在GetShadingModelColor(...)中定义着色器颜色。
-------------------------------以下基于Deferred Shading-----------------------------------
- 开启CustomData写入的关键字(如果你之前有添加customdata的引脚):在BasePassCommon.ush中为关键字WRITES_CUSTOMDATA_TO_GBUFFER增加一个条件。然后在ShadingModelMaterial.ush的SetGBufferForShadingModel(...)中添加预处理判断,在使用自定义的shadingmodel时,将CustomData 0 写入到GBuffer.CustomData.x中。
---------------------------------以下基于Mobile Shading-----------------------------------
5、打开MobileBasePassPixelShader.usf,大部分的操作都在这个地方完成。
- 做卡渲需要做个ramp,如果在材质编辑器里只获取光照计算NdotL来采样Ramp贴图的话,无法将自阴影算进去。但mobile管线下,材质编辑器无法获取阴影的信息,因此我们只能想办法将自定义贴图采样这个步骤移到内部完成。
- 方法1:在MaterialTemplate.ush中添加相关的模板函数,并在C++中完成自动生成HLSL的工作,相关链接:Unreal向shader传入材质贴图 - 知乎 (zhihu.com)。(这位大佬还有一篇给自定义shadingmodel增加自定义引脚的文章,感觉未来可能也会用到,上面这篇文章里有附链接)
- 方法2:编译一次Shader,查看编译的HLSL文件,HLSL中会有采样贴图的步骤,寻找与需要的贴图相关的采样代码,复制到basepasspixelshader中使用。
例如:这里我想使用连在EmissiveColor上的贴图,那么我就在HLSL中找到给EmissiveColor赋值的代码行(因为通常我们在basepixelshader中使用的都是GetMaterialxxxx,而这个函数的实现就在MaterialTemplate.ush中,非常简单一般就是把http://PixelMaterialInputs.xxx给返回了,因此这里我们只要寻找http://PixelMaterialInputs.xxx就行);
然后我们发现EmissiveColor = Local1,我们往前寻找Local1的定义;
这里我们看到Local1来自Local0 + 另外一个东西(暂时不知道是干什么的,输出看了一下是个黑色的东西),而Local0就是在采样贴图,因此我们把采样贴图的这个函数Texure2DSampleBias连同底层给这张贴图赋予的ID(即Material.Texture2D_0)一起复制到basspasspixelshader中使用就行。
(我这里是为了用shadow*NoL来采样Ramp贴图。)
6、由于需要在材质编辑器中获得光照信息,但UE4 mobile端材质编辑器屏蔽使用灯光信息,无法获取光源的方向,颜色等数据。Mobile Lighting的信息是运行时生成的UniformBuffer,在MaterialTemplate.ush里包含&#34;/Engine/Generated/UniformBuffers/MobileDirectionalLight.ush&#34;可以解决Custom Expression对灯光声明引用的问题。(在custom节点里获取)
7、切换到移动端rendering level,像是上面获取光照方向的方法只能在Mobile shading中使用。在settings->Preview rendering level中切换。(注意创建工程时选移动平台) |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|