技术美术个人笔记写到第十六篇,这一篇聊聊大地形。确切地说,是从Houdini生成地形,再送进UE的完整生产链路。这几乎是我入行以来被问得最多的一个问题,也是我前前后后跑了几个月才固定下来的一套流程。今天把关键步骤、参数逻辑和踩过的坑一次性捋清楚。
所谓大地形,我指的是单张地图动辄4km、8km甚至16km见方的项目。在这个规模下,引擎自带的地形编辑工具不是不能用,而是效率低到让人崩溃——任何一次修改都可能要重新手刷一两个小时,程序化生成、真实地形数据导入、快速迭代这些需求更是无从谈起。这时候,Houdini的HeightField体系和UE的Landscape系统就成了最搭的一对。
这篇笔记默认你对Houdini的HeightField基础节点已经有一定概念。如果还没用过,建议先拿HeightField Noise、Mask、Erode玩一玩,再回来看整条管线。这里聚焦的是从地形生成、数据导出到UE导入、材质分配这一段完整链路,以及我在链路上踩过的每一个坑。
1. 大地形的生产痛点:为什么地形工作流绕不开Houdini
1.1 引擎自带地形工具的极限在哪里
UE的Landscape工具在中小场景里其实够用。几个人在一个关卡里手刷、手雕、手画权重,配合Gaea或者World Machine导出来的Heightmap,做一个几百米见方的竞技场完全没问题。但一旦进入大地形项目,问题会成倍放大:
第一是迭代速度。策划说“这片山谷要往东挪两百米”,美术得在Landscape里用雕刻笔一点点重刷,刷完还要重新画材质权重。一个区域修改半小时,放到16km的地图里就是海量工作量。更难受的是,手刷的修改没有版本概念,想回到昨天的版本基本不可能。
第二是程序化能力缺失。真实地形有大量可归纳的特征:山脊线、冲沟、河谷、阶地,这些都能用噪声和侵蚀算法模拟出来。UE的Landscape工具虽然提供了基本雕刻笔刷,但没有侵蚀模拟、没有程序化噪声叠加、没有基于坡度/高度的Mask生成,这些正是Houdini的强项。
第三是数据兼容和可复用性差。真实项目的DEM数据、卫星影像、GIS数据经常要进地形,UE原生对这类数据的处理能力很弱。而且每次开新关卡都要重新刷一遍,同样的地形规则没法横向迁移。
而Houdini把这些问题都解决了:HeightField是一套专门为地形设计的程序化框架,地质特征可以用节点组合出来,mask可以精确控制,同一个节点树改个seed就能生成完全不同的版本,配合HDA封装还能让策划在UE里直接调参。
1.2 Houdini在大型地形生产中的核心位置
Houdini的HeightField本质上是一个2.5D的网格:XY平面保持规则的分辨率,高度存储在Z方向,还可以叠加多层mask。这个数据模型和UE的Landscape惊人地一致——Landscape也是Heightmap加多层Weightmap的组合。数据模型的天然匹配,是这两套工具能够打通的底层原因。
在大型地形生产里,Houdini通常扮演三个角色:
- 程序化地形生成器:用分形噪声叠加大型山体、山脉起伏、小型植被基底,用Mask控制特征区的分布,再用Erosion模拟水流冲刷。
- 地形数据加工厂:把一个大的Heightfield裁切成若干tile,重采样到UE需要的分辨率,批量导出Heightmap和Mask,打包坐标信息。
- 美术工具集成器:把常用流程封装成HDA,暴露尺寸、分辨率、seed、侵蚀强度等参数,让团队里非Houdini背景的人也能用。
这也决定了后面的生产流程:Houdini负责“长”出地形,UE负责“装”地形,中间的搬运工是各种数据格式。把这条链路理顺了,大地形就不再是美术的噩梦,而是一条可以反复使用的工业化产线。
2. 三条导出通道怎么选:HeightMap、网格与Houdini Engine
从Houdini到UE的地形数据传递,通道并不只有一条。我把常用的三条路放在一起对比,分别适用不同场景。
| 通道 | 数据内容 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| HeightField导出为图像 | 高度图 + Mask | 大地形最终交付 | 稳定、精准、分辨率可做大 | 只适用于规则高度场数据 |
| 网格导出 | 静态网格、碰撞代理 | 悬崖、洞穴、局部岩石结构 | 通用性强,不局限于Landscape | 大范围精度差、面数爆炸 |
| Houdini Engine | HDA实时同步 | 编辑期迭代、中小分辨率 | 参数可调、双向更新 | 高分辨率同步慢,依赖插件版本 |
2.1 HeightField导出:最稳妥、最可控的大地形通道
如果你要交付的是一张完整的、最终进游戏的地形,HeightField导出图片是最推荐的通道。原因很简单:UE的Landscape系统就是为Heightmap设计的,数据语义完全对应。
在Houdini中,HeightField节点树生成完毕后,通过HeightField Output节点可以直接输出16bit PNG或者RAW。UE的Landscape模式里选择Import Heightmap,选中图片,填好Scale,一张2km、4km甚至更大的地形就进来了。整个过程可控、可重复,也方便做批量处理。
关于位深度,这里必须强调:导出时务必用16bit,不要偷懒用8bit。8bit的灰度图只有256个高度等级,地形导入后会像梯田一样一层一层地出现台阶感,尤其在地势平缓的区域特别明显。16bit有65536个等级,绝大多数项目足够用了。如果你追求极致的精度,Houdini也支持导出32bit的RAW或者EXR,但UE对这类格式的支持要看具体版本,我建议先跑通16bit的流程,再按需升级。
2.2 网格导出:何时才能派上用场
网格导出适合那些“骨架”不是地形的部分:一面需要单独建造的悬崖、一个洞穴入口的岩石结构、一条需要精确碰撞控制的桥梁基座。这类区域如果还用Landscape去雕刻、刷权重,效率很低,不如直接在Houdini中用网格建模或VDB雕刻,然后导出FBX到UE。
网格通道在大地形流程里是辅助角色。我的做法是:大面积的基础地形全部走HeightField,地形上特殊的局部结构单独走网格。这样既有Landscape的资源流送和权重绘画优势,又保留了Houdini处理复杂几何体的能力。
不过要注意,网格导出的地形受限于顶点密度,拉远了看没问题,走近了面数不够会有明显的轮廓感。所以网格地形往往要配合法线贴图或者材质层面的细节增强,多数情况不承担大范围的最终地形任务。
2.3 Houdini Engine实时同步:适合什么,不适合什么
Houdini Engine for Unreal是SideFX官方插件,可以在UE里直接加载HDA,把Houdini里的HeightField作为Actor生成出来,同时暴露参数面板,改参数后点击Cook就能更新。这个工具在美术迭代期非常爽——策划或者场景美术不动Houdini,就能调整地形大小、噪声seed、侵蚀强度。
但它的短板也很明显:大地形高度图同步非常慢。我在项目里试过用Houdini Engine直接生成一张4097×4097的地形,Cook时间加上数据回传,编辑器能卡几分钟,体验很糟糕。所以我的建议非常明确:
Houdini Engine适合作为工具链和预览通道,不建议用它做最终大地形的数据交付。大分辨率地形老老实实走HeightMap导入流程。
实际操作中,我会在HDA里暴露一个“预览分辨率”参数,默认设成513×513,让策划在UE里调地形大形,确定方向后再切到4097以上输出Heightmap,走导入通道。
3. 实操:从HeightField到UE Landscape的完整工作流
3.1 Houdini侧的地形生成与分辨率设置
在Houdini里生成地形,起步是一块HeightField。这里我以一个2km×2km的项目为例:
- 创建一个HeightField节点,Sizes设成2000×2000(单位是米),Rows和Columns设成2041×2041。
- 为什么是2041而不是2049?因为UE的Landscape对高度图分辨率有严格规则。我常驻的UE配置是:Subsection Size为255,Sections Per Component为2×2,Components为4×4。这种情况下,每个方向的总quads数 = 255 × 2 × 4 = 2040,最终高度图必须等于2040 + 1 = 2041像素。这个数字是在UE侧反推出来的,照着填就不会出现导入时分辨率不匹配的问题。
如果你用的是其他Section配置,先按这个公式反推分辨率:HeightmapSize = SubsectionSize × SectionsPerComponent × ComponentCount + 1。不要凭感觉填,否则导入的时候UE要么拒绝,要么给你自动裁切,一裁切边界就出问题。
地形生成网络我常用的组合是:三层HeightField Noise叠加,第一层控制大尺度山体轮廓,第二层做中等尺度起伏,第三层加高频噪点增加地表细节。然后接一个HeightField Mask By Slope生成坡度信息,再用Mask控制后面的Erode在陡峭区域加强侵蚀、平缓区域保留沉积。侵蚀节点计算量很大,建议先在低分辨率下(比如513×513)把主要沟壑结构跑出来,确认大形满意后再用HeightField Resample提升到2041×2041,这样能省下大量等待时间。
3.2 导出HeightMap时的关键参数
在HeightField节点树的最末端接入HeightField Output节点。这个节点的核心参数:
- Export Mode:选择Height and Masks,这样不仅能导出高度图,还能把需要传递到UE的材质遮罩一并导出。
- File Type:推荐PNG,位深选16bit。
- Filename Prefix:设置输出路径,建议放到项目的Content目录外的一个临时文件夹,UE导入后可以再清理。不要直接输出到UE的Content目录,避免引擎自动扫描到大量临时文件。
导出之前,还有一个非常重要的步骤:高度范围确认。Houdini的HeightField高度值默认可能有负值,下限不一定是0。如果直接导出,UE导入时很容易出现地形整体抬高或者压扁的情况。我的习惯是先用HeightField Fit节点把高度范围映射到一个已知区间,比如-100到900米,这样导出后我在UE里只需要按这个范围配置Scale,不会出现意外偏移。
导出后,在输出文件夹里应该能看到一张16bit的灰度PNG,以及若干张Mask灰度图。灰度图的命名要清晰地标注是哪一层,后面导入UE的时候才不会搞混。
3.3 UE侧导入与Landscape基础配置
打开UE,进入Landscape模式,选择Manage,点击Import。弹出面板里选中刚才导出的Heightmap PNG,进入Import面板后重点设置几个参数:
- Scale:X和Y通常保持默认或根据实际尺寸微调。Z是高度缩放,必须根据地形高低差调整。UE默认的Scale=100含义是高度图的0~255等级映射到约256m的高度。如果你的地形高低差是500m,Z Scale就要相应调大。
- Material:可以暂时不指定,进入关卡后再给Landscape指定地形材质。
- Layer:如果你的Heightmap同时携带了材质分层信息,可以在这里绑定对应的Layer Info。
导入后第一件事不是看效果,而是验证方向。我建议在Houdini里生成地形之前,先把某一块区域做成不对称的形状——比如在东南角放一个高台。导入UE后,立刻能发现地形有没有发生90度旋转,或者X轴镜像。方向有问题就回Houdini里调整HeightField的Transform,而不是在UE里旋转Actor,因为旋转Actor在后续多地块拼接时会埋下坐标隐患。
方向验证完毕后,再检查Scale和高度是否正常。如果地形看起来像一张纸片,大概率是Z Scale设小了;如果高得像摩天大楼,就是Z Scale太大了。
4. 分块、尺度与坐标:大地形不出错的三个关键
4.1 为什么大地形必须分块
一张16km的地图如果用单个Landscape承载,引擎的浮点精度会先出问题。UE的传统坐标系统在距离原点超过10km之后,顶点位置开始出现肉眼可见的抖动。虽然UE5.1开始引入了Large World Coordinates(LWC),大大缓解了这个问题,但出于性能流送和团队协作的考虑,分块依然是大地形生产的主流做法。
分块还关系到数据流送。UE5的World Partition会把世界拆成网格单元,按玩家位置动态加载卸载,Landscape如果是一整块,就没有办法按区域卸载。分块后,每个Landscape Actor都作为独立单元参与流送,内存占用和加载压力都会平滑很多。
另外,协作也不允许“一张大图”的存在。多个关卡美术同时编辑同一张地形,如果没有分块,版本冲突会让人崩溃。分块后每个人负责各自的tile,合入时只要保证边界坐标对齐就行。
4.2 在Houdini里分块还是到UE里拼接
我强烈建议:在Houdini里先把整片地形“长”出来,再做分块导出,而不是每个tile单独生成。
原因很简单:单块生成时,相邻tile的山脊线、河流走向、侵蚀沟壑很难保证连续性。你在tile A里生成了一道山谷,tile B的相应位置却是一座山梁,接缝处就成了灾难。解决这种问题的成本远比一次性生成再裁切要高。
具体做法:
- 在Houdini里生成整片16km的地形,先低分辨率建立大形。
- 用HeightField Crop按块裁切,每个tile保留重叠区域。比如2km的tile可以裁成2041×2041,边缘多留几个像素的过渡。
- 每个tile单独导出Heightmap和Mask,同时记录它的世界坐标偏移,保存到一个CSV或者简单的文本表格里。
如果你的项目里有工具向的TA,强烈建议用Houdini Python写一个批量导出脚本,遍历所有tile,统一导出Heightmap和Mask,并把坐标信息自动写入CSV。CSV的内容大概长这样:
tile_name, origin_cm_x, origin_cm_y tile_00_00, 0, 0 tile_00_01, 0, 200000 tile_01_00, 200000, 0这里坐标用UE的厘米单位,2km就是200000cm。在UE里导入每个tile后,把Landscape Actor的Location设置成对应的origin坐标,拼起来就严丝合缝。
4.3 坐标映射与World Partition的配合
UE5项目里,World Partition是大世界地形的主流方案。Landscape在World Partition中作为Spatial Hash网格的一部分,可以自动按区域加载卸载。
手动导入的多块Landscape,进入World Partition后要注意Bounds设置。Landscape的Bounds默认根据Component数量计算,不需要手动调整,但如果你用了分块裁切且有重叠区,要让相邻Landscape之间保留一层“壳”,否则远距离观察时地块边缘会因为加载时机不同出现跳变。
如果项目还在用UE4,没有World Partition,那就用Level Streaming + Origin Rebasing的方案。Origin Rebasing是指当玩家移动到一定距离后,整个世界的坐标做一次原点平移,避免浮点抖动。这套方案下,所有Landscape相对位置必须是动态可移动的,所以你在Houdini里的tile坐标只能作为初始位置,不能写死到代码里。
实操建议:分块方案要在项目启动时就定下来,而不是等地形做了一半再拆。Landscape的Section/Component配置、分辨率、tile大小、重叠宽度,这些参数会影响后面所有关卡建设,越早固定越省事。
5. 材质层与Mask传递:保证地形特征不丢
5.1 从Mask生成到权重贴图导出
地形的高度信息只是骨架,真正决定视觉质量的是材质层的混合效果。草地、岩石、泥土、雪地这些区域如何分布,要靠Mask来控制。Houdini里生成Mask的常用方式:
- Mask By Slope:坡度大的区域划分给岩石或裸露地表,平缓区域划分给草地泥土。
- Mask By Height:高海拔区域做雪地,低海拔区域做植被。
- Mask Noise:为Mask增加噪声扰动,让区域边界更自然,避免生硬的“油漆分界线”。
- Mask Erode / Blur:让Mask过渡更柔和,模拟自然侵蚀。
这些Mask要传递到UE的Landscape Layer中去,通常做法是导出为16bit灰度图,在UE里为每个Layer导入对应的Weightmap。分辨率选择上,建议权重图只做到地形Heightmap的1/4或1/8。举个例子:地形Heightmap是2041×2041,权重图可以导出512×512或1024×1024。权重图分辨率太低会导致材质过渡糊成一团,太高则内存和性能开销会指数增长,1/4通常是个平衡点,不够再用1/2。
5.2 UE里Landscape Layer与Material的衔接
导入权重图的操作路径是:Landscape模式 > Paint > Manage,在Layer列表里新建Layer Info,对应到之前导入的权重图资源。如果有多层权重,需要保证它们的Layer Name和你在Houdini里导出的Mask命名一一对应,不然到了材质阶段会出现关联不上、权重全黑或全白的情况。
地形材质方面,核心节点是LandscapeLayerBlend。把草地、岩石、雪地各自的材质接入Blend节点的对应槽位,Layer Name填上和Landscape Layer一致的名称,材质就会根据权重自动混合。如果地形对细节要求高,还可以用LandscapeLayerWeight节点单独取某一层的权重,控制这层材质的法线强度、粗糙度变化、甚至做视差偏移。
在大地形项目中,我强烈建议材质里不要直接用大的细节纹理平铺,至少使用两层细节:一层是2k~4k的中频纹理,另一层是重复度较高的高频纹理,用Mask混合。否则远景和近景都会出现明显的纹理拉伸或过度重复感。
5.3 把整套流程封装成HDA
如果团队里要反复用这套地形工作流,建议把Houdini侧的地形生成网络封装成HDA。HDA里可以暴露这些常用参数:
- 地图尺寸(Size X/Y)
- Heightmap分辨率(Rows/Columns)
- 分块数量(Tiles X/Y)
- 噪声Seed
- 侵蚀强度
- Mask输出分辨率
- 高度范围(Min/Max)
这样策划在UE里加载HDA,就能直观调参数,调完低分辨率预览,觉得没问题再切高分辨率输出。Houdini Engine在这里扮演的是“外部调试面板”的角色,而不是最终数据通道。
封装HDA还有一个隐性好处:团队里不懂Houdini的人也能参与地形前期探索。TA定义好参数范围和使用规范后,场景美术可以自己在地形编辑器里快速做多方案对比,不用每次拉着TA改节点。
6. 我在实际项目中遇到的坑和解决办法
6.1 方向不对:Houdini坐标和UE坐标的90度之谜
我第一次用Houdini导入UE时,地形整整转了90度。查了半天资料,发现是Houdini HeightField的坐标系和UE导入Heightmap时的UV映射方向有差异。最靠谱的解决方式是在Houdini里给HeightField加一个Transform节点,绕Z轴旋转90度(或-90度,取决于你的场景),然后再导出。
但这里有个陷阱:如果你已经分好了tile并设置了世界坐标,旋转操作要在裁切之前做。等裁切之后再旋转,tile之间的相对位置关系会全部乱掉,坐标CSV也要全部重算。
验证方向的小技巧:在Houdini地形生成的最初阶段,故意在东南角做一个非常显眼的尖峰或高台。导入UE后如果尖峰出现在西北角,说明Y轴翻转了;如果出现在东北角,说明旋转了90度。用这个“路标”来验证方向,比盯着地形轮廓猜测要高效得多。
6.2 比例不对:从米到厘米的换算陷阱
Houdini的默认场景单位是米,UE的默认单位是厘米。这个比例关系如果没有处理好,地形会出现两种极端情况:一种是导入后地形只有二十米长,缩成一团;另一种是地形巨大到相机在内部穿行都找不到边界。
X/Y方向的Scale相对简单:如果Houdini地形宽度是2000米,高度图分辨率对应的quads数是2040个,那么UE里每个quad的间距就是2000/2040≈0.98米,即约98厘米。所以Scale X/Y设置为98或更精确的98.0392,才能在UE里得到精确的2000米。如果对尺寸精度要求不高,默认100带来的2%误差在大多数游戏地图里看不出太大区别,但如果地形需要严格对齐真实世界坐标或对接另一个系统,就必须精确计算。
Z方向的Scale更关键。我在前文提过,UE默认Z Scale=100时,16bit高度图的总高差大致映射到65535×某系数的范围。实操中我一般不在数学上纠结,而是先导入一版,观察地形最高点和最低点,对比Houdini里预期的高度范围,然后按比例修正Z Scale重新导入。地形整体悬空或者埋入地下,通常就是导入时Min/Max设置或者Fit节点没处理干净。
6.3 接缝与精度:多个Landscape拼接的问题
多块Landscape拼接,最常遇到的问题是两个相邻地块之间出现一条细缝或高度差。排查顺序一般是:
- 第一,确认所有Landscape的Section Setup完全一致。Section尺寸、Sections Per Component、Component数量都要相同,不同配置会导致边缘顶点采样方式不一致。
- 第二,确认在Houdini里裁切时所有tile使用的是同一套坐标网格。用HeightField Crop裁切时,裁切边界必须严格落在采样点上,不能出现半个像素的偏移。
- 第三,检查导入时的方向是否完全一致。如果某一个tile在UE里被手动旋转过,哪怕只是0.01度,边界也会出现肉眼可见的断裂。
还有一个非常隐蔽的坑是压缩设置。导入高度图时,UE可能会对Heightmap进行压缩处理,在边缘产生块状脏点。解决方式是在导入时选择不压缩或者使用无损格式,建议在纹理资源设置里把压缩设置改成“保留原始数据”,PC平台通常没问题,主机平台再针对平台做适配。
6.4 版本兼容与工具链建议
Houdini和UE都是版本更新很快的工具。Houdini主版本升级后,旧的HDA不一定能直接打开,某些节点API会变。UE的Landscape系统在UE5之后也有不少改动,特别是World Partition和地形材质的配合方式。
我的建议是:一个项目周期内锁定一套版本组合,不要中途随意升级。当前我比较稳定的组合是Houdini 20.0 + UE 5.3,配合对应版本的Houdini Engine。如果你在UE5.1以上的LWC环境下工作,材质的World Position相关节点需要重新验证,因为坐标系统的精度变了,有些老材质的距离效果会出现异常。
工具链维护上,强烈建议写一份简单的“配方文档”存在项目Wiki里,记录以下信息:
- Houdini版本和UE版本
- Heightmap分辨率与Section配置的对应关系
- Scale换算的公式和常用值
- 导出格式和位深度
- 方向修正的规则
这看起来像是一个很小的动作,但在多人团队里能避免大量重复踩坑。我见过不止一次,新同学接手地形工作流,因为不知道方向修正规则,又踩了一遍90度旋转的坑。
这段流程稳定下来之后的一些体会
这套流程前前后后跑了三个多月才算固定下来。现在每次开新地块,我会先花半天时间把Houdini侧的地形节点树和导出路径跑通,再开始做内容。方向、比例、分辨率这些基础项没定好,后面所有美术资源的返工成本都是几何级增长。另外一个值得投入的方向是把整个链路做成半自动化,Houdini侧批量分块导出,UE侧用编辑器脚本批量导入并设置坐标,这个脚本写在编辑器里会省下大量重复劳动。
笔记先记到这里。下一篇我大概率会写Houdini的地形Mask在UE的Runtime Virtual Texture里做混合优化的方案,正好是这条管线的下游环节。如果你也在折腾Houdini到UE的大地形,欢迎一起交流。