1. 项目概述从“贴图”到“深度”的视觉魔术在Unity的Standard Shader里有一个参数常常被开发者忽略或者仅仅被当作一个“高级法线贴图”来用它就是Height Map高度图。如果你用过它可能只是简单地拖了一张灰度图进去调整了一下滑块发现模型表面多了一些凹凸不平的细节感觉还不错。但你是否真正理解这个看似简单的滑块背后驱动的是一个名为“视差偏移映射”Parallax Offset Mapping的完整渲染技术它和法线贴图Normal Map有本质区别不是为了改变光照方向而是为了在视觉上“欺骗”你的眼睛模拟出真实的表面深度和视差位移。Standard Shader的Height Map参数其核心价值在于用极低的性能开销为静态或中远景物体增加惊人的几何细节感。它不像曲面细分Tessellation那样真的增加顶点也不像视差遮蔽映射Parallax Occlusion Mapping, POM那样进行光线步进追踪那么昂贵。它更像是一种“聪明的UV扭曲”技术。当你从不同角度观察一个平面比如一面砖墙时砖缝和砖块表面的凹凸应该会产生视觉上的位移Height Map配合视差算法就是为了模拟这种效果。对于移动端、VR或者需要大量实例化的场景这个功能是提升材质真实感的性价比之王。理解这个参数不仅仅是知道怎么用更要明白它的原理、局限和最佳实践。这能帮助你在项目里做出更合理的渲染预算决策避免错误使用导致的视觉瑕疵。接下来我们就深入这个“滑块”的内部看看它到底是如何工作的。2. Height Map核心原理视差偏移映射Parallax Offset Mapping拆解2.1 视觉欺骗的基本逻辑要理解Height Map首先要抛弃“它直接创造了高度”的想法。在Standard Shader的语境下它并没有改变模型的顶点位置。它的工作原理完全建立在纹理坐标UV的操纵上。想象一下你面前有一张印有砖墙图案的平整卡片。无论你怎么左右移动头部砖缝看起来都牢牢地“贴”在卡片表面的同一个位置。但在真实的砖墙上当你向左移动时右边的砖缝会因为前景砖块的遮挡而看到更少的部分左边的砖缝则会显露更多——这就是视差效应。视差偏移映射的目标就是让那张卡片上的砖缝图案能根据你的观察角度发生“偏移”从而模拟出这种遮挡关系。它实现这一目标的手段就是根据高度图提供的“深度信息”白色代表凸起黑色代表凹陷以及当前摄像机观察的方向动态地计算出纹理采样点应该朝哪个方向、移动多少距离。2.2 核心算法代码解读Unity内置的实现代码是理解这一切的钥匙。在UnityCG.cginc文件中我们可以找到ParallaxOffset函数这正是Standard Shader高度图效果的引擎// Calculates UV offset for parallax bump mapping inline float2 ParallaxOffset( half h, half height, half3 viewDir ) { h h * height - height/2.0; float3 v normalize(viewDir); v.z 0.42; return h * (v.xy / v.z); }我们来逐行拆解这个看似简短却充满玄机的函数输入参数h: 从高度图纹理中采样得到的灰度值范围通常为0到1。height: 就是我们能在材质面板上调节的那个“Height”缩放系数Scale。viewDir:切线空间下的观察方向。这是最关键的一点。切线空间是以表面法线、切线和副切线为基向量的局部空间在这个空间里观察方向(x, y, z)中z分量大致指向模型表面的法线方向。高度值重映射 (h h * height - height/2.0):h * height: 将原始高度值按比例缩放。height参数很小如0.02这意味着即使高度图里白色值为1的区域其表现出的高度也很有限。- height/2.0: 这是一个偏移操作。它将高度值的基准从[0, height]移动到了[-height/2, height/2]。这意味着灰色值为0.5的区域偏移量为0白色区域产生正向偏移凸起处纹理向后移动黑色区域产生负向偏移凹陷处纹理向前移动。这确保了平面的中间高度不会产生偏移视觉效果更自然。观察方向处理 (v.z 0.42):这行代码是算法的“经验性魔法数字”。v.z是观察方向在法线方向上的分量。直接使用v.xy / v.z在观察方向与表面近乎平行时v.z接近0会导致偏移量趋向于无穷大产生严重的视觉拉伸失真。加上一个常数0.42相当于给分母增加了一个“保护值”Guard Band防止除零错误并极大地稳定了掠射角观察时的效果。这个值是经过大量实践验证的经验值它牺牲了绝对物理准确性换来了整体的稳定性和性能。最终偏移计算 (return h * (v.xy / v.z)):v.xy / v.z: 这部分决定了偏移的方向和相对强度。观察方向越倾斜v.xy越大v.z越小偏移量就越大这符合近大远小的透视规律。乘以重映射后的高度h就得到了最终的UV偏移量。这个偏移量会直接加到基础UV坐标上从而使得在渲染漫反射、法线等纹理时采样点发生了位移。注意整个计算过程发生在切线空间。这意味着你必须确保传递给函数的viewDir是正确转换到切线空间的向量。Standard Shader在内部帮我们完成了这个转换但如果你自己编写Shader或使用Shader Graph复现这是最容易出错的一步。2.3 与其它技术的对比为了更清晰地定位Height MapParallax Offset Mapping的能力范围我们可以将其与相关技术进行对比技术原理性能开销视觉效果适用场景法线贴图 (Normal Map)改变像素点的法线方向影响光照计算。极低仅有光照凹凸感无轮廓变形和视差。所有平台增加表面细节的主力。视差偏移映射 (Parallax Offset Mapping)根据高度图和视角偏移UV坐标。低具有动态视差效果轮廓依然平滑。移动端、VR、中远景静态物体墙壁、地面、岩石。视差遮蔽映射 (Parallax Occlusion Mapping, POM)在高度场中进行光线步进寻找真正交点。中高能模拟遮挡、自阴影轮廓有阶梯感。PC/主机平台需要高质量表面细节的静态物体。曲面细分置换 (Tessellation Displacement)真正增加顶点几何并沿法线移动顶点。非常高物理正确的几何变形轮廓和阴影完全改变。电影级画质、PC高端显卡用于地形、角色皮肤等。从表格可以看出Standard Shader的Height Map是一个在效果和性能间取得了绝佳平衡的方案。它无法产生真正的几何轮廓变化模型的剪影依然是原来的形状也无法处理深度遮挡凸起部分不会在凹陷部分投下阴影但对于非掠射角度的观察它能提供远超法线贴图的立体感。3. 参数详解与美术资源制作3.1 Height参数那个神秘的滑块材质面板上的“Height”参数对应着代码中的height缩放系数。它的官方建议范围是0.005 到 0.08。这个范围为什么这么小物理尺度映射这个值并非直接对应世界空间中的米或厘米。它是一个相对比例系数作用于归一化的UV空间通常一个纹理瓦片覆盖模型上一块区域。值过大会导致UV偏移过度在视角变化时产生严重的纹理滑动和失真看起来像“游动的蝌蚪”而不是固定的凹凸。经验法则对于大多数砖墙、石板、粗糙金属表面从0.02 到 0.05开始尝试是一个安全的选择。你可以先调到0.03在场景中环绕物体观察如果没有明显的纹理扭曲或闪烁再酌情微增。依赖纹理分辨率同样的Height值在高分辨率纹理如4K上会比低分辨率纹理1K看起来更“温和”。因为高分辨率纹理包含更多细节较小的偏移就能产生可辨识的层次感。反之低分辨率纹理可能需要稍大的值来凸显效果但更容易暴露瑕疵。实操心得调整Height参数时务必在场景视图中环绕物体移动摄像机视角进行观察。静态的Game视图或材质预览球无法有效评估视差效果的好坏和稳定性。重点关注物体边缘和掠射角度看是否有异常的纹理拉伸或闪烁。3.2 高度图Height Map制作规范高度图的质量直接决定了最终效果的优劣。一张糟糕的高度图会让再好的参数设置也徒劳无功。来源从法线贴图转换这是最常用的方法。在Substance Designer、Photoshop使用NVIDIA Texture Tools插件或在线转换工具中都可以将法线贴图转换为高度图。原理是法线贴图的梯度信息反映了高度的变化。但转换得到的高度图通常对比度较弱可能需要后续调整。从灰度图生成如果你的漫反射贴图Albedo本身明暗对比强烈可以直接去色作为高度图的基础。砖缝是暗的凹陷砖面是亮的凸起。软件直接生成ZBrush、Mudbox、Substance Painter等雕刻/纹理软件可以直接烘焙出高精度的高度图。程序化生成通过噪声函数、节点材质如Unity的Shader Graph实时生成。核心要求清晰的边缘与平滑的过渡凹凸区域的边缘需要明确高对比度但灰度变化本身应该是平滑的避免出现尖锐的锯齿或色阶断层。这能确保UV偏移是连续变化的不会产生突兀的“跳变”。合理的对比度不要追求极端的黑白对比。过高的对比度纯白和纯黑相邻会导致相邻像素的UV偏移量差异巨大在低视角下极易产生纹理撕裂。理想的高度图应该以中灰色为基底细节在亮灰和暗灰之间变化。匹配其他贴图高度图的凹凸轮廓必须与法线贴图和漫反射贴图的细节对齐。砖缝在高度图上是黑的凹在法线贴图上对应的法线方向也应指向内部在漫反射贴图上颜色通常也较深。三者错位是材质看起来“假”的主要原因之一。纹理导入设置Unity中Texture Type设置为Default或Normal map均可。如果设为Normal mapUnity会假设这是一张法线贴图并进行颜色空间转换这可能会破坏高度数据。更推荐使用Default类型。sRGB (Color Texture)必须关闭。高度图存储的是线性数据强度信息而不是颜色信息。勾选sRGB会让Unity对纹理进行伽马校正导致高度值被错误地非线性化严重破坏效果。Wrap Mode通常设为Repeat以支持纹理平铺。Filter Mode推荐使用Trilinear或Anisotropic。Bilinear在视角变化时可能产生轻微的锯齿Point无过滤则绝对不可用会导致偏移计算出现块状瑕疵。3.3 与其他贴图通道的协作Standard Shader中Height Map并非孤立工作它需要与其他贴图通道协同才能构建出统一的表面叙事。与法线贴图Normal Map的关系这是最重要的搭档。必须使用经过Height Map偏移后的UV去采样法线贴图。Standard Shader内部自动完成了这个步骤。这样光照计算所依据的表面“微观朝向”才能与视觉上的凹凸位移保持一致。否则你会看到光照高光停留在错误的位置立体感立刻被破坏。与遮挡贴图Occlusion Map的关系环境光遮挡AO贴图模拟了缝隙和凹陷处接收更少环境光的效果。同样它也应该使用偏移后的UV进行采样让阴影出现在正确的视觉凹陷处。与细节贴图Detail Albedo/Normal的关系Unity的Standard Shader支持第二套UV和细节贴图。高度图通常不影响细节贴图的UV。细节贴图用于表现更微小的、无深度的表面噪点如沙粒、织物纤维保持其UV不变可以避免整体材质显得过于“游动”。4. 在Shader Graph中手动复现Standard Shader高度图效果虽然Unity内置的渲染管线Built-in RP的Standard Shader直接提供了Height Map参数但在可编程渲染管线URP/HDRP中或者当你需要自定义效果时就需要在Shader Graph中手动搭建这个逻辑。这个过程能让你对原理有更透彻的理解。4.1 核心节点图搭建目标是复现ParallaxOffset函数。以下是基于之前代码的节点化实现步骤输入准备Height Map Texture: 创建Texture2D属性并连接一个Sample Texture 2D节点。将其Sampler状态设置为Linear Clamp或Linear Repeat根据需求。Height Scale: 创建一个Vector1属性作为我们的height缩放参数默认值设为0.02。Base UV: 使用UV节点获取基础纹理坐标。View Direction (Tangent Space): 这是关键。使用View Direction节点并将其Space设置为Tangent。这将输出一个在切线空间下的观察方向向量。计算重映射高度将Sample Texture 2D的R通道灰度值输出与Height Scale属性相乘。复制一个Height Scale值除以2使用Multiply节点乘0.5。用第一步的乘法结果减去这个height/2.0的值。这一步对应代码中的h h * height - height/2.0。处理观察方向并计算偏移将View Direction (Tangent Space)向量进行Normalize归一化操作。使用Split节点将其拆分为X,Y,Z分量。对Z分量使用Add节点加上一个常数0.42。使用Divide节点用X分量除以处理后的Z分量得到offsetX。同理用Y分量除以Z分量得到offsetY。使用Combine节点将offsetX和offsetY组合成一个二维向量(v.xy / v.z)。生成最终UV偏移将第2步得到的重映射高度值一个标量与第3步得到的二维偏移向量相乘。这对应h * (v.xy / v.z)。这个相乘的结果就是最终的Parallax Offset。应用偏移将最终的偏移向量与Base UV相加得到Parallax UV。重要将这个Parallax UV连接到你的所有受视差影响的纹理采样器的UV端口包括Albedo、Normal、Metallic等。确保视觉细节统一位移。4.2 关键陷阱与调试技巧在Shader Graph中实现时以下几个坑几乎每个初学者都会遇到陷阱一错误的空间。最大的错误是使用了世界空间或观察空间的View Direction。必须确保是切线空间Tangent Space。你可以通过创建一个简单的调试输出将View Direction向量的各分量映射到颜色上如X-Red, Y-Green, Z-Blue应用到材质球。在切线空间下当你正面观察材质球时中心应该是蓝色的因为观察方向(0,0,1)与法线平行边缘会呈现其他颜色。如果颜色不对说明空间错了。陷阱二sRGB开关。如前所述高度图纹理导入时必须关闭sRGB (Color Texture)。在Shader Graph中采样后如果发现效果微弱或反向首先检查此项。陷阱三Height Scale值过大。这是导致纹理严重扭曲、闪烁的元凶。始终从很小的值开始0.01逐步增加。在掠射角度观察如果纹理看起来像要“飞走”一样说明值太大了。调试技巧你可以创建一个分支将计算出的Parallax UV直接输出为颜色例如使用Fraction节点后输出以可视化UV的扭曲模式或者将偏移向量的长度输出为灰度图来直观地查看偏移的强度和分布。暂时关闭法线贴图等其他效果只保留高度图对Albedo的影响可以更纯粹地评估视差效果是否正确。4.3 性能考量与优化即使在Shader Graph中这个算法的开销也很低因为它只增加了几次简单的标量和向量运算。但仍有优化点纹理采样优化高度图通常不需要很高的分辨率。对于中远景物体使用一张512x512甚至256x256的高度图配合双线性/三线性过滤效果已经足够且能节省纹理带宽和采样指令。条件执行可以为高度图效果添加一个开关属性。对于完全平坦的表面如塑料、玻璃或者摄像机距离极远的物体可以关闭视差计算直接使用基础UV节省GPU周期。与LOD结合在脚本中可以根据摄像机与物体的距离动态调整材质的Height Scale参数甚至在不同LOD级别使用不同精度的高度图或完全关闭效果。5. 进阶应用超越Standard Shader的简单视差理解了基础原理后我们可以尝试一些扩展思路虽然Standard Shader本身不支持但可以在自定义Shader或Shader Graph中实现。5.1 实现视差遮蔽映射POM的思路Parallax Occlusion Mapping是比Offset Mapping更高级的技术。它不再是简单的一次性偏移而是通过“光线步进”Raymarching在高度场中寻找真正的交点能模拟遮挡和更准确的深度。基本原理从像素点出发沿切线空间观察方向发出一条射线。将高度图视为一个2.5D的高度场UV坐标决定位置灰度值决定高度。让射线从表面上方高度1逐步向表面下方高度0步进。每次步进都检查射线当前点的高度与高度图在该UV点采样值的高度。当射线高度低于高度图采样高度时说明射线穿过了表面找到了一个近似交点。Shader Graph实现难点POM需要动态循环for-loop而Shader Graph的节点体系对循环支持有限通常需要自定义HLSL函数节点。Unity官方在较新版本的Shader Graph中提供了Parallax Occlusion Mapping节点主要在HDRP中但在URP中可能仍需手动编码或寻找第三方节点。性能警告POM的步进次数如10-20步直接决定了质量和性能。它比Offset Mapping昂贵数倍仅建议在PC/主机平台对近处的重要静态物体如地面、英雄武器使用。5.2 高度图驱动其他效果高度图本身是表面几何信息的描述我们可以用它驱动更多效果即使在不使用视差的情况下边缘磨损效果将高度图与一个噪声图结合在高度较低凹陷、缝隙的区域混合进另一种颜色或粗糙度值模拟污垢堆积或边缘磨损的视觉效果。雪堆积效果用世界空间或物体空间的高度图需要烘焙来判断表面的“向上”程度。法线朝上的平坦区域高度变化平缓更容易堆积雪而陡峭的斜坡或缝隙则露出原本材质。这需要结合法线信息进行计算。动态湿润效果水会首先积聚在凹陷处。可以用高度图来调制表面的高光强度、光滑度和颜色让凹陷区域看起来更湿滑、颜色更深。5.3 与HDRP/URP Lit Shader的异同在新的渲染管线中Lit Shader也提供了高度图功能但实现方式可能不同HDRP Lit Shader通常提供更完整的选项可能同时支持简单的Parallax Offset Mapping和更高质量的Parallax Occlusion Mapping甚至可能与Tessellation曲面细分联动实现真正的顶点置换。你需要仔细查阅对应版本HDRP的文档了解其具体实现和参数。URP Lit Shader在较新的版本中如Unity 2021 LTS之后URP的Lit Shader也内置了高度图支持其原理与Built-in RP的Standard Shader类似但参数名称和位置可能略有不同。同样它也可能提供POM作为可选项。迁移建议当从Built-in RP的Standard Shader迁移到URP/HDRP时不要假设高度图参数会完全一致。最好重新制作材质根据新管线的文档调整参数并进行视觉比对测试。6. 常见问题与排查实录在实际项目中使用Height Map你几乎一定会遇到下面这些问题。这里是我踩过坑后总结的排查清单。6.1 效果不明显或完全没效果检查1Height Scale值是否太小尝试调大到0.05或0.08观察是否有变化。检查2高度图纹理的sRGB是否关闭这是最常见的原因。在Project窗口选中高度图在Inspector中确保Bypass sRGB Sampling为勾选状态或sRGB (Color Texture)为取消状态。检查3观察角度是否合适视差效果在掠射角度摄像机几乎平行于表面下最明显。正面观察一个平面时效果几乎看不到。尝试倾斜摄像机。检查4Shader是否正确支持确认你使用的Shader确实实现了视差偏移映射。一些简化版的Mobile Shader或自定义Shader可能移除了此功能。检查5Shader GraphView Direction空间是否正确确保是Tangent空间。节点连接是否正确最终偏移是否加到了UV上6.2 纹理出现严重拉伸、闪烁或“游泳”现象检查1Height Scale值是否过大立即将值调小从0.01开始重新测试。这是导致此现象的首要原因。检查2纹理过滤模式是否为Point确保高度图和所有使用偏移UV采样的纹理其导入设置的Filter Mode不是Point。使用Bilinear或Trilinear。检查3UV平铺值是否过高如果材质的Tiling值很大例如10x10那么即使很小的Height Scale也会导致UV偏移跨越多个纹理瓦片造成视觉错误。考虑是否需要这么高的平铺或者降低Height Scale。检查4是否存在UV接缝在模型UV展开的边缘高度图的不连续可能导致偏移计算出现突变。确保高度图在UV边界是连续可平铺的。6.3 视差效果在特定角度下“失效”或反向检查1高度图黑白是否颠倒视觉上白色应该代表凸起偏移UV使纹理看起来“后退”黑色代表凹陷纹理“前进”。如果你的效果看起来是反的可以在Shader中用一个One Minus节点对高度图采样结果取反或者直接在Photoshop中反转高度图颜色。检查2法线贴图是否匹配如果高度图显示是凹槽但法线贴图显示该处法线朝外凸起两者会产生矛盾导致大脑无法识别表面结构感觉效果“奇怪”或“无效”。确保两者信息一致。6.4 性能问题分析怀疑对象纹理采样次数。使用高度图后所有受影响的纹理Albedo, Normal, Metallic等都需要用一套新的UV进行采样。这相当于增加了一整套纹理的采样开销。对于使用大量贴图的复杂材质影响会放大。优化建议纹理合图Texture Atlasing将多个材质的贴图合并到一张大图上可以减少采样器状态切换和Draw Call。使用mipmap确保所有纹理启用了mipmap远距离物体使用低分辨率mip等级节省带宽。按需使用如前所述通过距离判断或材质变体为远处物体关闭高度图效果。掌握Standard Shader的Height Map参数远不止是学会调节一个滑块。它是一扇门通往基于图像渲染Image-Based Rendering技术中“用2D信息模拟3D细节”的广阔世界。从理解其背后的视差偏移映射原理到制作规范的高度图资源再到在Shader Graph中亲手复现并调试这个过程能极大地加深你对现代实时渲染中“细节表现”与“性能权衡”的理解。最实用的建议是建立一个自己的材质测试场景。放上几个不同几何复杂度的模型平面、球体、复杂雕塑使用同一套带有高度图的材质。然后在场景中自由飞行摄像机从各个角度、不同距离去观察效果的变化。亲手去调大调小Height Scale感受它如何从“细腻的立体感”一步步变成“灾难性的扭曲”。这种肌肉记忆般的经验比记住任何参数范围都更有价值。当你再在项目中使用它时你就能凭直觉判断眼前这个表面是否需要高度图又该给它一个怎样的强度才能以最小的代价换来最可信的深度幻觉。