导出UE3材质研究

最近在研究如何将UE3的材质(UMaterial)导出,主要是先前在UE3项目中累计了不少材质资源,希望有办法迁移到自研引擎当中。因为自研引擎用的是纯HLSL和GLSL语法,所以理论上是可以直接转化的。因为UE3的材质编辑器自带了材质翻译成shader代码的功能,所以我们想要的所有功能其实在源码里都能找到。

有几个事情要做:

1、知道材质编辑器表达式的连接关系在代码里是怎么表示的

2、知道如何将表达式翻译成shader代码

3、知道材质实例(UMaterialInstanceConstant)中重载的参数数据是如何记录的

 

知道材质编辑器表达式的连接关系在代码里是怎么表示的

UMaterial这个类中可以看到几个数据类型是FColorMaterialInput、FScalarMaterialInput、FVectorMaterialInput的成员变量,它们其实对应材质编辑器里最左边的输入引脚。这几个数据类型都继承自FExpressionInput。我们来看下FExpressionInput,它有几个我们需要关心的成员变量,首先是class UMaterialExpression*    Expression; 它表示连接输入引脚的表达式。如下图连接Emissive的表达式是VectorParameter,那么我们从UMaterial中找到成员EmissiveColor,它的Expression就是UMaterialExpression的派生类UMaterialExpressionVectorParameter,材质编辑器里的其他表达式都能在EngineMaterialClasses.h中找到UMaterialExpression打头对应的派生类。然后我们看到下图VectorParameter的输出引脚是在R通道上的,这一信息,我们可以从FExpressionInput中找到(意味着输入引脚中记录了表达式的输出通道信息),下图使用了R通道,那么FExpressionInputMaskMaskR为TRUE,如果VectorParameter是黑色引脚输出,那么Mask为FALSE。

有些表达式不仅有输出引脚,可能还有输入引脚,例如Add,是将两个表达式相加之后输出,我们可以在UMaterialExpressionAdd类中发现它有两个FExpressionInput成员,意味着它有两个输入引脚,那么它的用法其实就跟上面说的那些步骤类似了,不再多赘述。

由于一个表达式可能对应多个输出,也就是多个表达式的输入引脚对应的是同一个表达式。意味着多个FExpressionInput可能对应同一个UMaterialExpression,它们其实对应的是同一个指针,所以我们从材质的输入引脚开始往右边遍历输入引脚的过程中,可以表达式的连接关系以及哪些UMaterialExpression已经遍历过了,来达到去重的目的。

知道如何将表达式翻译成shader代码

翻译成HLSL代码的在MaterialShared.cppFHLSLMaterialTranslatorTranslate方法中。它其实是将UMaterial的那些输入引脚,翻译成一个个代码块,然后以字符串格式化的方式注入到Engine\Shaders\MaterialTemplate.usf这个文件中去。在FHLSLMaterialTranslatorGetMaterialShaderCode可以看到格式化的方式。

FHLSLMaterialTranslator提供了基本的HLSL语法翻译函数,例如Add,可以看到它有两个INT形参,其实是FHLSLMaterialTranslator有个表将翻译过后的表达式存储下来并返回一个INT作为索引,然后在UMaterialExpression中流通,当要翻译成字符串时通过GetParameterCode将INT翻译成字符串,将字符串组合后存储起来又将INT索引返还回去。

这就是大体的思想,细节的地方比较多,这里不多做解释。

然后我们可以看到MaterialTemplate.usf只是shader的其中一部分,我猜测剩下的部分是运行时将MaterialTemplate跟其他文件拼接到一起来形成总的shader,因为在BasePassPixelShader.usf中我搜到了材质编辑器翻译的shader函数GetMaterialEmissive。对于文件拼接这部分没有考证过是不是我说的这样,如果存在误导的话请见谅。如果是我说的这样的话,其实根据BasePassPixelShader以及MaterialTemplate两部分,其实就足够还原BasePass的代码了,对于实现基本效果其实就够用了。

知道材质实例(UMaterialInstanceConstant)中重载的参数数据是如何记录的

UMaterialInstanceConstant类中经验证其实只记录了有重载的那部分数据,对于没重载的数据它其实是不记录在它的数组成员变量里的。也就是说如果没重载所有参数,那么运行时是没办法通过它的数组成员变量获取所有参数,也就拿不到材质的默认值。只能通过UMaterialGetAllVectorParameterNames、GetAllScalarParameterNames、GetAllTextureParameterNames等方法才能拿材质里的所有参数名,再通过调用GetVectorParameterValue、GetScalarParameterValue、GetTextureParameterValue等方法拿到参数重载后的值或没重载的值。

知道这些之后,我们就能将材质实例用的shader代码和它重载的数据以及资源结合了。

 

后续

因为之前没太关心材质的情况,之前只知道预编译所有材质要花上1个小时的时间,有几千个材质需要编译。后来在做这个事情的时候发现,美术在制作的时候,往往很少去关心性能,他们只关心如何实现出自己想要的效果。所以就会出现材质连得很复杂以及没有善用材质实例的习惯,经常是材质想换张图片,就直接Clone一个,然后修改TextureSample。然后就会出现大量长得差不多的材质。所以感觉创建材质的权限还是不能那么容易下放,需要先培养出良好的习惯和认知。因为之前在做粒子编辑器的时候只开放了两个通用材质给美术,基本也覆盖了大部分的效果。所以以程序的角度来看这个问题的话,其实总结归纳下其实几十个 材质基本就够用了。

之后还打算自己写一个材质编辑器,毕竟依赖虚幻的材质编辑器来输出自研引擎的shader代码姿势不是特别优美。尽管想想也知道材质编辑器就是个UI纯模拟的活,但还是要借鉴下,万一有坑呢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值