UE地形系统材质混合实现和Shader生成分析(UE5 5.2)

前言

随着电脑和手机硬件性能越来越高,游戏越来越追求大世界,而大世界非常核心的一环是地形系统,地形系统两大构成因素:HeightMap地形网格和多材质混合,此篇文章介绍下UE4/UE5 地形的材质混合方案----基于WeightMap混合。

材质层

地形着色的基础组成,比如雪, 沙漠,岩石,绿地等等。在UE5创建材质层是材质的节点LandscapeLayerBlend节点中创建

LandscapeLayerBlend节点每个输入上游就是一个材质基础层

这里我用简单的例子,假定材质基础层都是一个float3常量颜色,比如float3(1.0, 0.2, 0.1)。

WeightMap

各个材质基础层按照一定权重混合得到最终的效果,

WeightMap就是材质权重图,里面存储了各个基础层材质混合的权重值。在UE5里,WeightMap的格式是RGBA8,可以存储四层材质层的权重值,精度为0 - 255 (对应 0.0 - 1.0的精度)。

WeightMap数量和通道使用

从之前我写的UE地形系列文章,可以知道UE地形是由一个个地形块(LandscapeComponent)组成,一个LandscapeComponent可以存在多个WeightMap, 假设存在N个WeightMap.

那么最终地块的基础材质层总数:4 * (N - 1) < MaterialLayerNum <= 4 *N.

这里之所以用“<=” 和 “>”, 而不是“= 4 * N”,是因为一张WeightMap不一定会用完所有通道。比如当前地形块刷了5种材质基础层,则该地形块就存在两张WeightMap, 第一张WeightMap RGBA都用来存储四种材质基础层的权重,第二张WeightMap的R通道用来存储第五种材质基础层的权重,剩余的GBA通道都是零权重。

材质基础层用了哪张WeightMap的哪个通道是记录在ULandscapeComponent里

WeightMap存储权重总和

不管地形块刷了多少层材质基础层,最终地形某个点的各层权重总和为1.0(255).比如说上面的地形刷了五层材质基础层,有两张WeightMap,分别为W1和W2, W1[n][n].R 代表访问W1权重图的N行N列的像素R通道值。

则存在公式:

W1[n][n].R +  W1[n][n].G + W1[n][n].B +  W1[n][n].A + W2[n][n].R  = 255(byte)

                         (注意:W2[n][n].G = W2[n][n].B = W2[n][n].A = 0)

UE5地形权重混合的HLSL代码分析

生成的具体HLSL代码

以上面的5层材质的为案例, 抓帧得到的HLSL代码(位于Material.ush文件)

大致能看到采样了WeightMap1和WeightMap2, 颜色混合了5次。

为更清楚的表示, 手写HLSL代码表示,大致如下:


float4 Weigh1 = Texture2DSample(WeightMap1, UV);
float4 Weigh2 = Texture2DSample(WeightMap2, UV);
float4 Layer1Mask = float(1.0, 0.0, 0.0, 0.0);
float4 Layer2Mask = float(0.0, 1.0, 0.0, 0.0);
float4 Layer3Mask = float(1.0, 0.0, 1.0, 0.0);
float4 Layer5Mask = float(0.0, 0.0, 0.0, 1.0);
float4 Layer6Mask = float(1.0, 0.0, 0.0, 0.0);

float3 L1 = float3(0.78437978,0.95937508,0.50223249);
float3 L2 = float3(0.98749989,0.06583356,0.17528065);
float3 L3 = float3(0.45752281,0.53502721,0.88749981);
float4 L5 = float3(0.22312574,0.97916698,0.28475177);
float4 L6 = float3(0.88749981,0.25128645,0.26541224);


float3 BaseColor = float3(0.0, 0.0, 0.0);

// five layer blend
BaseColor += dot(Layer1Mask, Weigh1) * L1;
BaseColor += dot(Layer2Mask, Weigh1) * L2;
BaseColor += dot(Layer3Mask, Weigh1) * L3;
BaseColor += dot(Layer5Mask, Weigh1) * L5;
BaseColor += dot(Layer6Mask, Weigh2) * L6;

这里dot(float4(1, 0, 0,0),A),其实就是取A的R通道, 其他类似道理。

地形材质Shader代码编译流程

从上面可以更清楚整个材质权重混合的流程. Shader代码是动态生成的,Shader代码实际的内容和地块现在使用到的实际材质基础层数量相关,比说使用三层材质层和四层材质层的Shader代码不一样。在刷UE地形的时候,刷一种未出现的新材质会引起材质编译。

编译生成流程的最终在UMaterialExpressionLandscapeLayerBlend::Compile编译Shader代码,

这里判断是否需要编译发生在ULandscapeComponent::GetCombinationMaterial

首先ULandscapeComponent的所属ALandscapeProxy里存在一个 MaterialInstanceConstantMap 材质实例管理表

这个表会缓存出现过的各种地形材质实例,MaterialInstanceConstantMap的key代表使用了哪些材质基础,并且哪个基础材质使用了哪张权重纹理,如下红圈所示:

代表了LandscapeMaterialInstanceConstant_10材质实例是在 L1. L3, L3,L7使用WeightMap0, L6使用了WeightMap1的情况下编译出来的。

如果在MaterialInstanceConstantMap找到同个Key的,就使用已有的MaterialInstance, 如果找不到就以ALandscape的母材质为基础创建新的材质实例,并且设TerrainLayerWeightParameters材质静态编译信息, 触发编译。

上面WeightMap%d和LayerMask_%s 指在Shader代码中生成了每个Layer对应的权重图和纹理通道遮罩索引,和上面的HLSL代码实现地形材质混合的简化代码大致相符合。

当然最终这个材质实例是保存到ULandscapeComponent的MaterialInstances,被UPROPERTY序列化下来。

当然最终材质编译完后,得更新地形的材质实例参数值: WeightMap和LayerMask遮罩

参考资料

UE5.2的地形系统实现

猜你喜欢

转载自blog.csdn.net/qq_29523119/article/details/134173248
5.2