(三)unity自帶的着色器源碼剖析之——————UnityCG.cginc文件(下篇:法線貼圖及編解碼操作函數、線性化深度值函數、計算屏幕座標工具函數、陰影處理相關函數、霧相關函數等)

八、法線貼圖及其編解碼操作的函數

法線貼圖存儲的信息是對模型頂點法線的擾動方向向量,利用此擾動方向向量,在光照計算時對頂點原有的法線進行擾動,從而使法線方向排列有序的平滑表面產生法線方向雜亂無序從而導致表面凹凸不平的效果。

要在Unity3D中導入和使用法線貼圖,要設置爲Normal map類型,是因爲在不同平臺上,Unity3D可以利用該平臺硬件加速的紋理格式對導入的法線貼圖進行壓縮,同時也因爲法線貼圖和普通紋理貼圖在採樣和解碼時的方式也有所不同。

8.1 UnpackNormal函數

如果UNITY_NO_DXT5nm宏啓用了,表示引擎使用了DXT5nm壓縮格式或者BC5壓縮格式的法線貼圖紋理,則調用了UnpackNormalmapRGorAG函數去解碼,如下:

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
    return packednormal.xyz * 2 - 1;
#else
    return UnpackNormalmapRGorAG(packednormal);
#endif
}

DXT是一種紋理壓縮格式,以前稱爲S3TC。當前很多圖形硬件已經支持這種格式,即在顯存中依然保持着壓縮格式,從而減少顯存佔用量。目前有DXT1~5這5種編碼格式。

DXT系列壓縮格式被很多格式的文件所使用,如DDS文件格式就使用了DXT系列壓縮格式。要使用DXT格式壓縮圖像,要求圖像大小至少是4X4文素,而且圖像高寬的文素個數是2的整數次冪,如32X32,64X128等。

8.2 UnpackNormalmapRGorAG函數

該函數能同時處理DXT5nm和DXT格式的法線貼圖,並正確地把法線貼圖擾動向量從紋素中解碼出來,如下:

// Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1)
// Note neutral texture like "bump" is (0, 0, 1, 1) to work with both plain RGB normal and DXT5nm/BC5
fixed3 UnpackNormalmapRGorAG(fixed4 packednormal)
{
    // This do the trick
   packednormal.x *= packednormal.w;

    fixed3 normal;
    normal.xy = packednormal.xy * 2 - 1;
    normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
    return normal;
}

8.3 UnpackNormalDXT5nm函數

該函數用來解碼DXT5nm格式的法線貼圖,其算法思想和UnpackNormalmapRGorAG一致。

inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;
    normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
    return normal;
}

九、線性化深度值的工具函數

9.1 Linear01Depth函數

//把從深度紋理中取得的頂點深度值z變換到觀察空間中,然後映射到[0,1]區間內
//_ZBufferParams的x分量爲1減去視截體遠截面與近截面值的商,_ZBufferParams的y分量爲視截體遠截面與近截面值的商
// Z buffer to linear 0..1 depth
inline float Linear01Depth( float z )
{
    return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}

片元的深度往往是非線性的,即從近截面到遠截面之間的深度值精度分佈不均勻。但在有些場合中需要線性化的深度值,如在觀察空間中要利用深度值計算時,便要把深度紋理貼圖中獲取到的深度值重新映射到一個線性區域。

9.2 LinearEyeDepth函數

//把從深度紋理中取得的頂點深度值z變換到觀察空間中
//_ZBufferParams的x分量爲x分量除以視截體遠截面的值,w分量爲y分量除以視截體遠截面的值
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
    return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}

9.3 封裝了操作深度紋理的工具宏

// Depth render texture helpers
#define DECODE_EYEDEPTH(i) LinearEyeDepth(i)
//取得頂點從世界空間變換到觀察空間後的z值,並取其相反數
#define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos( v.vertex ).z
//取得頂點從世界空間變換到觀察空間後的z值,取得相反數後將值映射到[0,1]範圍內
#define COMPUTE_DEPTH_01 -(UnityObjectToViewPos( v.vertex ).z * _ProjectionParams.w)
//把頂點法線從世界空間變換到觀察空間
#define COMPUTE_VIEW_NORMAL normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal))

