Unity Shader中各類空間及變換方法

前幾天嘗試寫一個傳送門的shader,發現本身對座標之間的變換掌握的不夠熟練,趁着這陣子想整理shader相關的知識點,先把各類空間及之間轉換整理一下。編程

1 模型空間-世界空間-觀察空間-裁剪空間

建模時在模型空間進行,模型自帶的座標均爲模型空間下的表示。 
當模型被放到世界座標系中時,表達某個模型的位置使用的是世界空間下的座標,因此模型上對應的某一個點,必須相應的轉化爲世界空間下的座標。 
從模型空間到世界空間的變換 叫作 模型變換。 
Unity的Shader中,模型空間的座標由Renderer直接提供,做爲頂點着色器的輸入,語義爲POSITION。如:app

struct appdata
{
    float4 vertex : POSITION;
}

於咱們能看到的渲染圖像均是經過攝像機獲得的,爲了方便後續裁剪、投影等操做,因此在將模型從模型空間變換到世界空間以後,還須要將其轉換到觀察空間。所謂的觀察空間,就是以攝像機位置爲原點,攝像機局部座標軸爲座標軸的座標系。 
從世界空間到觀察空間的變換叫作觀察變換(視圖變換)。this

座標轉換到觀察空間後,因爲直接使用攝像機的平截頭體進行裁剪比較複雜(平截頭體的邊界方程求交困難),因此須要將其轉化到裁剪空間。裁剪空間變換的思路是,對平截頭體進行縮放,使近裁剪面和遠裁剪面變成正方形,使座標的w份量表示裁剪範圍,此時,只須要簡單的比較x,y,z和w份量的大小便可。 
從觀察空間到裁剪空間的變換叫作投影變換。 
注意,雖然叫作投影變換,可是投影變換並無進行真正的投影。spa

在頂點着色器中,模型、觀察、裁剪空間的相關變換矩陣通常爲如下幾個:.net

別名 定義 含義
UNITY_MATRIX_M unity_ObjectToWorld 模型變換矩陣
UNITY_MATRIX_V unity_MatrixV 視圖變換矩陣
UNITY_MATRIX_P glstate_matrix_projection 投影變換矩陣
UNITY_MATRIX_VP unity_MatrixVP 視圖投影變換矩陣
UNITY_MATRIX_MV mul(unity_MatrixV, unity_ObjectToWorld) 模型視圖變換
UNITY_MATRIX_MVP mul(unity_MatrixVP, unity_ObjectToWorld) 模型視圖投影變換

注意,最新版本中(筆者用的是Unity5.6.3p4),官方建議將座標點從模型空間轉換到裁剪空間時,應使用UnityObjectToClipPos方法,該方法內部定義爲:code

mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0));

上述方法比起下面的順序更有效率。blog

mul(UNITY_MATRIX_MVP,  appdata.vertex);

在定點着色器中,輸出即爲裁剪空間下的座標。接口

2 屏幕空間

通過裁剪操做以後,咱們須要將裁剪空間的座標投影到屏幕空間。 
這裏主要分紅兩個步驟: 
1. 齊次除法 
使用xyz分別處以w份量,獲得NDC(歸一化設備座標),通過這一步,可以看到的座標點變成了一個xy邊長爲1,z爲-1到1的立方體。 
2. 屏幕映射 
使用NDC座標和屏幕長寬像素直接映射,獲得屏幕空間下的xy座標。注意,雖然屏幕空間沒有深度,但屏幕空間下的座標仍然保留了z的深度值,能夠進行深度檢測或其餘處理。圖片

從裁剪空間到屏幕空間由unity直接進行。這裏還要記住,從裁剪空間到屏幕空間,插值是硬件直接進行的。ip

以後咱們從像素着色器中得到的語義爲SV_POSITION的輸入,其座標基本沒有太多用處了。 
若是但願得到此時的屏幕座標,可使用VPOS或者WPOS,在Unity中,這二者同義。固然,使用上述語義須要

#pragma target 3.0

VPOS中的xy表明屏幕空間中的像素座標,注意,這裏的像素座標是中心點座標,不是整數,如屏幕分辨率爲400*300,則x的範圍爲[0.5, 400.5],y的範圍爲[0.5, 300.5]。z的範圍爲[0, 1],0是近裁剪面,1是遠裁剪面。w的範圍須要根據攝像機投影類型劃分,若是是透視投影,則其範圍爲[1/near,1/far],若是是正交投影,則恆爲1。 
這裏還須要注意的是,若是使用VPOS或者WPOS做爲fs的輸入,就沒法同時使用SV_POSITION做爲輸入,因此vs和fs須要寫成以下方式:

Shader "Unlit/Screen Position"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            // note: no SV_POSITION in this struct
            struct v2f {
                float2 uv : TEXCOORD0;
            };

            v2f vert (
                float4 vertex : POSITION, // vertex position input
                float2 uv : TEXCOORD0, // texture coordinate input
                out float4 outpos : SV_POSITION // clip space position output
                )
            {
                v2f o;
                o.uv = uv;
                outpos = UnityObjectToClipPos(vertex);
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
            {
                // screenPos.xy will contain pixel integer coordinates.
                // use them to implement a checkerboard pattern that skips rendering
                // 4x4 blocks of pixels

                // checker value will be negative for 4x4 blocks of pixels
                // in a checkerboard pattern
                screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
                float checker = -frac(screenPos.r + screenPos.g);

                // clip HLSL instruction stops rendering a pixel if value is negative
                clip(checker);

                // for pixels that were kept, read the texture and output it
                fixed4 c = tex2D (_MainTex, i.uv);
                return c;
            }
            ENDCG
        }
    }
}

這裏寫圖片描述

攝像機和屏幕相關經常使用的內置變量以下表所示。

別名 類型 含義
_WorldSpaceCameraPos float3  
_ProjectionParams float4  
_ScreenParams float4  
_ZBufferParams float4  
unity_OrthoParams float4  
unity_CameraProjection float4x4  
unity_CameraInvProjection float4x4  
unity_CameraWroldClipPlanes[6] float4  

3 視口空間

熟悉OpenGL編程的同窗都知道gl接口有一個glViewport,該接口其實就是肯定視口空間。視口空間是一個將屏幕座標對應到(0, 0)到(1, 1)範圍內的空間。 
若是想獲得視口空間座標,則能夠經過下面兩種方法。

fixed4 frag(float4 sp : VPOS) : SV_Target {
    return fixed(sp.xy/_ScreenParams.xy, 0.0, 1.0);
}

這裏的_ScreenParams中保存了屏幕分辨率。 
另外一種方法以下:

struct vertOut {
    float4 pos : SV_POSITION;
    float4 srcPos : TEXCOORD0;
};

vertOut vert(appdata_base v) {
    vertOut o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.srcPos = ComputeScreenPos(o.pos);
    return o;
}

fixed4 frag(vertOut i) : SV_Target {
    float2 wcoord = (i.srcPos.xy/i.scrPos.w);
    return fixed4(wcoord, 0.0, 1.0);
}

這裏,ComputeScreenPos並無直接進行透視除法,緣由是插值是線性的,必須在透視除法以後進行,因此,咱們必須在fs中手動進行。

幾篇不錯的參考資料以下: 
http://blog.csdn.net/wangdingqiaoit/article/details/51594408 
http://blog.csdn.net/wangdingqiaoit/article/details/51589825

相關文章
相關標籤/搜索