HLOD builder
UE有好几套不同的合并Mesh,Baking材质的实现,Modeling Tool里有一套,UE4时期集成Simplygon有一套, HLOD这里也有好几种不同的baking method。
甚至存在一些相同类名相同功能的类但是实现不同的...咱也不敢想咱也不敢问
Mesh HLODBuilder
UE现在用的比较多的主要有个4个HLOD Builder:
- Simplify Mesh HLODBuilder
- Merge Actors HLODBuilder
- Batch Instance HLODBuilder
- Approximate Mesh HLODBuilder
这四个类的入口有两个地方:
第一个入口在选中Actors以后点顶部菜单的Actors -> Merge Actors
可以进入。 这里可以对单独的Actor进行合并操作,并且精心调制合并的参数(实际上这个手段更适合用来做一些debug操作,毕竟一个地图几十万个Actors谁会去一个一个的选啊)
第二个入口在顶部菜单的Build->BuildHLODs
,这个是对整个地图的Actors进行Build HLOD的入口。
每个Actor可以和一个UHLODLayer
类关联上(这里可以hook引擎的代码来做一些逻辑自动将一些Actor关联到HLOD Layer上),然后引擎会根据每个HLODLayer上选择的HLOD Builder来进行Build。
法线问题其1 法线空间
最近在使用HLOD Builder的时候遇到了一个问题,HLOD Builder生成的Mesh法线不正确。
经过了一番仔细的调查,发现原因是:
- 虚幻的材质系统即支持
Tangent Space Normal
也支持World Space Normal
- HLOD Builder在烘焙材质的时候直接取原材质的Normal引脚输出的结果烘焙到一个贴图上
- 这个贴图会被设置到一个新的材质上,这个材质由我们指定
一个输出世界法线的例子(注意材质的属性上要去掉TangentSpaceNormal)
这里就会出现一个问题:
- 如果我们指定的HLOD使用的BaseMaterial是Tangent Space Normal的材质,但是正在烘焙的原始材质的引脚输出的是World Space Normal,那么烘焙出来的贴图就会是错误的。
- 反之亦然。
解决方案
经过了调查确定上面的case有问题后,那么就读代码吧。
先从UHLODBuilderMeshSimplify
看起,可以注意到最后进到了void FMeshMergeUtilities::CreateProxyMesh
,然后材质部分的是依靠MaterialBaking
这个模块。
IMaterialBakingModule& MaterialBakingModule = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
那么跳到MaterialBakingModule
的实现,可以找到FMaterialBakingModule::CreateMaterialProxy
这个类,这个类对每个材质编译了很多新的变体来获取每个引脚的输出。
跟下去我们会发现很可疑的代码:
这里虚幻看起来也意识到了这个问题,并写了一些代码来处理。如果正在编译的材质和BaseMaterial的Normal不在同一个空间,那么这里会额外插入一些语句来对齐normal。
int32 CompileNormalTransform(FMaterialCompiler* Compiler, int32 NormalInput) const
{
return bTangentSpaceNormal && !Material->bTangentSpaceNormal
? Compiler->TransformVector(MCB_World, MCB_Tangent, NormalInput) : NormalInput;
}
/** helper for CompilePropertyAndSetMaterialProperty() */
int32 CompilePropertyAndSetMaterialPropertyWithoutCast(EMaterialProperty Property, FMaterialCompiler* Compiler) const
{
if (Property == MP_EmissiveColor)
{
case MP_Normal:
case MP_Tangent:
return CompileNormalEncoding(
Compiler,
CompileNormalTransform(&ProxyCompiler, MaterialInterface->CompileProperty(&ProxyCompiler, PropertyToCompile, ForceCast_Exact_Replicate)));
既然这里有代码,就直接断点在这里跟一下为什么没有执行到正确的逻辑。
经过一番跟踪,发现这个代码的执行条件是bTangentSpaceNormal && !Material->bTangentSpaceNormal
,也就是HLOD BaseMaterial
是Tangent Space Normal
,但是正在烘焙的材质不是。 但是我这里 bTangentSpaceNormal
始终是false
。说明HLODBuilder
那边没有正确设置这个标志。
往回跟发现在FMeshMergeUtilities::CreateProxyMesh
这个调用点确实没有看到任何处理bTangentSpaceNormal
的代码,这显然是不对的,我们需要根据BaseMaterial
的Normal Space
来设置这个标志。
找到合适的地方插入了一行代码以后就立竿见影了
void FMeshMergeUtilities::CreateProxyMesh(const TArray<UStaticMeshComponent*>& InComponentsToMerge, const struct FMeshProxySettings& InMeshProxySettings, UMaterialInterface* InBaseMaterial,
UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, const FCreateProxyDelegate& InProxyCreatedDelegate, const bool bAllowAsync, const float ScreenSize) const
{
....
FMaterialData MaterialSettings;
MaterialSettings.Material = Material;
// ++ravenzhong: see class FExportMaterialProxy::CompileNormalTransform
// if our Source Components outputs world-normal
// our base material is tangent-normal
// we need to do an additional transform here
MaterialSettings.bTangentSpaceNormal = InBaseMaterial->GetMaterial()->bTangentSpaceNormal; // should we transform
//--ravenzhong
for (const FPropertyEntry& Entry : Options->Properties)
{
if (!Entry.bUseConstantValue && Material->IsPropertyActive(Entry.Property) && Entry.Property != MP_MAX)
{
MaterialSettings.PropertySizes.Add(Entry.Property, Entry.bUseCustomSize ? Entry.CustomSize : Options->TextureSize);
}
}
法线问题其2 错误的法线混合
修了上面的问题后,我在demo场景 Build Mesh总算是对了。 信心满满的去流水线重新build了一下HLOD,结果发现还是有问题。
经过了一番调查,发现同样有问题的Mesh,只要放在我们的主场景就有问题,放在我新建的关卡就没问题。而且有的材质都是好的,但是有的材质是错误的。出错误的材质都是我们自己做的材质,官方的材质都没问题。
一定是我们做的材质哪里有问题。打开材质研究了一会,发现Normal
在最后输出前经过了一个可疑的RVT Blend
有关的material function。
考虑到一个假设: 流水线Build HLOD并没有完整的编辑器视口,而是通过UnrealEditor-cmd.exe
和 -allowcommandletrendering
参数来运行的,有理由怀疑在这种case下RVT没有正常工作,导致RVT的数据是错乱的。
恰好从Simplygon的文档里看到了虚幻提供一个MaterialProxyReplace
节点,当Build
HLOD等case的时候,材质会用下面的分支,当正常游戏时,材质用上面的分支。
Material baking | Simplygon 10.4.117.0
于是我直接把未经过RVT的Normal接到MaterialProxy
输出上,重新Build HLOD,结果一切就好了。
错误的BaseColor
注意到场景里有一些Mesh的BaseColor也有问题,经过调查以后发现这些材质是Unlit
材质,TA在Unlit
引脚直接进行了一些简易的光照计算来做一些效果。
快速对节点进行二分查找以后,发现问题来自CameraVector
这个节点。
这里为了查找问题直接把CameraVector
的输出直接接到BaseColor
上,Build HLOD来看。
- Simplify的结果似乎是每个面正对着拍的结果
- Approximate的结果似乎是斜着一点拍的
材质里这种和场景有关的节点应该都不太能获得正确的结果,想了下可能有问题的节点还挺多的:
- 从外部MPC获取数据的话需要考虑MPC的值
- 和Camera / WorldPosition有关的节点
- 和Time有关的节点
碰到以上这些效果只有想办法用MaterialProxyReplace
来处理了...
Simplify Builder产生的纹理有紫色的像素
Incorrect Diffuse Textures After Baking HLOD | Epic Developer Community
Epic社区已经有人发现了这个问题,只需要去掉勾选bReuseMeshLightMapUVs
这个选项即可。
以前UE4时期一般把LightMapUV放到第二个UV上,现在UE5材质越做越复杂,一个Mehs有好多套UV都很正常.. 这个LightMapUV放在这肯定没啥用了,除非项目还在用LightMap。
Approximate Builder产生的纹理有黑斑
这个可能和Approximate Builder展纹理和Simplify Builder不同有关。 Simplify的原理大概是是编译多个变体来获取原始材质的每个引脚的输出然后渲染到RT上。 而Approximate似乎是直接起了一个场景把Mesh放在场景里渲染出来的结果输出出来的。
没仔细看源码(也看不懂,太多了)。 总而言之研究一轮以后发现,在转角或者容易被遮蔽的地方,使用Approximate Builder容易产生黑斑的纹理。
另外一个问题是Approximate Builder BuildHLOD的时候,文档虽然没写,但是实际测试下来似乎要求Mesh是水密的(watertight),如果Mesh不是水密的,容易产生大面积的黑斑纹理。
比如拿一些旗帜等比较扁平的片去用Approximate Builder,容易Build不出来或者Build出来纹理是黑的。