十、計算屏幕座標的工具函數

10.1 ComputeNonStereoScreenPos函數

//float4 pos是在裁剪空間中的一個齊次座標值
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;
}

10.2 ComputeScreenPos函數

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;
}

10.3 ComputeGrabScreenPos函數

inline float4 ComputeGrabScreenPos (float4 pos) {
    #if UNITY_UV_STARTS_AT_TOP
    float scale = -1.0;
    #else
    float scale = 1.0;
    #endif
    float4 o = pos * 0.5f;
    o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREO
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
    o.zw = pos.zw;
    return o;
}

當把當前屏幕內容截屏並保存在一個目標紋理時,有時要求知道在裁剪空間中某一點對應保存在目標紋理中的哪一點。該函數就是實現這個功能的,傳入裁剪空間中某點齊次座標值,返回該點在目標紋理中的紋理貼圖座標。

10.4 UnityPixelSnap函數

// snaps post-transformed position to screen pixels
inline float4 UnityPixelSnap (float4 pos)
{
    float2 hpc = _ScreenParams.xy * 0.5f;
#if  SHADER_API_PSSL
// sdk 4.5 splits round into v_floor_f32(x+0.5) ... sdk 5.0 uses v_rndne_f32, for compatabilty we use the 4.5 version
    float2 temp = ((pos.xy / pos.w) * hpc) + float2(0.5f,0.5f);
    float2 pixelPos = float2(__v_floor_f32(temp.x), __v_floor_f32(temp.y));
#else
    float2 pixelPos = round ((pos.xy / pos.w) * hpc);
#endif
    pos.xy = pixelPos / hpc * pos.w;
    return pos;
}

該函數功能是把一個視口座標轉換成屏幕像素座標。

10.5 兩個版本的TransfromViewToProjection函數

inline float2 TransformViewToProjection (float2 v) {
    return mul((float2x2)UNITY_MATRIX_P, v);
}

inline float3 TransformViewToProjection (float3 v) {
    return mul((float3x3)UNITY_MATRIX_P, v);
}

十一、與陰影處理相關的工具函數

11.1 UnityEncodeCubeShadowDepth函數

float4 UnityEncodeCubeShadowDepth (float z)
{
    #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
    return EncodeFloatRGBA (min(z, 0.999));
    #else
    return z;
    #endif
}

該函數功能是把一個float類型的陰影深度值編碼近一個float4類型的RGBA數值中。

11.2 UnityDecodeCubeShadowDepth函數

//把一個從立方體紋理貼圖中獲取到的顏色值轉化爲一個深度值
float UnityDecodeCubeShadowDepth (float4 vals)
{
	//如果一個點光源生成的陰影中獲取到,那麼深度值就會被編碼放到R、G、B、A這4個顏色通道中,若再用就要解碼出來
    #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
    return DecodeFloatRGBA (vals);
    #else
    return vals.r;
    #endif
}

該函數功能是把一個float4類型的陰影深度值解碼到一個float類型的浮點數中。

11.3 UnityClipSpaceShadowCasterPos函數

//vertex是物體頂點在模型空間中的座標值
//normal是物體法線在模型空間中的座標值
float4 UnityClipSpaceShadowCasterPos(float4 vertex, float3 normal)
{
	//頂點從模型空間轉換到世界空間
    float4 wPos = mul(unity_ObjectToWorld, vertex);

    if (unity_LightShadowBias.z != 0.0)
    {
		//法線從模型空間轉換到世界空間
        float3 wNormal = UnityObjectToWorldNormal(normal);
		//UnityWorldSpaceLightDir計算世界座標系下光源位置點_WorldSpaceLightPos0與世界座標系下點wPos的連線的方向向量
        float3 wLight = normalize(UnityWorldSpaceLightDir(wPos.xyz));

        // apply normal offset bias (inset position along the normal)
        // bias needs to be scaled by sine between normal and light direction
        // (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)
        //
        // unity_LightShadowBias.z contains user-specified normal offset amount
        // scaled by world space texel size.
		//計算光線與法線夾角的餘弦值
        float shadowCos = dot(wNormal, wLight);
        float shadowSine = sqrt(1-shadowCos*shadowCos);
        float normalBias = unity_LightShadowBias.z * shadowSine;

        wPos.xyz -= wNormal * normalBias; //沿着法線進行偏移
    }

    return mul(UNITY_MATRIX_VP, wPos); //進行了偏移之後的值變換到裁剪空間
}

