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的地形系统实现

相关推荐
我的巨剑能轻松搅动潮汐2 天前
【UE5】pmx导入UE5,套动作。(防止“气球人”现象。
ue5
windwind20002 天前
UE5 跟踪能力的简单小怪
ue5
Deveuper5 天前
UE5 C+、C++、C# 构造方法区别示例
c++·ue5·c#·ue4
windwind20005 天前
UE5 学习方法的思考
ue5·学习方法
ue星空6 天前
UE材质常用节点
ue5·虚幻·材质·虚幻引擎
Zhichao_976 天前
【UE5 C++课程系列笔记】09——多播委托的基本使用
笔记·ue5
异次元的归来8 天前
UE5的TRS矩阵
线性代数·矩阵·ue5·游戏引擎·unreal engine
电子云与长程纠缠9 天前
UE5编辑器下将RenderTarget输出为UTexture并保存
学习·ue5·编辑器·虚幻
ue星空10 天前
虚幻5描边轮廓材质
ue5·材质
ue星空12 天前
虚幻引擎生存建造系统
ue5·游戏引擎·虚幻·虚幻引擎