|
Unity shader提供了内置函数ComputeScreenPos,其定义位于UnityCG.cginc中,大致如下:
inline float4 ComputeNonStereoScreenPos(float4 pos) {
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
o.zw = pos.zw;
return o;
}
inline float4 ComputeScreenPos(float4 pos) {
float4 o = ComputeNonStereoScreenPos(pos);
#if defined(UNITY_SINGLE_PASS_STEREO)
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
return o;
}
其中的宏UNITY_SINGLE_PASS_STEREO用于控制在不同平台上计算方式的差异
看该函数的命名第一直觉是该函数返回的是屏幕空间下的坐标值,但是该函数返回的是齐次坐标下的屏幕坐标值。让我们剔除不必要的代码,来看核心的计算代码,如下
inline float4 ComputeNonStereoScreenPos(float4 pos) {
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
o.zw = pos.zw;
return o;
}
首先,该函数传入的参数pos为顶点变换到齐次坐标系下的坐标,也就是说,在shader中,你需要这么使用:
o.pos = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.pos);
接着来回顾下如何从齐次坐标变换到屏幕坐标:我们知道,顶点在变换到齐次坐标后,其x和y分量的范围在[-w, w]。假设目前屏幕的宽度为width,高度为height,那么屏幕坐标的计算方法为:
screenPosX = ((x / w) * 0.5 + 0.5) * width
screenPosY = ((y / w) * 0.5 + 0.5) * height
我们来看ComputeScreenPos是如何做的,它的计算方法为:
o.x = (pos.x * 0.5 + pos.w * 0.5)
o.y = (pos.y * 0.5 * _ProjectionParams.x + pos.w * 0.5)
其中_ProjectionParams.x用于在使用翻转投影矩阵时(此时其值为-1.0)翻转y的坐标值。
现在我们将屏幕坐标的计算方法变换到ComputeScreenPos使用的计算方法,如下:
screenPosX / width = ((x / w) * 0.5 + 0.5)
screenPosY / height = ((y / w) * 0.5 + 0.5)
两边同时乘w:
screenPosX / width * w = ((x * 0.5 + w * 0.5)
screenPosY / height * w = ((y * 0.5 + w * 0.5)
和unity shader 计算方法比较
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y_ProjectionParams.x) + o.w;
o.zw = pos.zw;
展开将o.x=pos.x*0.5代入公式
o.x=pos.x0.5+pos.w*0.5
现在一目了然了,ComputeScreenPos返回的值是齐次坐标系下的屏幕坐标值,其范围为[0, w]。那么为什么Unity要这么做呢?Unity的本意是希望你把该坐标值用作tex2Dproj指令的参数值,tex2Dproj会在对纹理采样前除以w分量。当然你也可以像下面代码那样自己除以w分量后进行采样,但是效率不如内置指令tex2Dproj:
pos = UnityObjectToClipPos(v.vertex);
screenPos = ComputeScreenPos(o.pos);
tex2D(_ScreenTex, float2(screenPos.xy / screenPos.w)) |
|