11.4 UnityApplyLinearShadowBias函數

float4 UnityApplyLinearShadowBias(float4 clipPos)

{
    // For point lights that support depth cube map, the bias is applied in the fragment shader sampling the shadow map.
    // This is because the legacy behaviour for point light shadow map cannot be implemented by offseting the vertex position
    // in the vertex shader generating the shadow map.
#if !(defined(SHADOWS_CUBE) && defined(SHADOWS_CUBE_IN_DEPTH_TEX))
    #if defined(UNITY_REVERSED_Z)
	//對UnityClipSpaceShadowCasterPos函數計算出來的座標做一個增加,並且要保證不能越過遠近截面值
        // We use max/min instead of clamp to ensure proper handling of the rare case
        // where both numerator and denominator are zero and the fraction becomes NaN.
        clipPos.z += max(-1, min(unity_LightShadowBias.x / clipPos.w, 0));
    #else
        clipPos.z += saturate(unity_LightShadowBias.x/clipPos.w);
    #endif
#endif

#if defined(UNITY_REVERSED_Z)
    float clamped = min(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#else
    float clamped = max(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#endif
    clipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y);
	//根據第一次增加後的z值和z的極值,進行線性差值
    return clipPos;
}

UnityClipSpaceShadowCasterPos函數的功能是講陰影投射着(shadow caster)的座標沿着其法線做了一定偏移後再變換至裁剪空間。UnityApplyLinearShadowBias函數功能是將調用UnityClipSpaceShadowCasterPos函數得到的裁剪空間座標的z值再做一定的增加。因爲這個增加操作是在裁剪空間這樣的齊次座標系下進行的,所以要對透視投影產生的z值進行補償,使得陰影偏移值不會隨着與攝像機的距離的變化而變化。同時保證增加的z值不能超過裁剪空間的遠近截面z值。

11.5 V2F_SHADOW_CASTER_NOPOS宏和SHADOW_CASTER_FRAGMENT宏

#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    // Rendering into point light (cubemap) shadows
//用來存儲在世界座標系下當前頂點到光源位置的連線向量
    #define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
    #define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
   //x,y,z分量爲光源的位置,w分量爲光源的照射範圍的倒數,TRANSFER_SHADOW_CASTER_NOPOS的功能是計算在世界座標系下當前頂點到光源位置的連線向量,同時把頂點位置變換到裁剪空間
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
    //把一個float類型的陰影深度值編碼到一個float4類型中並返回
    #define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);

#else
    // Rendering into directional or spot light shadows
    //渲染由平行光或者聚光燈光源產生的陰影
    #define V2F_SHADOW_CASTER_NOPOS
    // Let embedding code know that V2F_SHADOW_CASTER_NOPOS is empty; so that it can workaround
    // empty structs that could possibly be produced.
    #define V2F_SHADOW_CASTER_NOPOS_IS_EMPTY
    #define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) \
        opos = UnityObjectToClipPos(v.vertex.xyz); \
        opos = UnityApplyLinearShadowBias(opos);
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
        opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
        opos = UnityApplyLinearShadowBias(opos);
    #define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif

十二、與霧效果相關的工具函數和宏

12.1 UNITY_Z_0_FAR_FROM_CLIPSPACE宏

#if defined(UNITY_REVERSED_Z)
    #if UNITY_REVERSED_Z == 1
        //D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
        //max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
       //不經z軸反向操作的OpenGL平臺上的裁剪空間座標
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
    #else
        //GL with reversed z => z clip range is [near, -far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
//經過z軸反向操作的OpenGL平臺上的裁剪空間座標
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(-(coord), 0)
    #endif
#elif UNITY_UV_STARTS_AT_TOP
    //D3d without reversed z => z clip range is [0, far] -> nothing to do

    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
    //Opengl => z clip range is [-near, far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#endif

在計算霧化因子時,需要取得當前片元和攝像機的距離的絕對值,並且離攝像機越遠這個值要越大。而這個距離要通過片元在裁剪空間中的z值計算得到。不同平臺下裁剪空間的z取值範圍有所不同,UNITY_Z_0_FAR_FROM_CLIPSPACE是把各個平臺的差異化給處理掉。

12.2 UNITY_REVERSED_Z宏和UNITY_NEAR_CLIP_VALUE宏的定義

//這些平臺逆轉裁剪空間的near-far取值,near爲1,far爲0
#if defined(SHADER_API_D3D11) || defined(SHADER_API_PSSL) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_METAL) || defined(SHADER_API_VULKAN) || defined(SHADER_API_SWITCH)
// D3D style platforms where clip space z is [0, 1].
#define UNITY_REVERSED_Z 1
#endif

#if defined(UNITY_REVERSED_Z)
#define UNITY_NEAR_CLIP_VALUE (1.0)   
#else
#define UNITY_NEAR_CLIP_VALUE (-1.0)
#endif

12.3 不同霧化因子計算方式下UNITY_CALC_FOG_FACTOR_RAW宏的實現

#if defined(FOG_LINEAR)  //霧化因子線性化衰減
    // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)  //霧化因子指數衰減
    // factor = exp(-density*z)
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2) //霧化因子指數衰減
    // factor = exp(-(density*z)^2)
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
#endif

