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代码表示,大致如下:

cpp 复制代码
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代码编译流程

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

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

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

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

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

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

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

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

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

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

参考资料

UE5.2的地形系统实现

相关推荐
UTwelve3 小时前
【UE5】使用基元数据对材质传参,从而避免新建材质实例
ue5·材质
UTwelve3 小时前
【UE5】在材质中计算模型在屏幕上的比例
ue5·材质
心怀梦想的咸鱼1 天前
UE5 第一人称射击项目学习(二)
学习·ue5
暮志未晚Webgl1 天前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
心怀梦想的咸鱼1 天前
UE5 第一人称射击项目学习(完结)
学习·ue5
暮志未晚Webgl2 天前
110. UE5 GAS RPG 实现玩家角色数据存档
java·前端·ue5
Zhichao_973 天前
【UE5】Slider控件样式
ue5
流行易逝3 天前
23.UE5删除存档
ue5
心怀梦想的咸鱼3 天前
UE5 第一人称射击项目学习(三)
学习·ue5
流行易逝3 天前
22.UE5控件切换器,存档列表,
ue5