UE5 / Material
UE5 清漆与半透雨衣材质:从 UV1 接缝根因到透明物体描边
项目里做武器和角色装备材质时,集中在三个问题上:清漆质感怎么做得对、半透雨衣怎么不出排序问题、透明物体怎么描边。这三个问题看着不相关,实际串起来是一条线——都是"材质特性与引擎渲染管线的假设不匹配"。这篇按这条线记一下,包括那个查了挺久的 UV1 接缝问题。
一、清漆:UE 的 Clear Coat 是双法线模型
先搞清楚模型
很多人把清漆理解成"在普通 PBR 上再加一层高光",在 UE 里这么理解会做错。UE 的 Clear Coat shading model 是两层独立 lobe:
关键点:
| 项 | 说明 |
|---|---|
| 底层法线,走常规 PBR | |
| 清漆层法线,独立输入,通常更平滑 | |
| 清漆层的菲涅尔项,约 0.04 起,随视角上升 | |
| 能量守恒的关键:底层高光被清漆反射掉的部分要扣掉 |
最后这条最容易漏。不乘 的结果就是:清漆越亮,物体整体越亮,能量凭空多出来,在强光下会明显"过曝"。
参数
| 参数 | 作用 | 经验值 |
|---|---|---|
ClearCoat |
清漆强度 | 0–1,武器烤漆一般 0.6–1.0 |
ClearCoatRoughness |
清漆粗糙度 | 新漆 0.03–0.08,磨损处上调 |
底层 Roughness |
底漆粗糙度 | 0.3–0.6 |
两层法线分开给是清漆质感的来源:底层给细节法线(划痕、磨损),清漆层给一张很弱的大尺度起伏(或者直接用默认法线)。如果两层用同一张法线,清漆就退化成了"加了个高光",没有那种"漆面浮在图案之上"的层次。
ORN 通道打包
项目里用的是 ORN 打包(Occlusion / Roughness / Normal),法线 XY 打包在 B / A 通道:
| 通道 | 内容 |
|---|---|
| R | Ambient Occlusion |
| G | Roughness |
| B | Normal.X |
| A | Normal.Y |
两个必须注意的设置:
- 采样类型设 LinearColor(sRGB 关闭)。法线的 XY 是
-1..1的有符号数据,走 sRGB 转换会被 gamma 掰弯,法线直接废掉。 - 解包后要 Normalize。Z 是按 反解出来的,不归一化就等于把误差带进后续所有计算——这点看着基础,但正是下面那个接缝 bug 的一半根因。
// 解包:B/A → 法线
float2 nxy = Tex.rg * 2.0 - 1.0; // 假设已取到 B/A 通道
float nz = sqrt(saturate(1.0 - dot(nxy, nxy)));
float3 N = normalize(float3(nxy, nz)); // <-- 这行不能省
二、UV1 接缝:一个查了两层原因的问题
现象
武器模型在 UV1 接缝处有一条明显的光照断线。不是贴图没对齐,是明暗本身不连续——同一条边,两侧的高光位置对不上。第一反应是"UV 展开问题",但检查 UV1 是连续的。
第一层原因:解包函数末尾缺 Normalize
查材质函数 MF_UnPackNormal,末尾确实没有 Normalize。补上之后接缝变淡了,但没消失。
这说明:缺 Normalize 是放大器,不是根因。它让法线误差累积,但不产生接缝本身。
第二层原因:硬边导致的切线空间断裂
继续往下挖才找到真正的根因:模型在接缝处是硬边(split normal / 拆点)。
逻辑链是这样:
硬边 → 同一个位置拆成两个顶点,各自带不同法线
→ 两侧的切线空间(TBN)基准不同
→ 同一张切线空间法线贴图,在两侧解出的世界法线不一样
→ 高光位置突变 → 肉眼看到的"接缝"
这是几何层面的固有矛盾,不是材质参数能修好的。硬边是模型拓扑的有意选择(保住硬朗的转折),而切线空间法线贴图要求连续的切线空间——两者在硬边处天然冲突。
能缓解到什么程度
按"能做 / 不能做"分开:
| 手段 | 效果 | 代价 |
|---|---|---|
| 解包后 Normalize | 消除误差累积,接缝变淡 | 几乎为 0,必做 |
| 接缝处切线空间平滑 | 缓解明显 | 需要在建模侧处理 |
| 用 MikkTSpace 统一切线算法 | 保证 DCC 与引擎一致 | 需统一烘焙/导入流程 |
| 完全消除 | 做不到 | 硬边 + 切线空间法线是固有冲突 |
最后一条要跟美术说清楚:UV 接缝 + 硬边 = 固有接缝,只能缓解,无法消除。这句话不是推卸责任,是物理事实。如果接缝位置很显眼,正确的解法是把接缝挪到不显眼的地方,而不是继续调材质参数。
5 秒验证:临时把材质切到 World Space Normal 显示(或者直接用无贴图的纯色 + 单一光源),如果接缝还在,就是几何/切线问题;如果消失,才是贴图问题。
三、半透雨衣:别直接上 Translucent
雨衣要的是"半透但看得见轮廓"的塑料/橡胶感。直接用 Translucent 会有三个老问题:
| 问题 | 原因 |
|---|---|
| 排序错误 | 半透明按物体中心排序,自身穿插时必然出错 |
| 不写深度 | 后续效果(DOF、描边)拿不到深度 |
| overdraw | 层数一多,移动端带宽直接爆 |
项目里的取舍是用 Dithered Opacity(抖动透明)替代真半透:
// Dithered Opacity:用屏幕空间抖动图案做二值化
// 配合 TAA 时,抖动在时间上被平均掉,看起来就是半透
float dither = DitherPattern(ScreenPosition.xy); // 如 Bayer 4x4 / Interleaved Gradient
clip(Opacity - dither);
| 方案 | 排序 | 深度 | 成本 | 适用 |
|---|---|---|---|---|
| Translucent | 容易错 | 不写 | 高(overdraw) | 必须真混合的场合 |
| Masked + Dither | 无问题 | 写 | 低 | 雨衣这类薄壳,首选 |
| Thin Translucent | 一般 | 不写 | 中 | 需要真实折射时 |
代价是需要 TAA 才能把抖动"抹平",关掉 TAA 会看到明显的网点。项目有 TAA,所以这笔交易划算。
四、透明物体的高亮描边
透明物体描边难在一点:描边方案几乎都依赖深度,而透明物体不写深度。
方案对比
| 方案 | 原理 | 透明物体可用 | 成本 |
|---|---|---|---|
| 背面外扩(Backface Outline) | 第二遍画放大的背面 | 可用 | 一次额外 draw |
| 屏幕空间深度差 | 相邻像素深度突变处描边 | 不可用(无深度) | — |
| Fresnel Rim | 视角掠射处提亮 | 可用 | 几乎为 0 |
| CustomDepth + 后处理 | 单独渲一遍深度再描 | 可用 | 一次额外 pass |
落地:Fresnel Rim 为主
对半透雨衣,最后用的是 Fresnel——因为它便宜,而且"边缘发光"本身就是想要的效果:
// Fresnel 描边
float3 V = normalize(CameraVector);
float fres = pow(1.0 - saturate(dot(N, V)), FresnelPower);
// 只在需要时高亮:Highlight 0..1 由游戏逻辑驱动
float3 outline = OutlineColor * fres * Highlight * OutlineIntensity;
// 加到自发光上,不要加到漫反射
EmissiveColor += outline;
要点:
- 加在 Emissive 而不是 BaseColor/Diffuse——描边是"发光",混进漫反射会被光照二次调制,暗处就看不见了。
FresnelPower控制描边粗细,3–5 比较自然;再大就只剩一圈硬边。Highlight做成参数,游戏逻辑(选中、受击、可拾取)直接驱动,不用切材质。
需要精确描边时
如果需求是"精确的外轮廓线"(不是发光感),Fresnel 就不够用了,改用 CustomDepth + 后处理:
- 物体勾
Render CustomDepth Pass,单独渲一遍深度 - 后处理里对 CustomDepth 做边缘检测
- 描边结果叠加回画面
好处是描边不受材质透明与否影响,坏处是多一个 pass,且描边是屏幕空间的,粗细会随距离变化。
性能预算
这套材质在移动端/主机上都跑过,几个值得记的数:
| 项 | 预算参考 |
|---|---|
| 清漆材质指令数 | 比默认 Lit 多约 15–25%,双法线是主要增量 |
| 半透明层数 | 控制在 3 层以内,超过就考虑 dithered |
| 描边 | Fresnel 方案几乎免费;CustomDepth 方案多一个 pass |
| 纹理采样 | ORN 打包后 3 张变 1 张,采样数省 2 |
清漆的开销主要来自双法线——每个像素算两遍 lobe。如果项目里清漆材质铺得很开,值得考虑在低 LOD 上降级成普通 PBR(LOD 切换材质)。
小结
这三个问题串起来其实是同一件事:引擎的渲染管线有一套默认假设(深度是连续的、法线是归一化的、切线空间是连续的),一旦你的资产或材质违反其中某一条,就会在看起来完全不相干的地方出问题。
所以排查这类 bug 的顺序我固定是:
- 先几何后材质——顶点焊接、UV 接缝、硬边切线空间,这些没排查干净之前不要动材质参数
- 确认数据本身对不对(通道、sRGB、归一化)
- 最后才是调参数
第 2 步和第 3 步最容易搞反。参数能调出"看起来还行"的效果,但根因还在,换个光照条件就原形毕露。