1. 问题
这个项目里的道路材质不只是 Brick 和 Dirt 两种。
普通道路段使用 Mesh UV。纹理会沿着道路走。到了路口以后,道路没有唯一方向。继续用同一套 UV 容易拉伸。所以路口改用 World Projection。
这样一来,一共会出现四种状态:
- UV Brick
- UV Dirt
- World Brick
- World Dirt
问题主要出现在道路和路口连接的位置。
一条 Dirt 道路可以接 Brick 路口。也可以接 Dirt 路口。Brick 道路也一样。坐标方式还要从 Mesh UV 过渡到 World Projection。
如果只按 road_level 在 Unreal 里切材质,材质和坐标都会跳变。
所以我需要一组数据。它要同时表达:
“现在是什么材质”
“接下来是什么材质”
“现在用 UV 还是 World Projection”
“已经过渡了多少”
2. 在 Houdini 中把道路状态编码进 RGBA

我的做法是先在 Houdini 里做判断。然后把结果写进顶点色。
道路网络里已经有 road_level。
普通道路段还会读取起点和终点连接对象的等级。然后保存成:
start_roadlevel
end_roadlevel
这样一条道路知道自己的类型。也知道两端要接到什么状态。
接着沿道路生成 curveu。再结合道路长度算一个 u。
u 的意思很简单:
路口连接点 道路主体 路口连接点
u = 0 → 1 ─── 1 ─── 1 → 0
道路中间用自己的 Mesh UV 材质。靠近两端以后,权重开始交给路口对应的 World Projection 材质。
这里用道路长度来限制 Blend 区域。不是直接用整条道路的 0–1 curveu。这样长道路不会出现很宽的过渡。短道路两端的 Blend 也不会一直重叠。
最后把四种状态映射到 RGBA:
R = UV Brick
G = UV Dirt
B = World Brick
A = World Dirt
普通道路会根据 road_level、所在端点和 u 来写权重。
比如一条 Brick 道路接 Dirt 路口。过渡区域里主要就是:
R = 当前道路权重
A = 路口权重
靠近路口时,R 从 1 变成 0。A 从 0 变成 1。
路口本身更简单。路口统一使用 World Projection。
Brick Junction → B = 1
Dirt Junction → A = 1
这样道路走到路口边界时,顶点色状态会和路口一致。
Houdini 输出以后,Unreal 不再判断 Mesh 是道路还是路口。也不再查相邻道路等级。它只读 Vertex Color。
材质里还是只有 Brick 和 Dirt 两套 Material Function。每套材质各用两种坐标:
Brick + Mesh UV
Dirt + Mesh UV
Brick + World Position
Dirt + World Position
然后按 RGBA 权重做混合。
我没有把四个通道直接相加。材质会先算局部混合比例。然后用 HeightLerp 生成 Blend Alpha。再用 Blend Material Attributes 混合整套材质属性。
这样 Brick 和 Dirt 的边界不会只是直线渐变。Base Color、Normal、Roughness 也会共用同一套混合结果。
这个项目里最重要的是 Houdini 和 Unreal 之间的数据接口:
Houdini
道路拓扑
+ 道路等级
+ 连接关系
+ 空间位置
↓
RGBA Vertex Color
↓
Unreal
材质读取与混合
3. 尽量让生成端决定语义,渲染端只消费结果
这套实现主要解决一个具体问题。道路段和路口用了不同的坐标方式。道路又有 Brick 和 Dirt 两种材质。
如果把判断都放到 Unreal 材质里,材质就要重新理解道路拓扑。还要重新判断道路等级和区域类型。
我最后把这些判断留在 Houdini。
Houdini 本来就知道:
- 当前道路是什么等级;
- 一条道路两端接到哪里;
- 哪些几何属于路口;
- 当前点离连接区域有多远。
所以我直接把这些信息转成连续权重。然后写进 RGBA 顶点色。再跟着 Mesh 一起传到 Unreal。
Unreal 只需要知道四个通道代表什么。然后做材质采样和混合。
这样还有一个实际好处。以后道路生成逻辑变了,只要 RGBA 的含义不变,Unreal 材质就不用跟着改。
这个项目里我采用的原则很简单:
能在 Houdini 里确定的拓扑和语义,就尽量在 Houdini 里先确定。进入 Unreal 以后,不再重新推导。只读取已经编码好的数据。
这里的顶点色就是这个数据接口。
1. The Problem: Road Materials Use Both Material Types and Coordinate Systems
The road material in this project is not only about Brick and Dirt.
Normal road segments use Mesh UVs. The texture follows the road direction.
Intersections are different. They do not have one clear road direction. Using the same UV method can cause stretching. So intersections use World Projection.
This gives four material states:
- UV Brick
- UV Dirt
- World Brick
- World Dirt
The main problem appears where road segments connect to intersections.
A Dirt road can connect to a Brick intersection. It can also connect to a Dirt intersection. The same applies to Brick roads.
The coordinate system also needs to change from Mesh UV to World Projection.
If Unreal only switches materials by road_level, both the material type and the coordinate system can change suddenly.
So I need one set of data that can describe:
“What material is used now?”
“What material comes next?”
“Should this area use UV or World Projection?”
“How far has the transition progressed?”
2. The Solution: Encode Road States into RGBA in Houdini

