虚幻4渲染编程(Shader篇)【第二十三卷:MaterialShaderSystem】
baidu ue4 globalshader materialshader
虚幻4渲染编程(Shader篇)【第二十三卷:MaterialShaderSystem】
https://zhuanlan.zhihu.com/p/139984908
MY BLOG DIRECTORY:
YivanLee:专题概述及目录zhuanlan.zhihu.com
INTRODUCTION:
UE对Shader层做了一些抽象,主要分为两类,GlobalShader和MaterialShader,它们会分别存放在不同的ShaderMap内,它们的生成编译和资源组织绑定都有一定差异。前面文章大着重在使用GlobalShader,现在开始介绍MaterialShader。
MaterialShader负责单个物体的绘制这也是它和GlobalShader在用途上区分的原因之一。下面的内容主要会拆成几个部分:Shader的生成和编译,Shader的管理和使用,Shader的资源绑定和管理。
作为一个效果开发者如果对于这块如果不清楚的话就犹如是在旷野上裸奔,是一件非常危险的事情。
MAIN CONTENT:
【1】What is material
在一切开始之前我们需要对一些概念和理念进行探索。现在的游戏引擎对渲染管线的各种数据做了封装和切割,模型顶点三角形数据存放在了Mesh里,渲染模型需要的贴图,Shader以及各种其它资源数据存放在了Material里。这些数据会被处理优化归纳,最后还是会合并到一起组成一个绘制命令(MeshDrawCommand)。Material是一个对渲染管线的上层的抽象,一个Material可能要为很多Pass服务,还可能会去控制一些Pass是不是需要渲染。Material还需要管理各种渲染资源,同时还给GamePlay提供了一些图形接口。
Unity的材质系统就非常简单,基本上满足了上述材质的各个点。
Unity材质对混合模式的控制也很简单暴力,直接在UnityShaderLab里写Blend标志
UE4的材质总得来说作用如下:
(1)管理并持有需要被渲染的这个物体的特有渲染资源,如贴图,参数等等。
(2)管理并生成渲染这个物体需要的所有Shader。
(3)提供完整渲染这个物体所有pass的绘制状态混合方式。
(4)给其它系统提供一个物体渲染的总控制抽象,可以方便控制该物体的渲染。
对比Unity和UE的材质系统似乎两者之前其实最后做的事情差不多但是不同之处显而易见。
【2】Material system of unreal engine 4
UE4的材质类为UMaterial
材质编辑器也是在为它服务。它的功能总得来说就是最开始第一大点介绍的那四大功能。下面就按照上述四大功能的顺序对虚幻4的材质类进行剖析。
(1)管理并持有需要被渲染的这个物体的特有渲染资源
UE4的材质所引用到的资源全部存放在了一个叫CachedExpressionData的成员里
包括贴图纹理,UniformBuffer变量等等资源
当我们在材质编辑器里把渲染资源拖入编辑器的时候,这些渲染资源就会被Cache下来。现在既然保存了资源,那么下一步UE是如何把这些资源取出来使用的呢。
在FMaterialRenderProxy中会调用FUniformExpressionSet::CreateBufferStruct和FUniformExpressionSet::FillUniformBuffer
FUniformExpressionSet::FillUniformBuffer会把材质里的资源填入一个UniformBuffer里
以GetTextureParameter为例
进入GetTextureParameter
进入GetTextureParameterValue
再进入GetIndexedTexture
再进入GetReferencedTextures
再进入GetReferencedTextures,终于回到了UMaterial从CachedExpressionData中获取到了Texture。
获取到的资源会被填入UniformBuffer里,在构造MeshDrawCommand的时候会有ShaderBinding操作
这里会把从UMaterial中获取资源生成的UniformBuffer塞到ShaderBingds里供MeshDrawCommand使用
这里获取过程过于繁琐,下面断下这部分逻辑就能清楚看到数据被拿到了,如下图所示:
MaterialCollectionUniformBuffer资源也是在这里绑定的
再会过头来看看FUniformExpressionSet::CreateBufferStruct函数,这里你能找到为什么材质编辑器的HLSL代码的贴图名字会是写成那样:Material.Texture2D_0
全是因为下面的代码写死了,Shader代码必须要和Texture的名字对应才行。
FUniformExpressionSet::CreateBufferStruct()的调用位置在后面的文章里有介绍。
(2)管理并生成渲染这个物体需要的所有Shader
上面的那条是资源管理部分,下面就是Material对Shader的管理。某种程度上说对于可编程管线,Shader只是提供给管线的资源而已,材质还负责Shader的生成。
在UMaterial中,有一个MaterialResources的成员,它是一个二维数组,根据材质的Quality等级和对应的平台分组
这个成员内就含有渲染需要的Shader
进入到FMaterialResource类型内,会发现它是FMaterial的派生类。FMaterialResource/FMaterial就负责了材质所需要的Shader部分的生成和管理。首先看生成部分。
在Umaterial中有三个函数,它们分别是
用于资源Cooking:CacheResourceShadersForCooking
用于材质编辑和引擎运行时渲染:CacheResourceShadersForRendering
执行单个Shader生成的函数为CacheShadersForResources
它们无一例外全部会调用FMaterialResource/FMaterial 的 CacheShaders接口。
再进一步跟进CacheShaders逻辑之前,先暂时停下仔细观察一下FMaterial类型
FMaterial/FMaterialResource中有一个ShaderMap的容器,这里才是正真存放shader的地方。它们持有所有特定截面下的所有Shader的变体。影响Shader变种的因素很多,如勾选的VertexFactory的种类,是否开启阴影等等。
当触发shader编译的时候,FMaterial会调用FMaterial::BeginCompileShaderMap
首先这里会调用HLSLTranslator把材质节点翻译成代码,
然后会设置材质的Environment。
在FHLSLMaterialTranslator::GetMaterialEnvironment中会塞入各种宏
包括我们熟悉的ShadingModel
完成准备以后就会调用ShaderMap的Compile函数编译Shader
在Compile函数中还会做一些准备,这里有CreateBufferStruct和SetupMaterialEnvironment。其中CreateBufferStruct就是前面资源管理的时候为Material创建UniformBuffer的地方,SetupMaterialEnvironment还会塞入一些宏设置。
准备过程完成以后就可以开始准备Shader编译了。会根据所有VertexFactory的种类分配编译任务
分配多个Job多线程编译
最后完成编译把Shader放入ShaderMap中
(3)提供完整渲染这个物体所有pass的绘制状态混合方式
第三点就很简单了,对于Umaterial或者是FMaterial来说这就是个开关
构建MeshDrawCommand设置混合状态绘制标记的时候直接把这些接口调出来即可,例如下面这个例子。
(4)给其它系统提供一个物体渲染的总控制抽象,可以方便控制该物体的渲染
UMaterialInterface是材质的基类,UMaterial和UMaterialInstance都派生自它,UMaterial包含了大量功能,UMaterialInstance是UMaterial的一个实例,UMaterialInstance会引用UMaterial的资源而尽量减少资源拷贝。UMaterialInstance的子类UMaterialInstanceDynamic主要用于参数更新
它全是更新参数接口,它本身也是通过UMaterialInstance的接口调用FMaterialRenderProxy的接口。
FMaterialRenderProxy主要负责一个材质在渲染层的沟通
FMaterialRenderProxy在前面的材质资源管理部分已经有所接触了,它其实就是一个代理模式设计的产物。它代表的是UMaterial
SUMMARY AND OUTLOOK:
至此对虚幻的材质系统,MaterialShader有了一个整体的认知,当然其中还有很多细节,不过主要的框架已经呈现在了眼前。
Enjoy it.
NEXT:
todo...
By YivanLee 2020/5/13