
简介UE5 视差材质是一套面向虚幻引擎5材质学习者的进阶素材包围绕高度图驱动的视差贴图技术解决平面纹理缺乏立体深度、场景表面细节不足的问题适用于游戏开发、建筑可视化与影视场景搭建等需要高真实感表面的项目。压缩包共478个文件约918.09MB以uasset材质与贴图资产为主体辅以umap示例关卡、ini配置、dat测试数据及少量ush、uproject工程文件可直接导入或对照工程结构研究九宫格与单图两种视差实现。已有1127人学习下载说明该方案在材质学习者中有一定参考价值。内容涵盖高度图、视差偏移、视差贴图UV、法线贴图与切线空间等核心概念的落地组织并保留测试数据与缓存文件便于读者观察资源依赖、对比不同精度贴图的表现进而掌握材质节点连接与参数调优思路提升表面细节与场景沉浸感。1. 视差材质到底在省什么一张平面贴图换出砖缝深度一面砖墙如果用几何堆出每块砖的厚度几千个面改成一张高度图加一个平面走近了砖缝有深度侧看砖块边缘会互相遮挡这就是 UE5 视差材质在做的事。虚幻5材质系统里这套东西分三档Bump Offset 做单次 UV 偏移Parallax Mapping 做多层近似POMParallax Occlusion Mapping做步进采样加遮挡判断。它们都在像素着色阶段起作用模型面数一点没变改变的是每个像素采哪里的 UV。适合用在建筑室内、地砖、砖石墙、鹅卵石地面、浮雕面板这类“表面细节多、剪影不需要变化”的地方尤其是项目被几何预算和 Nanite 面数卡住的时候。反过来需要真实轮廓、需要和角色做物理交互、需要特写镜头怼脸看的物件别指望它。这份资料把三档做法拆成了可对照的材质示例下面按“能跑起来”的顺序拆。2. Bump Offset 打底把 UV 偏移量算成一个可调参数先从最便宜的一档开始。很多人一上来就抄 POM结果连高度图该怎么导入都没搞明白最后调出来的效果像贴图在蠕动。Bump Offset 只有一次额外采样节点少、开销低正好用来把“高度图到底怎么参与 UV 计算”这件事弄透。这一章的所有连线在移动端也能跑做完之后再往 POM 走你会清楚每一步到底多花了什么。2.1 BumpOffset 节点里的三个参数各管什么BumpOffset 节点的核心就三个输入Height、HeightRatio、ReferencePlane输出一组偏移后的 UV。它的计算逻辑是拿视线方向在切线空间里的 XY 分量除以 Z 分量得到一个“斜视程度”再乘以高度值和比例系数把 UV 沿这个方向推开一段距离。HeightRatio 决定推开的长度默认 0.05砖墙我一般从 0.03 起调地砖 0.02浮雕可以到 0.1。数值越大立体感越强但拉花的边界也更早出现。ReferencePlane 决定“哪个高度算不动的平面”默认 0.5。高度图里值等于 0.5 的地方 UV 不偏移高于它的往一边推低于它的往另一边推。如果你发现整张贴图在移动而不是“凸起在动”多半是这里没对上高度图的分布。Height 输入接高度图采样。注意高度图必须是灰度信息不能当颜色贴图处理这个坑在第 5 章专门讲。调参的顺序建议是先把 HeightRatio 归零确认颜色、法线都正常再把 HeightRatio 拉到 0.05 看方向对不对最后微调 ReferencePlane 让图案的“基准面”落在你想要的位置。2.2 从 TexCoord 到采样一张地砖材质的完整连线下面这套连线是地砖材质的模板砖墙、石板、木地板都是同一套改参数。步骤写成可以直接照抄的形式。第一步建一个材质Material Domain 保持 SurfaceBlend Mode 用 OpaqueShading Model 用 Default Lit。视差材质不要用 Unlit否则法线贴图白给。第二步放一个 TexCoord 节点平铺次数设成你要的值比如 (4, 4)。这个 TexCoord 后面要同时喂给颜色贴图、法线贴图和高度图三者必须共用同一套 UV 和平铺参数这一点是后面少踩坑的关键。第三步放一张高度图Texture Sample 的 UV 接 BumpOffset 的 Height 输入前先过一层Desaturation或者直接取 R 通道保证送进去的是标量而不是带颜色的值。第四步BumpOffset 的 Coordinate 输入接第二步那个 TexCoordHeightRatio 给 0.03ReferencePlane 给 0.5输出的 UVs 分别接到颜色贴图和法线贴图的 UV 上。节点结构大致是这样TexCoord(4,4) ──┬── BumpOffset.Coordinate │ HeightMap.R ────┴── BumpOffset.Height BumpOffset.HeightRatio 0.03 BumpOffset.ReferencePlane 0.5 │ └── UVs ── ColorMap.UVs └── NormalMap.UVs第五步法线贴图采样后接 Normal 输入颜色贴图接 Base ColorRoughness 用一个常量或者再采一张 Mask 图。到这一步在视口里转动相机应该能看到砖缝跟着视角有轻微的位移感。这里有个容易忽略的细节BumpOffset 输出的 UV 可能超出 0 到 1 的范围。如果贴图是单张不重复的比如一块浮雕面板要把 Texture Sample 的 Sampler Source 改成 Clamp或者对 UV 做一次 Saturate否则边缘会采到对侧的内容形成一条明显的接缝。如果是平铺贴图Wrap 模式下越界采到的是隔壁 tile接缝反而不明显。2.3 什么时候该停手Bump Offset 的三个失效边界Bump Offset 只有一次偏移它假设“每个像素只对应一个高度”没有遮挡判断。这个前提一旦破了效果就会露馅。有三条边界值得记住。第一条斜视角超过大约 45 度。视线越斜偏移量越大原本被砖块挡住的缝隙会被拉到砖面上形成明显的 UV 拉伸。这时候再怎么加 HeightRatio 都救不回来只能上 POM。第二条高低差大于平铺单元宽度的一半。比如砖块很高、缝隙很宽一次偏移根本走不到“被挡住”的位置视觉上就是贴图在滑动而不是几何在遮挡。第三条需要接收或投射明显阴影的表面。Bump Offset 只改 UV不改深度光照仍然认为这是一个平面所以砖块不会在缝隙里投下阴影PDO 也没法用。碰到这三条中的任意一条就该换方案了。反过来说如果你的场景是远景地砖、天花板、走廊尽头那种观众不会凑近也不会大角度斜看的地方Bump Offset 是性价比最高的选择一次采样换来的立体感足够了没必要上步进。3. ParallaxOcclusionMapping 节点步进数、高度比与参考面怎么定引擎里自带的ParallaxOcclusionMapping材质函数是大多数人真正落地时会用的东西。它把步进、遮挡判断、线性插值都封装好了参数暴露得也算克制。这一章讲清楚它的内部在干什么、每个参数怎么给以及那个经常被忽略的 Pixel Depth Offset 输出到底接不接。3.1 POM 节点比 Bump Offset 多做了什么Bump Offset 是“根据高度一次性把 UV 推开”POM 是“沿着视线方向一步一步往里走每走一步采一次高度图直到踩到实地”。举个直观的例子你站在砖墙前面斜着看视线穿过砖缝的上边缘时实际打到的是缝隙内部很深的位置。Bump Offset 只会把这个像素往一个方向推固定的距离推多了就穿到砖块背面去了POM 会一层层往下试探找到第一个“高度值大于当前扫描层”的点再在前后两层之间插值所以它能表现出“砖块边缘挡住缝隙内部”的效果。代价也很直接每一步都要采一次高度图。步进 16 次就是每个像素 16 次额外纹理采样。这也是为什么 POM 通常只在近景开、远景要衰减掉。函数的输出有两组Parallax UVs 和 Pixel Depth Offset。前者接给颜色、法线、粗糙度贴图的 UV后者是一个标量接到材质主节点的 Pixel Depth Offset 输入上。3.2 Min Steps / Max Steps / Height Ratio 的取值方法这三个参数直接决定效果和开销给一组我常用的起始值。参数推荐起始值调整方向影响Height Ratio0.03 ~ 0.08越大越立体超过 0.1 容易拉花整体视差强度Min Steps4远景低于这个值会抖远处的最小采样层数Max Steps16近景糊就加到 24别超 32近处的最大采样层数Reference Plane0.5图案整体偏移时微调零位移基准面Min Steps 和 Max Steps 之间函数会根据视距做一个插值离得远用 Min离得近用 Max。这个机制本身就是在帮你做性能分级所以别把 Min 设成和 Max 一样那等于放弃了自动降级。Height Ratio 的取值和你的高度图对比度强相关。如果高度图的灰度集中在 0.3 到 0.7 之间实际有效高度只有 0.4你给 0.05 的 Height Ratio 实际效果只有 0.02。这种情况要么把高度图在图像软件里重新拉伸对比度要么把 Height Ratio 翻倍补回来。判断方法在第 6 章给了一段脚本。还有一点步进数不是越高越好。16 步到 24 步之间画面提升已经很小了但采样次数线性上涨。我见过有人为了“更精细”直接给到 64结果整个场景的 Base Pass 时间翻倍而截图对比几乎看不出差别。这就是典型的用 GPU 时间买心理安慰。3.3 Pixel Depth Offset 输出接不接差别在哪Pixel Depth OffsetPDO的作用是把“视差计算出来的虚拟表面高度”写进深度缓冲让引擎认为这个像素的深度比平面更靠前或更靠后。接与不接差别在三个地方。一是与其它物体的穿插。不接 PDO 的时候一个角色站在视差地砖上脚会和地砖出现一条硬边因为深度缓冲里地砖还是个平面角色的一部分被判定在地砖后面。接了 PDO角色脚部会被正确地“埋”进砖缝里。二是屏幕空间效果。Lumen 的屏幕追踪、SSR 反射、SSAO 这些依赖深度的效果不接 PDO 就会按平面算砖缝里不会有暗角反射也不会被砖块挡住。三是阴影。这里要小心PDO 会影响阴影贴图的深度写入如果偏移方向和阴影贴图的偏移方向配合不好会出现自阴影闪烁shadow acne。常见的处理是给 PDO 乘一个小于 1 的系数比如 0.7或者配合 Dither Temporal AA 节点做一个渐变减弱硬边。移动端和前向渲染下 PDO 的支持情况不一样有些平台写进去也不生效甚至带来额外开销。所以我的习惯是PC 上开移动端先关掉测一版确认没有穿插问题再决定。还有一处联动PDO 输出的是“偏移量”不是“最终深度”。所以你调 Height Ratio 的时候PDO 的强度也会跟着变两个参数不是独立的。调参时先定 Height Ratio再看 PDO 需不需要额外乘系数压一压。4. 手写 Custom 节点 POM一次射线步进加线性插值内置函数够用但它的步进方向、纹理通道、插值方式都是固定的遇到特殊情况就得自己动手比如你想把步进数接到一个全局参数上做画质分级或者想用两张高度图混合或者想省掉函数内部多余的节点。这一章给一份可以直接贴进 Custom 节点的实现顺便把每行在算什么讲清楚。4.1 Custom 节点的输入引脚怎么摆在材质里放一个 Custom 节点把输入引脚按下面的顺序建好类型别选错。引脚名类型说明UVsFloat2基础 UV来自 TexCoordHeightTexTexture2D高度图纹理对象HeightSamplerSamplerState高度图采样器ViewDirWSFloat3像素指向相机的单位向量NormalWSFloat3顶点法线世界空间TangentWSFloat3顶点切线世界空间BitangentWSFloat3副切线世界空间HeightRatioFloat视差强度0.03 起NumStepsFloat步进数先给 16ViewDirWS 用Camera Position WS减去Absolute World Position再过Normalize。注意不要用PixelNormalWS当 NormalWS因为它包含法线贴图的扰动会让视差方向和光照方向打架表现出来就是高光区域跟着视角抖。用VertexNormalWS更稳。Texture2D 和 SamplerState 这两个引脚把高度图的 Texture Object 和 Sampler 分别连进来。Sampler 可以在材质里用 Texture Sample 节点的 Sampler Source 拆出来也可以直接给一个默认采样器但显式给更可控尤其是你想用 Clamp 的时候。4.2 步进 插值的 HLSL 实现下面这段是核心输出一个修正后的 UV后面的颜色、法线采样都用它。// 输入UVs, HeightTex, HeightSampler, ViewDirWS, // NormalWS, TangentWS, BitangentWS, HeightRatio, NumSteps // 输出float2 修正后的 UV float3 N normalize(NormalWS); float3 T normalize(TangentWS); float3 B normalize(BitangentWS); float3 V normalize(ViewDirWS); // 把视线转到切线空间 float3 vTS float3(dot(V, T), dot(V, B), dot(V, N)); // 视线在平面上的投影 / 垂直分量得到每一步的 UV 位移方向与长度 // max 防止接近垂直时数值爆炸 float2 dir vTS.xy / max(vTS.z, 0.2); float steps clamp(NumSteps, 1.0, 32.0); float layerStep 1.0 / steps; float2 dUV dir * HeightRatio * layerStep; float2 curUV UVs; float2 prevUV UVs; float curLayer 0.0; float prevLayer 0.0; // 从表面往下扫描扫描平面高度 1 - curLayer float h Texture2DSample(HeightTex, HeightSampler, curUV).r; [unroll(32)] for (int i 0; i (int)steps; i) { // 采样点高度越过扫描平面说明踩到实处停止 if (h 1.0 - curLayer) break; prevUV curUV; prevLayer curLayer; curUV dUV; curLayer layerStep; h Texture2DSample(HeightTex, HeightSampler, curUV).r; } // 在命中层和上一层之间做线性插值消除阶梯感 float hPrev Texture2DSample(HeightTex, HeightSampler, prevUV).r; float d1 (1.0 - prevLayer) - hPrev; // 上一层与高度场之差通常 0 float d2 (1.0 - curLayer) - h; // 当前层与高度场之差通常 0 float w saturate(d1 / max(d1 - d2, 1e-4)); return lerp(prevUV, curUV, w);逐段说明。前四行构造切线空间基N、T、B 分别对应法线、切线、副切线注意都要归一化材质里传进来的向量不保证是单位长度。vTS.xy / max(vTS.z, 0.2)是视差计算的老朋友分子是视线在平面上的投影分母是视线与平面的夹角余弦。当相机正对表面时XY 接近 0偏移量接近 0符合直觉当相机大角度斜视时分母变小、分子变大偏移量被放大这也是斜视时最容易拉花的原因。max(vTS.z, 0.2)里的 0.2 是个保护值防止分母趋近 0 时数值溢出成噪点。如果你做的是地面材质、相机永远不会低到贴地这个值可以给 0.3。dUV是每一步的位移量等于总位移除以步数。curLayer从 0 递增到 1对应的扫描平面高度从 1 递减到 0也就是从表面往底部扫。循环体里的判断h 1.0 - curLayer是命中的条件采样到的高度超过了当前扫描平面说明视线在这一层碰到了实体。[unroll(32)]是给编译器的提示让它把循环展开步数在 16 以内时展开收益明显超过 32 就别展开了指令数会爆。最后的三行插值是把“上一次还没命中”和“这一次命中了”两个位置之间做一次线性混合权重由两层与高度场的距离决定。不做这一步画面会出现一层层的阶梯状条纹尤其在大角度斜视时特别明显。调试时如果发现凸起看起来像凹陷把dUV前面加个负号即可。这个符号取决于你的高度图是“白高黑低”还是“白低黑高”两种约定都有人用没有对错统一就行。4.3 把步进数做成距离衰减压住 GPU 开销Custom 版最大的好处就是参数完全可控可以把步进数和视距绑起来。做法是在材质里算一个衰减因子乘到 NumSteps 和 HeightRatio 上。// 单独一个 Custom 节点或材质节点图里算 float dist length(CameraPositionWS - AbsoluteWorldPosition); float fade saturate(1.0 - (dist - StartFade) / max(FadeRange, 1.0)); float finalSteps lerp(MinSteps, MaxSteps, fade); float finalRatio HeightRatio * fade;StartFade给 300 到 500厘米FadeRange给 800 到 1500。意思是 5 米内满效果13 米外完全退化成平面。这两个值要按你的场景尺度调室内场景和室外场景差一个数量级。更省一点的做法是配合 Quality Switch 节点Low 档直接输出 Bump Offset 的结果High 档才走 POM。这样在低配机器上不至于整个场景都在跑 16 次采样。还有一处Custom 节点里如果有分支和循环编译出来的指令数会比你想象的多。想看真实开销用ProfileGPU或者在编辑器里看材质的 Instruction Count改一次参数对比一次别凭感觉。我一般会把 NumSteps 从 8 到 24 各测一遍记录 Base Pass 的毫秒数选一个画质和开销的平衡点这个表比任何教程都靠谱。5. 避坑记录视差材质翻车的五个现场这一章的五条都是实际调材质时撞出来的按“现象、原因、解决”写遇到对得上的直接照方抓药。5.1 高度图 sRGB 没关视差强度像玄学现象Height Ratio 从 0.02 一路拉到 0.15画面几乎没什么变化偶尔某个角度突然又特别夸张调参完全没有线性反馈。原因高度图被当成颜色贴图导入了sRGB 开启。sRGB 是一条非线性曲线0.5 的灰度存进去再读出来不是 0.5中间调的对比被压缩、两端的差异被放大。视差计算需要的是线性高度值你给了一条曲线参数自然就不听使唤。解决在纹理资源的细节面板里取消勾选 sRGB压缩设置改成 Grayscale 或 MasksMip 生成保留POM 在远处需要 Mip 来避免噪点。重新导入后 Height Ratio 会突然变得“灵敏”之前调好的值要重新给一遍。顺带一个判断方法在材质里把高度图直接接到 Emissive 上看灰度正常的高度图应该能看出清晰的明暗层次如果整体发灰、集中在中间一小段说明对比度不够需要回到图像软件里做 Levels 拉伸。5.2 平铺次数不一致砖缝和砖面对不上现象颜色贴图的砖块和高度图的砖缝错位转视角的时候砖缝在砖面上“游走”越斜越明显。原因颜色、法线、高度三张贴图用了不同的 TexCoord或者 Multiply 的平铺值不一样。视差偏移是作用在高度图上的偏移后的 UV 又拿去采颜色两者一旦不同源偏移量就和图案对不上。解决整个材质只保留一个 TexCoord 节点和一套平铺参数用材质函数或者参数集把它抽出来三张贴图统一引用。如果确实需要不同的平铺比如细节法线也要保证基础平铺一致细节层单独叠。还有一种隐蔽情况三张贴图分辨率不同导入时 Mip 生成设置不同远景的采样位置有细微偏差。这种偏差在近景看不出来远景会变成一层薄薄的摩尔纹。统一分辨率、统一 Mip 设置能消掉大部分。5.3 高度图与法线贴图方向相反光照反着来现象视差效果本身是对的凸起也在正确的位置但打光之后高光落在凹陷处砖块看起来像被压进去的印章。原因高度图和法线贴图的高度约定相反。法线贴图默认“凸起朝外”而高度图有人用“白高黑低”有人用“白低黑高”。两者不一致时几何上凸起、光照上凹陷视觉冲突非常刺眼。解决拿一张有明显凸起的测试图比如一个球或者一颗铆钉单独看高度图的视差效果和法线贴图的光照效果哪个反了就改哪个。改高度图最省事采样后加一个OneMinus节点改法线贴图要在图像软件里翻转绿通道成本高且容易忘。这个坑最坑的地方在于它不是“报错”而是“看起来有点怪”新手很容易以为是光照或者后处理的问题查半天查不到材质上。5.4 PDO 开了影子却浮在半空现象接上 Pixel Depth Offset 之后物体穿插问题解决了但砖块投在地面上的阴影位置偏移或者表面出现细密的噪点闪烁移动相机时尤其明显。原因PDO 修改了深度写入阴影贴图的深度比较也随之改变。偏移量过大、或者阴影贴图的 bias 没跟着调就会出现自阴影shadow acne。另一种情况是 PDO 方向和视差方向相反深度被推到了错误的一侧。解决先确认 PDO 的符号把 Height Ratio 临时归零看 PDO 是否也归零再慢慢加大观察方向。符号对了之后给 PDO 乘一个 0.6 到 0.8 的系数压一压强度同时在项目设置的阴影部分适当提高阴影 bias。如果还闪用 Dither Temporal AA 节点给 PDO 做一个基于屏幕位置的抖动把硬边打散。移动端可以直接关掉 PDO接受轻微的穿插比闪烁好看得多。5.5 移动端 / 半透明材质里采样数爆掉现象PC 上跑得好好的 POM 材质切到移动端预览直接变成一片噪点或者纯黑编辑器里提示材质指令数超限。另一种是把这个材质用到半透明物体上深度完全不对。原因移动端渲染路径对纹理采样数和指令数有硬限制16 步循环展开后很容易超。半透明材质默认不写深度PDO 的输出无处可去视差计算出来的深度和实际渲染深度对不上。解决移动端用 Quality Switch 节点做降级Low 档走 Bump Offset只采一次高度图。或者用前面说的视距衰减把远处的 NumSteps 压到 1 到 2。半透明物体上不要用 POM改成 Bump Offset或者在材质里把 Blend Mode 改成 Masked用 Mask 做镂空这样深度能正常写。还有一个容易忽略的材质里如果同时用了 ClearCoat、Subsurface 这些特性指令预算会更紧POM 的步进数要相应往下调。6. 验证与调参把视差强度做成可对比的开关调视差材质最怕的是“感觉好像好了一点”因为没有基准。我的做法是永远留一个开关在材质参数集里放一个ParallaxIntensity标量范围 0 到 1乘到 HeightRatio 和 NumSteps 上。截图的时候同一机位拍 0、0.5、1 三张并排看差异比来回拖滑块靠谱得多。这个开关在上线时也有用低配机型直接全局关掉。高度图本身的质量决定了效果上限所以进材质之前先量一下。用一段脚本读灰度分布三行就能看出这张图能不能用from PIL import Image import numpy as np img Image.open(T_Brick_Height.png).convert(L) a np.asarray(img, dtypenp.float32) / 255.0 print(min%.3f max%.3f mean%.3f std%.3f % (a.min(), a.max(), a.mean(), a.std()))min和max应该接近 0 和 1如果只有 0.2 到 0.6说明有效高度只有 0.4Height Ratio 要给到两倍才有别人 0.05 的效果。std低于 0.15 基本可以判定对比度不足砖缝和砖面的高度差太小视差做出来会像一层雾。mean偏离 0.5 太多说明整体偏亮或偏暗Reference Plane 要跟着调否则整张贴图会朝一个方向飘。量完之后如果对比度不够别急着加大 Height Ratio先回图像软件把 Levels 拉到 0 到 1 满量程再重新导入。高度图的对比度是可以无损拉伸的Height Ratio 拉过头只会换来拉花。验证阶段还有两个动作值得固定下来。一是侧视检查把相机压到接近地面平视砖墙慢慢横移看砖块边缘有没有撕裂、有没有 UV 拉到砖面上的痕迹这是 POM 最容易翻车的角度。二是性能对照用Stat GPU记录开和关两种情况下的 Base Pass 毫秒数步进从 8 加到 24 各记一次做成一张小表下次换项目直接有参考值不用重新试。血泪经验是我曾经为了一个远景墙面把 Max Steps 拉到 32截图放大到 200% 对比了半天觉得“确实细腻一点”结果整个室内场景的帧率掉了 8 帧那面墙在实际游戏里根本没人会凑近看。从那以后我每次做视差材质都强制先跑一遍侧视检查和性能对照两个都过了再谈效果。希望帮到你。本文还有配套的精品资源点击获取