I do most of the logic in Houdini first. Then I write the result into vertex colors.
The road network already has road_level.
Each road segment also reads the road level of the object connected to its start and end.
The values are stored as:
start_roadlevel
end_roadlevel
Now each road knows its own type. It also knows the material state at both ends.
Next, I generate curveu along the road. I also calculate another value called u.
The meaning of u is simple:
Intersection Road Body Intersection
u = 0 → 1 ─────── 1 ─────── 1 → 0
The middle of the road uses its own Mesh UV material.
Near both ends, the weight moves toward the World Projection material used by the connected intersection.
The blend area is controlled by road length. It does not use the full 0–1 curveu range directly.
This keeps the transition length stable on long roads. It also prevents the two blend areas from overlapping too much on short roads.
The four states are then mapped to RGBA:
R = UV Brick
G = UV Dirt
B = World Brick
A = World Dirt
A normal road writes weights based on road_level, the current end of the road, and u.
For example, a Brick road can connect to a Dirt intersection.
In the transition area:
R = current road weight
A = intersection weight
Near the intersection, R changes from 1 to 0. A changes from 0 to 1.
The intersection itself is simpler. It always uses World Projection.
Brick Junction → B = 1
Dirt Junction → A = 1
So the vertex color of the road matches the vertex color of the intersection at the connection.
After Houdini exports the mesh, Unreal does not need to detect roads and intersections again.
It does not need to check neighboring road levels.
It only reads Vertex Color.
The material still uses only two main Material Functions:
Brick + Mesh UV
Dirt + Mesh UV
Brick + World Position
Dirt + World Position
The RGBA values control the blending between them.
I do not simply add the four channels together.
The material first calculates a local blend value. It then uses HeightLerp to generate the Blend Alpha. Blend Material Attributes uses this Alpha to mix the full material data.
This makes the Brick and Dirt boundary less like a simple linear gradient.
Base Color, Normal, Roughness, and the other material properties also use the same blend result.
The main data flow is:
Houdini
Road Topology
+ Road Level
+ Connection Data
+ Spatial Position
↓
RGBA Vertex Color
↓
Unreal
Material Sampling and Blending
3. Summary: Let the Generation Side Define the Meaning
This setup solves one specific problem in this project.
Road segments and intersections use different coordinate systems. The roads also use Brick and Dirt materials.
If all of this logic stays inside the Unreal material, the material needs to understand road topology again.
It also needs to detect road levels and geometry types again.
I keep this logic in Houdini instead.
Houdini already knows:
- the current road level;
- what each end of the road connects to;
- which geometry belongs to an intersection;
- how far each point is from the connection area.
So I convert this information into continuous weights.
Then I write the weights into RGBA vertex colors.
The data is exported together with the mesh.
Unreal only needs to know what each channel means.
Then it samples and blends the materials.
This also makes later changes easier.
The road generation logic can change. The Unreal material does not need to change as long as the RGBA meaning stays the same.
The rule I use in this project is simple:
If Houdini already knows the topology and the meaning of the data, I try to define it there first. Unreal does not derive the same logic again. It only reads the encoded result.
In this setup, vertex color is simply the data interface.