从深度纹理重建像素的世界空间位置
此示例中的 Unity 着色器使用深度纹理和屏幕空间 UV 坐标来重建像素的世界空间位置。该着色器在网格上绘制棋盘图案,使位置可视化。
下图展示了最终结果:
本页包含以下几节:
创建示例场景
要按照本节中的步骤操作,应创建示例场景:
将 URP 安装到现有 Unity 项目中,或使用通用项目模板创建新项目。
在示例场景中,创建一个平面游戏对象,并将其放置在适当位置以使其遮挡一些游戏对象。
创建一个新材质并将其分配给平面。
创建一个新着色器并将其分配给材质。从 URP 无光照基本着色器页面复制并粘贴 Unity 着色器源代码。
选择 URP 资源。如果项目是用通用渲染管线模板创建的,则 URP 资源路径为
Assets/Settings/UniversalRP-HighQuality
。在 URP 资源的 General 部分中,启用
Depth Texture
。打开在步骤 4 中创建的着色器。
编辑 ShaderLab 代码
本节假设已从 URP 无光照基本着色器页面复制了相应的源代码。
对 ShaderLab 代码进行以下更改:
在
HLSLPROGRAM
代码块中,添加深度纹理着色器头的 include 声明。例如,将该声明放在Core.hlsl
的现有 include 声明下。#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // DeclareDepthTexture.hlsl 文件包含用于对摄像机深度纹理进行采样的 // 实用程序。 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
DeclareDepthTexture.hlsl
文件包含用于对摄像机深度纹理进行采样的函数。此示例使用SampleSceneDepth
函数对像素的 Z 坐标进行采样。在片元着色器定义中,添加
Varyings IN
作为输入。half4 frag(Varyings IN) : SV_Target
在此示例中,片元着色器使用
Varyings
结构中的positionHCS
属性来获取像素的位置。在片元着色器中,要计算用于采样深度缓冲区的 UV 坐标,请将像素位置除以渲染目标分辨率
_ScaledScreenParams
。_ScaledScreenParams.xy
属性会考虑渲染目标的任何缩放,例如动态分辨率。float2 UV = IN.positionHCS.xy / _ScaledScreenParams.xy;
在片元着色器中,使用
SampleSceneDepth
函数对深度缓冲区进行采样。#if UNITY_REVERSED_Z real depth = SampleSceneDepth(UV); #else // 调整 z 以匹配 OpenGL 的 NDC real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(UV)); #endif
SampleSceneDepth
函数来自DeclareDepthTexture.hlsl
文件。它返回[0, 1]
范围内的 Z 值。要使重建函数 (
ComputeWorldSpacePosition
) 正常工作,深度值必须位于归一化设备坐标 (NDC) 空间中。在 D3D 中,Z 处于[0,1]
范围内,而在 OpenGL 中,Z 处于[-1, 1]
范围内。此示例使用
UNITY_REVERSED_Z
常量来确定平台并调整 Z 值范围。有关更多说明,请参阅此示例中的步骤 6。UNITY_NEAR_CLIP_VALUE
变量是裁剪空间的与平台无关的近裁剪面值。有关更多信息,请参阅平台特定的渲染差异。
从像素的 UV 和 Z 坐标重建世界空间位置。
float3 worldPos = ComputeWorldSpacePosition(UV, depth, UNITY_MATRIX_I_VP);
ComputeWorldSpacePosition
是一个实用函数,根据 UV 和深度 (Z) 值计算世界空间位置。此函数在 SRP Core 包的Common.hlsl
文件中定义。UNITY_MATRIX_I_VP
是一个逆视图投影矩阵,可将点从裁剪空间变换为世界空间。要使像素的世界空间位置可视化,请创建棋盘效果。
uint scale = 10; uint3 worldIntPos = uint3(abs(worldPos.xyz * scale)); bool white = (worldIntPos.x & 1) ^ (worldIntPos.y & 1) ^ (worldIntPos.z & 1); half4 color = white ? half4(1,1,1,1) : half4(0,0,0,1);
scale
是棋盘图案大小的相反比例。abs
函数将图案镜像到负坐标侧。worldIntPos
变量的uint3
声明将坐标位置贴靠到整数。表达式
<integer value> & 1
中的AND
运算符检查值是偶数 (0) 还是奇数 (1)。该表达式让代码将表面划分为正方形。表达式
<integer value> ^ <integer value>
中的XOR
运算符将翻转正方形颜色。对于未渲染几何图形的区域,深度缓冲区可能没有任何有效值。以下代码会在这些区域绘制黑色。
#if UNITY_REVERSED_Z if(depth < 0.0001) return half4(0,0,0,1); #else if(depth > 0.9999) return half4(0,0,0,1); #endif
不同的平台对远裁剪面使用不同的 Z 值(0 == far,或 1 == far)。
UNITY_REVERSED_Z
常量让代码可以正确处理所有平台。保存着色器代码,示例即准备就绪。
下图展示了最终结果:
完整的 ShaderLab 代码
以下是此示例的完整 ShaderLab 代码。
// 此 Unity 着色器使用深度纹理和屏幕空间 UV 坐标来重建
//像素的世界空间位置。该着色器在网格上绘制棋盘图案,
//使位置可视化。
Shader "Example/URPReconstructWorldPos"
{
Properties
{ }
// 包含 Shader 代码的 SubShader 代码块。
SubShader
{
// SubShader Tags 定义何时以及在何种条件下执行某个 SubShader 代码块
// 或某个通道。
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
Pass
{
HLSLPROGRAM
// 此行定义顶点着色器的名称。
#pragma vertex vert
// 此行定义片元着色器的名称。
#pragma fragment frag
// Core.hlsl 文件包含常用的 HLSL 宏和
// 函数的定义,还包含对其他 HLSL 文件(例如
// Common.hlsl、SpaceTransforms.hlsl 等)的#include 引用。
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// DeclareDepthTexture.hlsl 文件包含用于对摄像机深度纹理进行采样的
// 实用程序。
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
// 此示例使用 Attributes 结构作为顶点着色器中的
// 输入结构。
struct Attributes
{
// positionOS 变量包含对象空间中的顶点
// 位置。
float4 positionOS : POSITION;
};
struct Varyings
{
// 此结构中的位置必须具有 SV_POSITION 语义。
float4 positionHCS : SV_POSITION;
};
// 顶点着色器定义具有在 Varyings 结构中定义的
// 属性。vert 函数的类型必须与它返回的类型(结构)
// 匹配。
Varyings vert(Attributes IN)
{
// 使用 Varyings 结构声明输出对象 (OUT)。
Varyings OUT;
// TransformObjectToHClip 函数将顶点位置
// 从对象空间变换到齐次裁剪空间。
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
// 返回输出。
return OUT;
}
// 片元着色器定义。
// Varyings 输入结构包含来自顶点着色器的
// 插值。片元着色器使用 `Varyings` 结构中的
// `positionHCS` 属性来获取像素的位置。
half4 frag(Varyings IN) : SV_Target
{
// 要计算用于采样深度缓冲区的 UV 坐标,
// 请将像素位置除以渲染目标分辨率
// _ScaledScreenParams。
float2 UV = IN.positionHCS.xy / _ScaledScreenParams.xy;
// 从摄像机深度纹理中采样深度。
#if UNITY_REVERSED_Z
real depth = SampleSceneDepth(UV);
#else
// 调整 Z 以匹配 OpenGL 的 NDC ([-1, 1])
real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(UV));
#endif
// 重建世界空间位置。
float3 worldPos = ComputeWorldSpacePosition(UV, depth, UNITY_MATRIX_I_VP);
// 以下部分创建棋盘效果。
// 比例是平方反比。
uint scale = 10;
// 缩放、镜像和捕捉坐标。
uint3 worldIntPos = uint3(abs(worldPos.xyz * scale));
// 将表面划分为正方形。计算颜色 ID 值。
bool white = ((worldIntPos.x) & 1) ^ (worldIntPos.y & 1) ^ (worldIntPos.z & 1);
// 根据 ID 值(黑色或白色)为正方形着色。
half4 color = white ? half4(1,1,1,1) : half4(0,0,0,1);
// 在远裁剪面附近将颜色设置为
// 黑色。
#if UNITY_REVERSED_Z
// 具有 REVERSED_Z 的平台(如 D3D)的情况。
if(depth < 0.0001)
return half4(0,0,0,1);
#else
// 没有 REVERSED_Z 的平台(如 OpenGL)的情况。
if(depth > 0.9999)
return half4(0,0,0,1);
#endif
return color;
}
ENDHLSL
}
}
}