UE4中内置了一些着色模型(Shading Model),基本满足了绝大部分游戏开发的需求。但一些特殊美术风格的游戏也许需要自定义着色模型,比如当下火热的二次元向游戏。本文记录了4.22+版本添加自定义着色模型的方法。
创建自定义着色模型
添加着色模型类型
在EngineTypes.h中为自定义的着色模型添加一个新的UENUM。
MaterialShader.cpp
添加一个枚举只是可以在材质编辑器看到而已,必须要定义这个着色模型。首先要设置编译这个着色器的环境。通过SetDefine()实现。SetDefine()函数的作用是为着色器添加一个属性的#define预处理器,编译着色器时就知道按照何种着色模型去编译。下面的代码就会让着色器编译器将MATERIAL_SHADINGMODEL_CEL_SHADING认作1.
HLSLMaterialTranslator.h
开启材质引脚
将会使用CustomData0和1作为Cel Bands和Outline Thickness的引脚。只需要在Material.cpp中的MP_CustomData0和MP_CustomData1之后添加开启条件。
CustomData
可以保持CustomData0或1的引脚名称,也可以自定义名字。在MaterialGraph.cpp中的GetCustomDataPinName()中添加着色模型对应的引脚名称。
另外CustomData0和1的范围是0到1,所以大于1的数值n操作最好用1/n做后续数学计算。
可以在AllocGBufferTargets()函数中看到UE4使用的是B8G8R8A8存储GBuffer的,CustomData的数据是储存在R或G通达的,8-bit小数最大值会被限制1.0。
借Refraction的引脚
修改case MP_Refraction引脚的激活条件。
但是编译材质图标节点到着色器代码时,UE会检查冗余和不持支的内容,不支持的部分就会丢弃掉,或者被默认值代替。
在HLSLMaterialTranslator.h的Translate()中,Refraction引脚只有混合模式为半透的时候才会编译,所以修改一下编译条件。
在MaterialGraph.cpp的RebuildGraph()函数中可以修改Refraction引脚名称的实现方式,用GetRefractionPinName()函数获取最终名称。
现在已经修改好所有的cpp和h文件,可以编译引擎工程。
修改着色器:BasePass和GBuffer
建立着色模型ID和颜色
在ShadingCommon.ush中定义新的SHADINGMODELID,并设置ID和颜色
为了让GBuffer输出CustomData,需要在BasePassCommon.ush中扩展输出条件
输出GBuffer数据的准备工作
ShadingModelsMaterial.ush中的SetGBufferForShadingModel()函数会设置GBuffer的数据,添加Cel着色模型的情况。
但是Refraction引脚的数据需要用GetMaterialRefraction(FPixelMaterialInputs PixelMaterialInputs),可以在BasePassPixelShader.ush中使用PixelMaterialInpus输入参数得到Refraction引脚的数据。
修改着色器:DeferredPass和Lighting
光照调整
DeferredLightPixelMain()函数在DeferredLightPixelShaders.usf中。颜色是通过GetDynamicLighting()函数实现的,该函数位于DeferredLightingCommon.ush中。首先获取阴影项,然后调用函数IntegrateBxDF()函数得到光照项。在该函数中,不同的着色模型会执行不同的BxDF(),所以可以在ShadingModels.ush的IntegratedBxDF()函数中添加CelShadingBxDF()。可以以DefaultLitBxDF()为基础修改和扩展。
阴影调整
阴影还是默认的柔和,在GetDynamicLighting()中的IntegrateBxDF()前调用了GetShadowTerms()。在计算光照颜色的时候将LightColor与LightMask和Shadow.SurfaceShadow进行了相乘。所以只需要对LightMask和Shadow.SurfaceShadow进行一步处理,得到分层的锐利阴影即可。
DeferredLightingCommon.ush
反射调整
在ReflectionEnvironmentPixelShader.ush文件中加入判断条件,如果是卡通着色模型,就转为LAB格式调整亮度再转回RGB。
描边
因为是延迟渲染,所以可以在延迟光照的步骤可以拿到深度和法线做描边。
修改代码
在DeferredLightPixelShaders.usf的DeferredLightPixelMain()中添加函数CalcSceneDepth()。SceneTexturesStruct包含了GBufferA/B/C/DTexture,场景深度就在其中。法线存在GBufferA。