如果不啓用霧化效果,則霧化因子值爲0.

Unity3D引擎提供了控制霧化因子衰減選項的功能,如下圖:

Color是霧的顏色,Density是霧的濃度。Mode下拉列表中有Linear、Exponential、Exponential Squared這3個選項,依次對應於啓用FOG_LINEAR、FOG_EXP、FOG_EXP2這3個宏。

12.4 UNITY_CALC_FOG_FACTOR宏和UNITY_FOG_COORDS_PACKED宏

#define UNITY_CALC_FOG_FACTOR(coord) UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))

#define UNITY_FOG_COORDS_PACKED(idx, vectype) vectype fogCoord : TEXCOORD##idx;

12.5 不同平臺和不同霧化因子計算方式下的UNITY_TRANSFER_FOG宏

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
    #define UNITY_FOG_COORDS(idx) UNITY_FOG_COORDS_PACKED(idx, float1)

    #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
        // mobile or SM2.0: calculate fog factor per-vertex
//如果移動平臺或者使用shader model2.0的平臺,則在頂點中計算霧化效果
        #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord.x = unityFogFactor
  
//如果是使用shader model3.0的平臺,或者使用PC以及一些遊戲主機平臺,就在頂點着色器中計算每個頂點離當前攝像機的距離。在片元着色器中計算霧化因子
        #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord.x = (outpos).z
        
    #endif
#else
    #define UNITY_FOG_COORDS(idx)
    #define UNITY_TRANSFER_FOG(o,outpos)

#endif

12.6 UNITY_FOG_LERP_COLOR宏

#define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))

該宏的功能是利用霧的顏色和當前像素的顏色,根據霧化因子進行線性差值運算,得到最終的霧化效果顏色

12.7 UNITY_APPLY_FOG_COLOR宏

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
    #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
        // mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp the color
//在移動平臺或者使用shader model 2.0的平臺中,因爲霧化因子以及在頂點着色器中計算過了,所以直接在片元着色器插值以計算霧化效果顏色
        #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,(coord).x)
    #else
        // SM3.0 and PC/console: calculate fog factor and lerp fog color
//如果是PC或者遊戲主機平臺,或者shader model3.0平臺,將在片元着色器中計算霧化因子,然後在片元着色器中通過插值計算霧化效果的顏色
        #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR((coord).x); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
    #endif
 
#else
    #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
    
#endif

UNITY_APPLY_FOG_COLOR宏定義在不同的平臺上的最終霧化效果的顏色計算方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章