学习ue4有段时间了,准备实现一些以前看得比较有趣的效果,最近研究如何实现实时绘制War3地表效果,War3本身的绘制功能十分强大,这里就只实现了拙劣的简化版本,先来看视频效果,录屏软件有时候会占用鼠标输入,压缩的效果也不好
不同于魔兽争霸地图一个网格单位支持3种混合纹理(下图分别是草地,坑挖泥土和草色泥土一起混合),如果是手游上则可以折中为2种混合纹理减少压力同时也满足大部分MOD地图的需求
基本思路为
1.定义地图信息存储对象
对于每一个地图我们希望能存储一些信息,如地图大小,默认war3支持如下大小
可以看到war3以32为大小创建,假设我们创建一个128*128网格的地图,那么就需要一个可创建资源来保存128*128个网格的信息(如高度和地形纹理),在ue4中创建一个自定义资源比较简单
首先我们定义一个类需要继承UObject即可,用来保存每个网格的信息
然后在Editor模块继承UFactory,FactoryCreateNew直接NewObject返回就可以了,之后右键在Miscellaneous分类就可以创建一个WLevelData数据储存我们的地形信息
使用自定义资源的好处就是即使是Runtime下,资源在Editor中也可以修改,我们通过绘制功能修改自定义资源中每个单位网格存储的信息,载入并编辑地图时候读取存储信息
2.HierarchicalInstancedStaticMeshComponent仿War3每个网格单位
这里先讨论平面,因为一开始拿平面实现是最容易debug的,从内置的模型可以看出魔兽争霸每个网格单位大小128*128
所以我们需要准备几个128*128的基本单位作HierarchicalInstancedStaticMeshComponent(hismc)的mesh,这里虚幻有个坑,如果hismc包含N多Instance(如实现一个256*256地图),那么你如果你在蓝图编辑器或detail panel点击任何一个component,内存会直接爆掉,推荐隐藏该选项并且千万别点击展开detail,测试环境为4.22
然后按照128为间距添加instance即可
我们还需要按需求配置hismc的参数,比较重要的是
InstanceEndCullDistance,我在BeginPlay里把该值设为2100
第二个就是bEnableDensityScaling = 0;
3.RenderTarget存储每个网格单位的地形纹理信息,通过坐标转换采样
如果需要实现两个地形纹理混合我们需要在每个网格定义如下
我们先分析自带的岩石贴图,我们把他拆解为2个部分,mask部分和solid部分,PatternIndex就是指代每个网格索引的贴图样式,从上图就是0到15,说一下mask部分,可能大部分人第一眼看过去不知道这是什么的,举例12索引为例子,12的二进制写作1100, 1的二进制可以写作0001,按2*2排
12 | 1
11 | 00
00 | 01
其实1就对应存在,0不存在然后地形pattern按照0,1,2,3 | 4,5,6,7 | 12,13,14,15 排列
pattern如此使用,然后index则对应选择哪个纹理(如岩石,草地)
我们打开World Editor可以看到一个默认地图带的以下几个可绘制纹理
每个地形纹理占用两个数,偶数index代表mask部分,奇数index代表solid部分,如果需要其他地形纹理需要合并为一个flipbook
通过世界坐标转换到我们存储的RenderTarget uv中,这一步获取每个网格的Layer12TexIndex和PatternIndex,即上面提到的FWTerrainUnitData
然后根据这四个值进行uv偏移计算,需要注意的是如果layer2(即顶层地形纹理)的PatternIndex为0时候,其实可以省略一次LUT,war3地形贴图是针对特定相机位置制作的,这里我们用unlit材质就可以了,实现下来shader也不昂贵,百元机也可以60fps, 如果针对性能好一些的手机实现PBR等,则需要自己创建类似war3的地形纹理贴图(diffuse,normal,roughness)等
这里需要调整2个地方来降低网格边缘的缝隙效果
1.调整为合成地表贴图为NoMipmaps,否则移动时候边缘效果非常差
2.膨胀单位网格uv边缘,最早在shaderbit看到 Automated and Improved UV Dilation ,
然而我们处理这种地形只需要对uv边缘处理就可以了,我这里用的贴图是1024*1024,对应每个小块则为1024/16=64,则对clamp(uv,1.f/64, 1-1.f/64);就可以了,针对特定相机位置的RTS游戏足够了
4.绘制中的添加和删除纹理信息到RenderTarget
虚幻的蓝图自带的draw canvas to render target其实功能已经很强了,如果我们需要把FWTerrainUnitData转换为Pixel就已经够用了,然而追求绘制效率(有耐心的话可以alt+G一路看draw canvas下去)和避免连连看,还是上手cpp吧,首先如果是要实时绘制,需要在Runtime申请一个RenderTarget,如果是已经绘制完作为成品则直接在编辑器中create static texture传贴图参数就可以了
这里不得不提ue4另一个坑,在编辑器内新建的RenderTarget,当作为shader纹理参数时候,默认的sampler type是color,且无法更改, 当在电脑测试时候作为索引是没有问题的,手机上则会乱序.解决办法可以用一张默认贴图去掉sRGB当做默认值,然后游戏开始改贴图参数
游戏中创建一个索引贴图如下
注意点就是SRGB=false, LODGroup为Pixels2D,这里我们用的索引为0-15,用rgba8压缩就足够了,减少手机压力
压缩0-15索引到rgba8如下,我测试的时候发现如果是Runtime创建的RGBA8 RT,则索引为0-31在shader是可以解压正常使用的,但是0-63就出现乱序了
更新索引贴图的部分区域如下,如我们每次绘制,只需要更新2*2区域即可
添加贴图的思路
首先我们需要将触碰点的世界坐标转换到纹理uv,然后拉该网格边邻的2*2的信息,注意边界不允许绘制
每次我们增加这四个网格依次为1,2,4,8,因为这几个数对应2进制能形成基本图案
00 00
01 10
01 10
00 00
然而添加是有约束的,约束条件为如果4个网格中任意一个添加对应数量后索引>15,则绘制不成立,典型的就是0,0,0,0->1,2,4,8->2,4,8,16 可以看到16>15,则第二次绘制不成立, 如果绘制成立,我们就Flush 2*2网格部分到RenderTarget
其次当pattern index变为mask部分的15时候,我们需要递进为solid部分,就是Texture Index+=1,然后赋予其pattern index一个随机值避免纹理重复,solid部分的pattern index不是均匀的分布的,war3中大部分地形纹理用的是0,4,8,12
我们需要增加0,4,8,12相对于其他索引的比例
可以看到war3编辑器中草地带坑的pattern部分占比例非常少,我们通过随机数模拟该效果,Seed的值传入该网格的在TArray的索引来保证一致性
移除纹理的思路
移除纹理发生在当绘制纹理和底层纹理重复时候,这时候就需要减少顶层纹理pattern index 1,2,4,8, 可惜我并没找到约束规律,而是通过打表枚举所有可能的情况然后检查, 希望评论区讨论