编写 Unity Shader 的时候,语义 POSITION 和 SV_POSITION 的区别?
在编写 Unity Shader 的时候,经常会遇到语义 POSITION 和 SV_POSITION,它们都是表示顶点位置的语义。但是,它们之间有着一些微妙的区别。本文将详细介绍这两个语义的区别,并给出相应的示例。
一、POSITION
首先,我们来看一下 POSITION。在 Unity Shader 中,POSITION 表示的是顶点在模型空间中的位置。也就是说,这个语义表示的是从模型空间到世界空间的变换后的顶点位置。在顶点着色器中,我们可以使用 float4 类型的 POSITION 变量来表示顶点的位置:
void vert(inout appdata_full v, out Input o)
{
// 计算顶点在模型空间中的位置
float4 pos = mul(unity_ObjectToWorld, v.vertex);
// 将顶点位置赋值给 POSITION
o.vertex = pos;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
}
在上面的代码中,我们首先使用 unity_ObjectToWorld 矩阵将顶点从模型空间转换到世界空间,然后将变换后的顶点位置赋值给了 POSITION。这个 POSITION 变量将会被传递到像素着色器中,用来计算最终的颜色。
二、SV_POSITION
接下来,我们来看一下 SV_POSITION。在 Unity Shader 中,SV_POSITION 表示的是顶点在屏幕空间中的位置。也就是说,这个语义表示的是从世界空间到屏幕空间的变换后的顶点位置。在顶点着色器中,我们可以使用 float4 类型的 SV_POSITION 变量来表示顶点在屏幕空间中的位置:
void vert(inout appdata_full v, out Input o)
{
// 计算顶点在模型空间中的位置
float4 pos = mul(unity_ObjectToWorld, v.vertex);
// 计算顶点在屏幕空间中的位置
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
// 将顶点位置赋值给 SV_POSITION
o.vertex = ComputeGrabScreenPos(o.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
}
在上面的代码中,我们首先使用 unity_ObjectToWorld 矩阵将顶点从模型空间转换到世界空间,然后使用 UNITY_MATRIX_MVP 矩阵将顶点从世界空间转换到屏幕空间。最后,我们将变换后的顶点位置赋值给了 SV_POSITION。这个 SV_POSITION 变量将会被传递到像素着色器中,用来计算最终的颜色。
三、POSITION 和 SV_POSITION 的区别
从上面的示例中,我们可以看出 POSITION 和 SV_POSITION 的区别。具体来说,它们的区别如下:
POSITION 表示的是顶点在模型空间中的位置,而 SV_POSITION 表示的是顶点在屏幕空间中的位置。
POSITION 变量在顶点着色器中使用,用来计算最终的颜色,而 SV_POSITION 变量在像素着色器中使用,用来计算最终的颜色。
POSITION 变量的值是从模型空间到世界空间的变换后的顶点位置,而 SV_POSITION 变量的值是从世界空间到屏幕空间的变换后的顶点位置。
四、示例
下面我们来看一个示例,来更好地理解 POSITION 和 SV_POSITION 的区别。假设我们有一个简单的立方体模型,我们需要将其绘制到屏幕上,并且在每个面上显示不同的颜色。我们可以使用以下的 Shader:
Shader "Custom/ColorCube" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Standard
struct Input {
float2 uv_MainTex;
float4 worldPos;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutputStandard o) {
// 计算顶点在模型空间中的位置
float4 pos = IN.worldPos;
// 将顶点位置赋值给 POSITION
o.Normal = pos.xyz;
o.Albedo = pos.xyz;
o.Alpha = 1.0;
}
ENDCG
}
FallBack "Diffuse"
}
在上面的代码中,我们使用了 worldPos 变量来传递顶点在世界空间中的位置。在 surf 函数中,我们将 worldPos 赋值给了 POSITION,这样就可以在像素着色器中使用这个变量了。
接下来,我们需要修改一下顶点着色器,来计算顶点在屏幕空间中的位置,并将其赋值给 SV_POSITION 变量。修改后的代码如下:
void vert (inout appdata_full v) {
// 计算顶点在模型空间中的位置
float4 pos = v.vertex;
// 将顶点位置赋值给 POSITION
v.normal.xyz = pos.xyz;
v.tangent.xyz = pos.xyz;
v.texcoord.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
// 计算顶点在屏幕空间中的位置
v.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
// 将顶点位置赋值给 SV_POSITION
ComputeGrabScreenPos(v.vertex);
}
在上面的代码中,我们首先计算了顶点在模型空间中的位置,然后将其赋值给了 normal 和 tangent 变量。这里使用 normal 和 tangent 变量来传递顶点的位置是因为 Surface Shader 中没有 POSITION 变量。接着,我们使用 UNITY_MATRIX_MVP 矩阵将顶点从世界空间转换到屏幕空间,并将变换后的顶点位置赋值给了 SV_POSITION 变量。
最后,我们需要在像素着色器中使用 SV_POSITION 变量来计算最终的颜色。修改后的代码如下:
void surf (Input IN, inout SurfaceOutputStandard o) {
// 计算世界空间中的顶点位置
float3 worldPos = mul(unity_ObjectToWorld, IN.worldPos).xyz;
// 计算顶点在屏幕空间中的位置
float4 screenPos = ComputeGrabScreenPos(IN.worldPos);
// 根据屏幕空间中的位置来计算颜色
if (screenPos.x < 0.5) {
o.Albedo = float3(1, 0, 0);
} else if (screenPos.x < 1.0) {
o.Albedo = float3(0, 1, 0);
} else {
o.Albedo = float3(0, 0, 1);
}
o.Normal = worldPos;
o.Alpha = 1.0;
}
在上面的代码中,我们首先使用 unity_ObjectToWorld 矩阵将顶点从模型空间转换到世界空间,然后计算顶点在屏幕空间中的位置,并根据屏幕空间中的位置来计算颜色。这里我们将 x 坐标小于 0.5 的面设置为红色,将 x 坐标在 0.5 到 1.0 之间的面设置为绿色,将 x 坐标大于 1.0 的面设置为蓝色。
五、总结
在编写 Unity Shader 的时候,POSITION 和 SV_POSITION 都是表示顶点位置的语义。它们之间的区别在于,POSITION 表示的是顶点在模型空间中的位置,而 SV_POSITION 表示的是顶点在屏幕空间中的位置。在顶点着色器中,我们可以使用 POSITION 变量来表示顶点的位置,在像素着色器中,我们可以使用 SV_POSITION 变量来表示顶点的位置。在实际应用中,我们需要根据具体的需求来选择使用哪个语义。