视差遮挡贴图(Parallax Occlusion Mapping,简称POM)是计算机图形学中一种重要的技术,用于在不增加几何复杂度的前提下为表面增加深度感和细节。在Unity URP的Shader Graph中,Parallax Occlusion Mapping节点提供了一个直观的方式来创建这种效果,让开发者能够通过简单的节点连接实现高质量的视觉表现。
视差效果基于人类视觉感知原理,当观察者移动时,靠近的物体相对于远处的物体会出现更明显的位移。POM技术正是模拟了这种现象,通过对纹理坐标进行智能偏移,创造出表面凹凸不平的错觉。与传统的法线贴图相比,POM能够提供更加真实的深度感和自遮挡效果,特别是在视角较陡的情况下。
描述
Parallax Occlusion Mapping节点是Shader Graph中用于实现高级视差效果的核心组件。该节点通过处理输入的高度贴图信息,计算出适当的UV偏移和深度偏移,从而在材质表面产生立体感和深度错觉。
视差遮挡贴图的工作原理是基于光线追踪的概念。当光线(视线)与表面相交时,系统会根据高度贴图中的信息模拟光线在凹凸表面上的行进路径。通过多步采样和插值计算,找到光线与表面几何的精确交点,进而确定应该显示哪个纹理位置的像素。
在Shader Graph中使用Parallax Occlusion Mapping节点时,需要注意版本兼容性问题。如果在包含自定义函数节点或子图形的图形中使用此节点时遇到纹理采样错误,可以通过升级到Unity 10.3或更高版本来解决这些问题。这是因为早期版本在处理复杂节点连接时可能存在一些限制和bug。
一个常见的技术要点是:当将相同的Texture2D分配给POM节点和Sample Texture 2D节点时,需要避免UV坐标被转换两次。这种情况会导致纹理采样不正确,产生意外的视觉效果。为解决这个问题,开发者可以使用Split Texture Transform节点的Texture Only端口连接到Sample Texture 2D节点的UV端口,确保UV坐标只被正确处理一次。
Parallax Occlusion Mapping技术特别适用于需要高细节表现但受限于性能的场景,比如:
- 游戏中的墙面、地面材质
- 复杂表面的科学可视化
- 建筑可视化中的材质细节
- 虚拟现实环境中的高沉浸感场景
端口
Parallax Occlusion Mapping节点包含多个输入和输出端口,每个端口都有特定的功能和用途。理解这些端口的作用对于正确使用该节点至关重要。
输入端口
输入端口决定了POM效果的具体表现和行为。每个输入端口都接受特定类型的数据,并影响最终的视觉效果。
- Heightmap:这是POM效果的核心输入,接受一个Texture2D类型的高度贴图。高度贴图通常是一张灰度图像,其中白色代表高点,黑色代表低点。高度贴图的质量直接决定了POM效果的精细程度和真实感。建议使用高分辨率、无压缩的高度贴图以获得最佳效果。
- Heightmap Sampler:此端口定义如何对高度贴图进行采样,接受Sampler State类型。采样器状态控制着纹理的过滤方式和寻址模式,如点过滤、双线性过滤或三线性过滤。正确的采样器设置可以避免视觉瑕疵,特别是在低角度观察时。
- Amplitude:浮点型输入,控制高度贴图应用的强度,单位为厘米。这个参数决定了表面凹凸的明显程度。较高的值会产生更强烈的深度感,但可能在某些视角下出现失真;较低的值则产生更微妙的效果。通常需要根据场景尺度和观察距离来调整此参数。
- Steps:浮点型输入,定义算法线性搜索执行的步骤数。步数越多,效果越精确,但性能开销也越大。在实际应用中,需要在质量和性能之间找到平衡。通常8-16步对于大多数应用已经足够,但对于极端角度或需要高质量效果的情况,可能需要24步或更多。
- UVs:Vector 2类型输入,指定采样器用来采样纹理的UV坐标。这是基础的纹理坐标输入,通常来自模型的默认UV或经过其他节点处理后的UV坐标。
- Tiling:Vector 2类型输入,控制纹理在UV空间中的重复次数。这个参数允许在不修改原始网格UV的情况下调整纹理密度,对于创建无缝重复的材质模式非常有用。
- Offset:Vector 2类型输入,对输入UV应用偏移量。这个参数可以用于动画效果或动态调整纹理位置。
- Primitive Size:Vector 2类型输入,定义对象空间中UV空间的大小。例如,Unity内置的平面网格的原始大小为(10,10)。这个参数对于确保不同尺寸的模型上POM效果的一致性非常重要。
- Lod:浮点型输入,指定用于采样高度贴图的细节级别。该值应始终为正值,较高的值表示使用更模糊的mip级别。Lod控制可以帮助在远处减少POM效果的细节,优化性能。
- Lod Threshold:浮点型输入,定义POM效果开始淡出的高度贴图Mip级别。这等效于高清渲染管线(HDRP)光照材质中的Fading Mip Level Start属性。这个参数可以平滑地过渡到远处的平面纹理,避免远处出现视觉瑕疵。
输出端口
输出端口提供了POM处理后的结果,可以连接到其他节点或主节点的相应输入。
- Pixel Depth Offset:浮点型输出,表示应用于深度缓冲区的偏移量,用于产生深度感。这个输出对于实现正确的深度交互效果至关重要。要启用依赖深度缓冲区的效果,例如阴影和屏幕空间环境光遮挡,需要将此输出连接到主节点上的Depth Offset输入。这样确保了POM表面的凹凸能够正确地投射和接收阴影。
- Parallax UVs:Vector 2类型输出,表示添加视差偏移后的UV坐标。这些修改后的UV应该用于采样颜色贴图、法线贴图和其他表面属性贴图,以确保所有纹理都与POM效果保持一致。使用原始UV采样其他纹理会导致纹理与几何不匹配,破坏视觉一致性。
生成的代码示例
理解Parallax Occlusion Mapping节点背后生成的代码对于高级使用和调试非常有帮助。以下示例代码表示此节点的一种可能结果,并解释了关键部分的功能。
ini
// 计算视图方向并调整对象尺度
float3 ParallaxOcclusionMapping_ViewDir = IN.TangentSpaceViewDirection * GetDisplacementObjectScale().xzy;
// 计算视图方向与表面法线的点积
float ParallaxOcclusionMapping_NdotV = ParallaxOcclusionMapping_ViewDir.z;
// 计算最大高度值,将振幅从厘米转换为适当的尺度
float ParallaxOcclusionMapping_MaxHeight = Amplitude * 0.01;
// 根据平铺值调整最大高度
ParallaxOcclusionMapping_MaxHeight *= 2.0 / ( abs(Tiling.x) + abs(Tiling.y) );
// 计算UV空间中的尺度因子
float2 ParallaxOcclusionMapping_UVSpaceScale = ParallaxOcclusionMapping_MaxHeight * Tiling / PrimitiveSize;
// 将视图向量转换到UV空间
float3 ParallaxOcclusionMapping_ViewDirUV = normalize(float3(ParallaxOcclusionMapping_ViewDir.xy * ParallaxOcclusionMapping_UVSpaceScale, ParallaxOcclusionMapping_ViewDir.z));
// 设置POM参数结构
PerPixelHeightDisplacementParam ParallaxOcclusionMapping_POM;
ParallaxOcclusionMapping_POM.uv = UVs.xy;
// 输出高度变量
float ParallaxOcclusionMapping_OutHeight;
// 执行主要的POM计算,得到视差调整后的UV
float2 _ParallaxOcclusionMapping_ParallaxUVs = UVs.xy + ParallaxOcclusionMapping(Lod, Lod_Threshold, Steps, ParallaxOcclusionMapping_ViewDirUV, ParallaxOcclusionMapping_POM, ParallaxOcclusionMapping_OutHeight);
// 计算像素深度偏移
float _ParallaxOcclusionMapping_PixelDepthOffset = (ParallaxOcclusionMapping_MaxHeight - ParallaxOcclusionMapping_OutHeight * ParallaxOcclusionMapping_MaxHeight) / max(ParallaxOcclusionMapping_NdotV, 0.0001);
这段生成的代码揭示了POM技术的内部工作机制:
- 视图方向转换:代码首先将观察方向转换到切线空间,这是处理表面相对方向的标准方法。切线空间允许算法独立于模型的实际朝向工作。
- 高度计算:通过振幅参数计算最大高度值,并进行适当的单位转换和尺度调整。这一步确保高度值与场景的其他部分保持比例一致。
- UV空间变换:将视图向量转换到UV空间,这是POM算法的核心部分。这种转换使得算法能够在纹理坐标空间中模拟光线行进。
- POM函数调用:实际的视差遮挡映射计算发生在ParallaxOcclusionMapping函数中。这个函数执行多步光线行进,通过高度贴图找到视线与表面的交点。
- 深度偏移计算:最后,根据计算出的高度值和视图方向计算像素深度偏移。这个值用于修改深度缓冲区,确保POM表面能够正确地与场景中的其他几何体交互。
理解这段代码有助于开发者更好地调试POM效果,特别是在遇到视觉异常或性能问题时。例如,如果POM效果在某些角度出现断裂,可能是Steps参数过低;如果深度效果不明显,可能是Amplitude值需要调整。
实际应用示例
为了更好地理解Parallax Occlusion Mapping节点的使用,以下是一个完整的应用示例,展示如何创建一个具有深度感的砖墙材质。
首先,需要准备以下纹理资源:
- 颜色贴图:砖墙的漫反射颜色
- 法线贴图:砖墙的表面方向信息
- 高度贴图:砖缝和砖块的高度变化
- 粗糙度贴图:砖墙的光泽度变化
在Shader Graph中创建新图形,并按照以下步骤连接节点:
- 添加Parallax Occlusion Mapping节点到画布
- 将高度贴图连接到Heightmap输入
- 设置适当的Amplitude值(例如0.5,根据实际尺度调整)
- 设置Steps值为16,平衡质量和性能
- 将模型的UV0输出连接到UVs输入
- 设置适当的Tiling值,如(2,2)使纹理重复两次
- 根据模型尺寸设置Primitive Size(平面模型通常为(10,10))
- 将Parallax UVs输出连接到颜色贴图、法线贴图和粗糙度贴图的Sample Texture 2D节点的UV输入
- 将Pixel Depth Offset输出连接到主节点的Depth Offset输入
通过这样的设置,当摄像机在砖墙前移动时,砖缝会出现适当的视差位移,产生真实的深度感。砖块似乎从表面凸出,而砖缝则凹陷进去,即使实际几何体是完全平坦的。
性能考虑和最佳实践
使用Parallax Occlusion Mapping时,性能是一个重要考虑因素。以下是一些优化建议:
- 步骤数优化:Steps参数对性能影响最大。在远处或小表面上使用较少的步数,可以通过根据距离动态调整Steps值来实现。
- 纹理压缩:使用适当压缩的高度贴图可以减少内存带宽使用。对于高度贴图,通常建议使用BC5(DXTC_RG)格式存储高度数据。
- LOD过渡:合理设置Lod Threshold,确保在远处平滑过渡到平面着色,避免突然的视觉变化。
- 振幅比例:根据场景尺度设置适当的Amplitude值。过高的值会导致不自然的变形和性能问题。
- 视锥裁剪:确保POM效果只在必要时计算,通过适当的视锥裁剪和遮挡剔除来避免不可见表面的计算。
【Unity Shader Graph 使用与特效实现】专栏-直达 (欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)