(二)unity自帶的着色器源碼剖析之——————UnityCG.cginc文件(上篇:數學常數、顏色空間常數和函數、頂點佈局格式結構體、進行空間變換的函數、HDR級光照貼圖編解碼相關函數等)

一、數學常數

unity3D內置着色器定義了一系列的數學常數,如下:

從第3行開始,第13行結束:

#ifndef UNITY_CG_INCLUDED
#define UNITY_CG_INCLUDED

#define UNITY_PI            3.14159265359f       //圓周率
#define UNITY_TWO_PI        6.28318530718f       //2倍圓周率
#define UNITY_FOUR_PI       12.56637061436f      //4倍圓周率
#define UNITY_INV_PI        0.31830988618f       //圓周率的倒數
#define UNITY_INV_TWO_PI    0.15915494309f       //2倍圓周率的倒數
#define UNITY_INV_FOUR_PI   0.07957747155f       //4倍圓周率的倒數
#define UNITY_HALF_PI       1.57079632679f       //半圓周率
#define UNITY_INV_HALF_PI   0.636619772367f      //半圓周率的倒數

二、與顏色空間相關的常數和工具函數

2.1 IsGammaSpace函數的定義如下:

//用來判斷當前是否啓用了伽馬顏色空間函數
inline bool IsGammaSpace()
{
    #ifdef UNITY_COLORSPACE_GAMMA
        return true;
    #else
        return false;
    #endif
}

上述代碼段中的IsGammaSpace函數根據宏UNITY_COLORSPACE_GAMMA是否被啓用了,判斷當前是否啓用了伽馬顏色空間。

2.2 GammaToLinearSpaceExact函數

從88行開始,96行結束:

inline float GammaToLinearSpaceExact (float value)
{
    if (value <= 0.04045F)
        return value / 12.92F;
    else if (value < 1.0F)
        return pow((value + 0.055F)/1.055F, 2.4F);
    else
        return pow(value, 2.2F);
}

上述代碼段中的GammaToLinearSpaceExact函數把一個顏色值精確地從伽馬顏色空間(sRGB顏色空間)變化到線性空間(CIE-XYZ顏色空間)。

2.3 GameToLinearSpace函數

從98行開始,105行結束:

inline half3 GammaToLinearSpace (half3 sRGB)
{
    //GammaToLinearSpaceExact函數的近似模擬版本
    return sRGB * (sRGB * (sRGB * 0.305306011h + 0.682171111h) + 0.012522878h);

    // Precise version, useful for debugging.
    //return half3(GammaToLinearSpaceExact(sRGB.r), GammaToLinearSpaceExact(sRGB.g), GammaToLinearSpaceExact(sRGB.b));
}

上述代碼段中的GameToLinearSpace函數是用一個近似模擬的函數把顏色值近似地從伽馬空間變換到線性空間。

2.4  LinearToGammaSpaceExact函數

從107行開始,117行結束:

inline float LinearToGammaSpaceExact (float value)
{
    if (value <= 0.0F)
        return 0.0F;
    else if (value <= 0.0031308F)
        return 12.92F * value;
    else if (value < 1.0F)
        return 1.055F * pow(value, 0.4166667F) - 0.055F;
    else
        return pow(value, 0.45454545F);
}

上述函數把一個顏色值精確地從線性空間變換到伽馬顏色空間。

2.5 LinearToGammaSpace函數

從119行開始,第127行結束:

inline half3 LinearToGammaSpace (half3 linRGB)
{
    linRGB = max(linRGB, half3(0.h, 0.h, 0.h));
    // An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
    return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.h);

    // Exact version, useful for debugging.
    //return half3(LinearToGammaSpaceExact(linRGB.r), LinearToGammaSpaceExact(linRGB.g), LinearToGammaSpaceExact(linRGB.b));
}

這個函數用一個近似模擬的函數把顏色值近似地從線性空間變換到伽馬顏色空間。

三、描述頂點佈局格式的結構體

因爲在不同場合中渲染引擎需要頂點攜帶的信息是不同的,如果只用一個把所有頂點信息都添加在內的結構體去描述頂點,在很多場合會造成數據的冗餘。因此unity預定了若干用於描述頂點結構佈局的結構體版本,方便在不同場合下使用。

3.1 頂點結構體 appdata_base

從51行開始,56行結束:

struct appdata_base {
    float4 vertex : POSITION;    //世界座標下的頂點座標
    float3 normal : NORMAL;      //頂點法線
    float4 texcoord : TEXCOORD0;    //頂點使用的第一層紋理座標
    UNITY_VERTEX_INPUT_INSTANCE_ID    //頂點多例化的ID
};

UNITY_VERTEX_INPUT_INSTANCE_ID是定義頂點多例化ID用的一個宏,後面會講到。

3.2 頂點結構體 appdata_tan

從第58行開始,64行結束:

struct appdata_tan {
    float4 vertex : POSITION;   //世界座標下的頂點座標
    float4 tangent : TANGENT;;//頂點切線
    float3 normal : NORMAL;//頂點法線
    float4 texcoord : TEXCOORD0; //頂點使用的第一層紋理座標
    UNITY_VERTEX_INPUT_INSTANCE_ID  //頂點多例化的ID
};

相對於appdata_base結構體,多了一個頂點的切線信息,在使用法線貼圖技術時需要利用頂點的切線。

3.2 頂點結構體 appdata_full

從第66行開始,76行結束:

struct appdata_full {
    float4 vertex : POSITION;//世界座標下的頂點座標
    float4 tangent : TANGENT;//頂點切線
    float3 normal : NORMAL;//頂點法線
    float4 texcoord : TEXCOORD0;//頂點使用的第一層紋理座標
    float4 texcoord1 : TEXCOORD1;//頂點使用的第二層紋理座標
    float4 texcoord2 : TEXCOORD2;//頂點使用的第三層紋理座標
    float4 texcoord3 : TEXCOORD3;//頂點使用的第四層紋理座標
    fixed4 color : COLOR;//頂點顏色
    UNITY_VERTEX_INPUT_INSTANCE_ID//頂點多例化的ID
};

上述代碼段中appdata_full定義了最全的頂點信息結構體,該結構體有四層紋理座標和頂點顏色。在做地形渲染時,通常會用到非常多的紋理以產生複雜多變的地面效果。

四、用於進行空間變換的工具函數

實際開發中經常會遇到把某一個位置座標或者方向向量從一個空間座標系下變換到另一個空間座標系的要求。

4.1 UnityWorldToClipPos函數

// Tranforms position from world to homogenous space
inline float4 UnityWorldToClipPos( in float3 pos )
{
    return mul(UNITY_MATRIX_VP, float4(pos, 1.0));
}

宏UNITY_MATRIX_VP是當前觀察矩陣與投影矩陣的乘積,該函數作用就是把世界座標空間中某一點pos變換到齊次裁剪空間中去。

4.2 UnityViewToClipPos函數

// Tranforms position from view to homogenous space
inline float4 UnityViewToClipPos( in float3 pos )
{
    return mul(UNITY_MATRIX_P, float4(pos, 1.0));
}

宏是當前的投影矩陣,該函數的作用就是把觀察座標空間中某一點pos變換到齊次裁剪空間中去。

4.3 參數類型爲float3的UnityObjectToViewPos函數

// Tranforms position from object to camera space
inline float3 UnityObjectToViewPos( in float3 pos )
{
    return mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(pos, 1.0))).xyz;
}

宏是當前的觀察矩陣,該函數作用就是把模型局部空間座標系中某一個點pos,首先變換到世界空間座標系下,然後變換到觀察空間座標系下。

4.4 參數類型爲float4的UnityObjectToViewPos函數

inline float3 UnityObjectToViewPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
    return UnityObjectToViewPos(pos.xyz);
}

定義這個重載版本是爲了當傳遞float4類型參數到UnityObjectToViewPos(int float3 pos)函數時,避免出現隱式截斷問題。

4.5 UnityWorldToViewPos函數

// Tranforms position from world to camera space
inline float3 UnityWorldToViewPos( in float3 pos )
{
    return mul(UNITY_MATRIX_V, float4(pos, 1.0)).xyz;
}

該函數作用是把世界座標系下的一個點pos變換到觀察空間座標系下。

4.6 UnityObjectToWorldDir函數

該函數的作用是把一個方向向量從模型座標系變換到世界座標系下,然後對結果進行單位化。

// Transforms direction from object to world space
inline float3 UnityObjectToWorldDir( in float3 dir )
{
    return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}

4.7  UnityWorldToObjectDir函數

該函數的作用是把一個方向向量從世界座標系變換到模型座標系下,然後對結果進行單位化。

// Transforms direction from world to object space
inline float3 UnityWorldToObjectDir( in float3 dir )
{
    return normalize(mul((float3x3)unity_WorldToObject, dir));
}

4.8 UnityObjectToWorldNormal函數

// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
    return UnityObjectToWorldDir(norm);
#else
    // mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
    return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}

該函數中,把一個頂點從模型座標系變換到世界座標系上的變換矩陣爲unity_ObjectToWorld,則把該頂點的法線從模型座標系變換到世界座標系的矩陣應是unity_ObjectToWorld的逆轉置矩陣,如上寫法即可。

4.9 UnityWorldSpaceLightDir函數

// Computes world space light direction, from world space position
inline float3 UnityWorldSpaceLightDir( in float3 worldPos )
{
    #ifndef USING_LIGHT_MULTI_COMPILE
        return _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;
    #else
        #ifndef USING_DIRECTIONAL_LIGHT    //如果不是平行光
        return _WorldSpaceLightPos0.xyz - worldPos;
        #else
        return _WorldSpaceLightPos0.xyz;   //如果是平行光直接返回
        #endif
    #endif
}

該函數中,輸入參數worldPos是一個世界座標系下的座標,該函數用於計算這個座標到同樣在世界座標系下並且內置在引擎中的光源位置點_WorldSpaceLightPos0的連線的方向向量。

4.10 WorldSpaceLightDir函數

// Computes world space light direction, from object space position
// *Legacy* Please use UnityWorldSpaceLightDir instead
inline float3 WorldSpaceLightDir( in float4 localPos )
{
	//首先把localPos變換到世界座標系下,然後調用UnityWorldSpaceLightDir函數計算出連線方向
    float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
    return UnityWorldSpaceLightDir(worldPos);
}

該函數首先把傳遞進來的局部座標系下某個座標localPos變換到世界座標系下,然後在進行上個函數的操作,本函數在當前版本的unity3D中已經不使用,保留下來是爲了兼容舊有的第三方着色器代碼。

4.11 ObjSpaceLightDir函數

// Computes object space light direction
inline float3 ObjSpaceLightDir( in float4 v )
{
    float3 objSpaceLightPos = mul(unity_WorldToObject, _WorldSpaceLightPos0).xyz;
    #ifndef USING_LIGHT_MULTI_COMPILE
        return objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;
    #else
        #ifndef USING_DIRECTIONAL_LIGHT
        return objSpaceLightPos.xyz - v.xyz;
        #else
        return objSpaceLightPos.xyz;
        #endif
    #endif
}

上述代碼段中的ObjSpaceLightDir函數和WorldSpaceLightDir函數類似,只是把_WorldSpaceLightPos0位置變換到模型座標系下,計算出光源位置點和v的連線的方向向量。

4.12 UnityWorldSpaceViewDir函數

// Computes world space view direction, from object space position
inline float3 UnityWorldSpaceViewDir( in float3 worldPos )
{
    return _WorldSpaceCameraPos.xyz - worldPos;
}

該函數在世界座標系下計算出某位置點到攝像機位置點worldPos的連線向量。

4.13 WorldSpaceViewDir函數

// Computes world space view direction, from object space position
// *Legacy* Please use UnityWorldSpaceViewDir instead
inline float3 WorldSpaceViewDir( in float4 localPos )
{
    float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
    return UnityWorldSpaceViewDir(worldPos);
}

傳遞進來的參數localPos是一個基於模型座標系下的位置值,需要先把localPos變換到世界座標系下,再轉調用UnityWorldSpaceViewDir函數得到連線向量,在當前版本unity3D已經不使用,保留下來只是爲了兼容舊有的第三方着色器代碼。

4.14 ObjectSpaceViewDir函數

// Computes object space view direction
inline float3 ObjSpaceViewDir( in float4 v )
{
    float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
    return objSpaceCameraPos - v.xyz;
}

把_WorldSpaceCameraPos位置變換到當前的模型座標系下,計算出攝像機位置點objSpaceCameraPos和v的連線的方向向量。

4.15 TANGENT_SPACE_ROTATION宏的定義如下:

// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
    float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
    float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )

定義一個宏TANGENT_SPACE_ROTATION,此宏的作用是定義一個類型爲float3X3,名字爲rotation的3X3矩陣。這個矩陣由頂點的法線、切線、以及與頂點的法線切線都相互垂直的副法線組成,構成了一個正交的切線空間。

五、與光照計算相關的工具函數

5.1 Shade4PointLights函數

// Used in ForwardBase pass: Calculates diffuse lighting from 4 point lights, with data packed in a special way.
//本函數將用在ForwardBase類型的渲染通道上,參數lightPosX、lightPosY、lightPosZ的4個分量依次存儲了4個點光源的x座標、
//y座標、z座標。參數lightColor0、lightColor1、lightColor2、lightColor3依次存儲了4個點光源的顏色的RGB值。
//lightAttenSq的4個分量依次存儲了4個點光源的二次項衰減係數。
float3 Shade4PointLights (
    float4 lightPosX, float4 lightPosY, float4 lightPosZ,
    float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
    float4 lightAttenSq,
    float3 pos, float3 normal)
{
    // to light vectors
	//一次性計算頂點到每一個光源之間的x座標差、y座標差、z座標差
    float4 toLightX = lightPosX - pos.x;
    float4 toLightY = lightPosY - pos.y;
    float4 toLightZ = lightPosZ - pos.z;
    // squared lengths
	//一次性計算頂點到每一個光源的距離的平方
    float4 lengthSq = 0;
    lengthSq += toLightX * toLightX;
    lengthSq += toLightY * toLightY;
    lengthSq += toLightZ * toLightZ;
    // don't produce NaNs if some vertex position overlaps with the light
	//如果頂點離光源太近了,就微調一個很小的數作爲他們的距離
    lengthSq = max(lengthSq, 0.000001);

    // Ndot
	//計算頂點到4個光源連線的向量,以及頂點法線normal的夾角的餘弦值,即頂點到4個光源在法線的投影
    float4 ndotl = 0;
    ndotl += toLightX * normal.x;
    ndotl += toLightY * normal.y;
    ndotl += toLightZ * normal.z;
    // correct NdotL
	//因爲由toLightX、toLightY、toLightZ組成的頂點到4個光源連線的向量是沒有經過單位化的,
	//所以ndot1變量中的每一個分量必須除以lengthSq變量中的每一個分量,即頂點到每一個光源的距離的平方。
    float4 corr = rsqrt(lengthSq);
    ndotl = max (float4(0,0,0,0), ndotl * corr);
    // attenuation
	//計算出從光源到頂點位置的光的衰減值
    float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
	//衰減值在乘以夾角餘弦
    float4 diff = ndotl * atten;
    // final color
	//計算出最終的顏色
    float3 col = 0;
    col += lightColor0 * diff.x;
    col += lightColor1 * diff.y;
    col += lightColor2 * diff.z;
    col += lightColor3 * diff.w;
    return col;
}

Shade4PointLights函數用在頂點着色器的ForwardBase渲染通道上,本函數在每一個頂點被4個點光源照亮時,利用蘭伯特光照模型計算出光照的漫反射效果。

5.2 ShaderVertexLightsFull函數

// Used in Vertex pass: Calculates diffuse lighting from lightCount lights. Specifying true to spotLight is more expensive
// to calculate but lights are treated as spot lights otherwise they are treated as point lights.
//本函數用在頂點着色器中,計算出光源產生的漫反射光照效果
//float4 vertex 頂點的位置座標
//float4 normal 頂點的法線
//float4 lightCount參與光照計算的光源數量
//float4 spotLight 光源是不是聚光燈光源
float3 ShadeVertexLightsFull (float4 vertex, float3 normal, int lightCount, bool spotLight)
{
	//Unity3D提供的光源位置和光線傳播方向在當前攝像機所構成的觀察空間中,
	//所以先把傳遞進來的頂點座標變換到觀察空間,頂點的法線也乘以model-view矩陣的逆轉置矩陣變換到觀察空間
    float3 viewpos = UnityObjectToViewPos (vertex);
    float3 viewN = normalize (mul ((float3x3)UNITY_MATRIX_IT_MV, normal));
	//UNITY_LIGHTMODEL_AMBIENT在UnityShaderVariables.cginc文件中定義
    float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    for (int i = 0; i < lightCount; i++) {
		//如果 unity_LightPosition[i]對應的光源是有向平行光,則unity_LightPosition[i].w的值爲0,
		//unity_LightPosition[i].xyz就是光的方向;如果不是有向平行光,w值爲1,x、y、z是光源在觀察空間
		//中的位置座標。總之,toLight就是頂點位置到光源位置的連線的方向向量
        float3 toLight = unity_LightPosition[i].xyz - viewpos.xyz * unity_LightPosition[i].w;
		//toLight自身的點積實際上就是頂點到光源的距離的平方
        float lengthSq = dot(toLight, toLight);

        // don't produce NaNs if some vertex position overlaps with the light
		//如果頂點離光源太近了,就微調一個很小的數作爲他們的距離
        lengthSq = max(lengthSq, 0.000001);
		//求出距離的倒數
        toLight *= rsqrt(lengthSq);
		//光源的衰減計算
        float atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[i].z);
        if (spotLight)
        {
            float rho = max (0, dot(toLight, unity_SpotDirection[i].xyz));
            float spotAtt = (rho - unity_LightAtten[i].x) * unity_LightAtten[i].y;
            atten *= saturate(spotAtt);
        }

        float diff = max (0, dot (viewN, toLight));
        lightColor += unity_LightColor[i].rgb * (diff * atten);
    }
    return lightColor;
}

上述代碼中,變量rho由頂點到光源連線方向toLight與聚光燈光源的正前照射方向unity_SpotDirection求點積而成。也就是說,rho爲toLight方向與unity_SpotDirection方向的夾角餘弦值cos(p)。

5.3 ShadeVertexLights函數

float3 ShadeVertexLights (float4 vertex, float3 normal)
{
    return ShadeVertexLightsFull (vertex, normal, 4, false);
}

該函數就是轉調ShadeVertexLightsFull函數,指定使用4個非聚光燈光源進行光照計算。

5.4 TRANSFORM_TEX宏和TRANSFORM_UV宏

// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

// Deprecated. Used to transform 4D UV by a fixed function texture matrix. Now just returns the passed UV.
#define TRANSFORM_UV(idx) v.texcoord.xy

TRANSFORM_TEX即是用頂點中的紋理映射座標tex與待操作紋理name的Tiling(平鋪值)和Offset(偏移值)做一個運算操作。Tilling的默認值爲(1,1),Offset的默認值爲(0,0),假設指定待採樣紋理名爲_TextureName,在面板中指定的Tiling和Offset屬性的x、y值就分別對應_TextureName_ST變量中的x、y分量和z、w分量。也就是說,如果聲明一個名字爲_TextureName的sampler2D類型的紋理採樣器變量。要使用紋理的Tiling和Offset屬性,就必須同時定義一個_TextureName-ST的四維向量(float4、half4、或者fixed4)纔可以。

5.5 結構體v2f_vertex_lit

struct v2f_vertex_lit {
    float2 uv   : TEXCOORD0;
    fixed4 diff : COLOR0;
    fixed4 spec : COLOR1;
};

上述代碼段的結構體和之前代碼段中的VertexLight函數是在VertexLit渲染路徑中執行光照計算的。v2f_vertex_lit定義了一個頂點佈局格式結構體,該結構體很簡單,只用到了一層紋理,另外指定了兩種顏色,用來模擬漫反射顏色和鏡面反射顏色。

Vertex-Lit是實現最低保真度的光照且不支持實時陰影的渲染路徑,最好使用於舊機器或受限制的移動平臺上。VertexLit渲染途徑通常在一個渲染通路中渲染物體,所有光源的照明都是在頂點着色器上進行計算的。這種渲染途徑運行速度最快且有最廣泛的硬件支持。但由於所有光照都在頂點着色器中計算的,因此渲染途徑不支持大部分逐片元渲染效果,如陰影、法線貼圖等。

5.6 VertexLight函數

inline fixed4 VertexLight( v2f_vertex_lit i, sampler2D mainTex )
{
    fixed4 texcol = tex2D( mainTex, i.uv );
    fixed4 c;
    c.xyz = ( texcol.xyz * i.diff.xyz + i.spec.xyz * texcol.a );
    c.w = texcol.w * i.diff.w;
    return c;
}

VertexLight是一個簡單的頂點光照計算函數,其顏色計算方式就是用頂點漫反射顏色乘以紋理顏色,然後加上紋素的Alpha值與頂點鏡面反射顏色,兩者之和就是最終的顏色。

5.7 Parallaxoffset函數

// Calculates UV offset for parallax bump mapping
inline float2 ParallaxOffset( half h, half height, half3 viewDir )
{
    h = h * height - height/2.0;
    float3 v = normalize(viewDir);
    v.z += 0.42;
    return h * (v.xy / v.z);
}

該函數根據當前片元對應的高度圖中的高度值h,以及高度縮放係數height和切線空間中片元到攝像機的連線向量,計算到當前片元實際上要使用外觀紋理的哪一點的紋理。

5.8 Luminance函數

// Converts color to luminance (grayscale)
inline half Luminance(half3 rgb)
{
    return dot(rgb, unity_ColorSpaceLuminance.rgb);
}

該函數把一個RGB顏色值轉化成亮度值,當前的RGB顏色值基於伽馬空間或者線性空間,得到的亮度值有不同的結果。

5.9 LinearRgbToLuminance函數

//把在線性空間中的顏色RGB值轉換成亮度值
half LinearRgbToLuminance(half3 linearRgb)
{
    return dot(linearRgb, half3(0.2126729f,  0.7151522f, 0.0721750f));
}

LinearRgbToLuminance函數是把一個在線性空間中的RGB顏色值轉換成亮度值,它實質上就是把一個基於RGB顏色空間的顏色值變換到CIE1931-Yxy顏色空間中得到對應的亮度值y。

六、與HDR及光照貼圖顏色編解碼相關的工具函數

高動態範圍(HDR)光照是一種用來實現超過了顯示器所能表現的亮度範圍的渲染技術。如果採用8位通道存儲每一個顏色的RGB分量,則每個分量的亮度級別只有256種。顯然只有256個亮度級別是不足以描述自然界中的亮度差別的情況的,如太陽的亮度可能是一個白熾燈亮度的數千倍,將遠遠超出當前顯示器的亮度表示能力。

假如房間中刺眼的陽光從窗外照射進來,普通渲染方法是把陽光和白色牆的顏色都視爲白色,RGB(255,255,255),儘管同是白色,但陽光肯定比白牆刺眼的多。所以應用HDR技術對亮度進行處理,使得它們的亮度能夠體現出明顯的差異。HDR技術就是把儘可能大的亮度值範圍編碼到儘可能小的存儲空間中。

把大數字範圍編碼到小數字範圍的簡單方式,就是把大範圍中的數字乘以一個縮小系數。線性映射到小範圍上,這種方法雖然能表示的亮度範圍擴大了,但卻導致了顏色帶狀階躍的問題。

所以實際上HDR實現一般遵循以下幾步:1.在每個顏色通道是16位或者32位的浮點紋理或者渲染模板上渲染當前的場景。2.使用RGBM、LogLuv等編碼方式來節省所需的內存和帶寬。3.通過降採樣計算場景亮度。4.根據場景亮度值對場景做一個色調映射,將最終顏色值輸出到一個每通道8位的RGB格式的渲染目標上。

RGBM是一種顏色編碼方式,M(shared multiplier)。爲了解決精度不足以存儲亮度範圍信息的問題,可以創建一個精度更高的浮點渲染目標,但使用高精度的浮點渲染目標會帶來另一個問題,即需要更高的內存存儲空間和更高的帶寬,並且有些渲染硬件無法操作8位精度的渲染目標的速度去操作16位浮點渲染目標。爲了解決這個問題,需要採用一種編碼方法將這些顏色數據編碼成一個能以8位顏色分量存儲的數據。編碼方式有多種,如RGBM編碼,LogLuv編碼等。假如有一個給定的包含了RGB顏色分量的顏色值color,定義了一個定義了一個編碼後取值“最大範圍值”maxRGBM,將其編碼成一個含有R、G、B、M這4個分量的顏色值的步驟如Unity3D引擎提供的UnityEncodeRGBM函數所示。

6.1 UnityEncodeRGBM函數

half4 UnityEncodeRGBM (half3 color, float maxRGBM)
{
    float kOneOverRGBMMaxRange = 1.0 / maxRGBM;
    const float kMinMultiplier = 2.0 * 1e-2;
	//將color的RGB分量各自除以maxRGBM的倒數,然後取得最大商
    float3 rgb = color * kOneOverRGBMMaxRange;
    float alpha = max(max(rgb.r, rgb.g), max(rgb.b, kMinMultiplier));
	//用最大商乘以255得到結果值之後,取得大於這個結果值的最小整數,然後將這個值除以255之後,再賦值給變量alpha
    alpha = ceil(alpha * 255.0) / 255.0;
	//最小的multiplier控制在0.02
    // Division-by-zero warning from d3d9, so make compiler happy.
    alpha = max(alpha, kMinMultiplier);

    return half4(rgb / alpha, alpha);
}

6.2 DecodeHDR函數

inline half3 DecodeHDR (half4 data, half4 decodeInstructions)
{
	//當decodeInstruction的w分量爲true,即值爲1,要考慮HDR紋理中的alpha值對紋理的RGB值的影響,此時的alpha變量值爲紋理的alpha值。如果decodeInstruction
    //的w分量爲false,則alpha始終爲1
    // Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)
    half alpha = decodeInstructions.w * (data.a - 1.0) + 1.0;

    // If Linear mode is not supported we can skip exponent part
	//使用伽馬工作流
    #if defined(UNITY_COLORSPACE_GAMMA)
        return (decodeInstructions.x * alpha) * data.rgb;
    #else
	//使用線性工作流
	//若使用原生的HDR,則使用decodeInstructions的x分量乘以data的RGB分量即可
    #   if defined(UNITY_USE_NATIVE_HDR)
            return decodeInstructions.x * data.rgb; // Multiplier for future HDRI relative to absolute conversion.
    #   else
            return (decodeInstructions.x * pow(alpha, decodeInstructions.y)) * data.rgb;
    #   endif
    #endif
}

6.3 DecodeLightmapRGBM函數


// Decodes HDR textures
// handles dLDR, RGBM formats
// Called by DecodeLightmap when UNITY_NO_RGBM is not defined.
inline half3 DecodeLightmapRGBM (half4 data, half4 decodeInstructions)
{
    // If Linear mode is not supported we can skip exponent part
	//在伽馬工作流下
    #if defined(UNITY_COLORSPACE_GAMMA)
    # if defined(UNITY_FORCE_LINEAR_READ_FOR_RGBM)
        return (decodeInstructions.x * data.a) * sqrt(data.rgb);
    # else
        return (decodeInstructions.x * data.a) * data.rgb;
    # endif
    #else
	//在線性工作流下,以data.a爲底數,decodeInstrutions.y爲指數,求出冪,然後乘以decodeInstruction.x,作爲源顏色的解碼係數
        return (decodeInstructions.x * pow(data.a, decodeInstructions.y)) * data.rgb;
    #endif
}

該函數是把一個RGGM顏色值解碼成一個每通道8位的RGB顏色值。

6.4 DecodeLightmapDoubleLDR函數

//解碼一個用dLDR編碼的光照貼圖
inline half3 DecodeLightmapDoubleLDR( fixed4 color, half4 decodeInstructions)
{
    // decodeInstructions.x contains 2.0 when gamma color space is used or pow(2.0, 2.2) = 4.59 when linear color space is used on mobile platforms
    return decodeInstructions.x * color.rgb;
}

6.5 DecodeLightMap函數

inline half3 DecodeLightmap( fixed4 color, half4 decodeInstructions)
{
#if defined(UNITY_LIGHTMAP_DLDR_ENCODING)
    return DecodeLightmapDoubleLDR(color, decodeInstructions);
#elif defined(UNITY_LIGHTMAP_RGBM_ENCODING)
    return DecodeLightmapRGBM(color, decodeInstructions);
#else //defined(UNITY_LIGHTMAP_FULL_HDR)
    return color.rgb;
#endif
}

該函數依據UNITY_NO_RGBM是否開啓分別轉調用了dLDR版本的解碼函數和RGBM版本的解碼函數。

6.6 unity_Lightmap_HDR變量和DecodeLightmap函數

half4 unity_Lightmap_HDR;

inline half3 DecodeLightmap( fixed4 color )
{
    return DecodeLightmap( color, unity_Lightmap_HDR );
}

6.7 DecodeDirectionalLightmap函數

inline half3 DecodeDirectionalLightmap (half3 color, fixed4 dirTex, half3 normalWorld)
{
    // In directional (non-specular) mode Enlighten bakes dominant light direction
    // in a way, that using it for half Lambert and then dividing by a "rebalancing coefficient"
    // gives a result close to plain diffuse response lightmaps, but normalmapped.

    // Note that dir is not unit length on purpose. Its length is "directionality", like
    // for the directional specular lightmaps.
	//半蘭伯特光照模型
    half halfLambert = dot(normalWorld, dirTex.xyz - 0.5) + 0.5;
	//w分量用來控制該點上輻射入射度的方向性,即被dominant方向影響的程度
    return color * halfLambert / max(1e-4h, dirTex.w);
}

光照貼圖目前用的比較多而且渲染效果也不錯的實現方法是定向光照貼圖,它它是原始光照貼圖的增強實現。它主要是通過在預處理與實時還原的過程中加入場景中表面的法向量進行運算,進而增強效果。定向光照貼圖技術的大致實現方式如下所示:

1)在採樣點處把其所處的半球空間中的輻射入射度用某種方法進行採集並保存。

(2)以某種方法存儲額外的且與該輻射入射度相關的法線信息到烘焙所得的光照貼圖中。

(3)在運行時的實時渲染過程中,通過光照貼圖對片元上的場景輻射入射度,並結合光照信息和方向信息進行還原。

6.8 DecodeRealtimeLightmap函數

inline half3 DecodeRealtimeLightmap( fixed4 color )
{
	//該函數對實時生成的光照貼圖進行解碼,Enlighten中間件實時生成的光照貼圖
	//格式不同於一般的unity3d的HDR紋理
	//例如,烘焙式光照貼圖、反射光照探針、還有IBL圖像等
	//Englithen渲染器的RGBM格式紋理是在線性顏色空間中定義顏色,使用了不同的指數操作,要將其還原成RGB顏色需要做以下操作
    //@TODO: Temporary until Geomerics gives us an API to convert lightmaps to RGBM in gamma space on the enlighten thread before we upload the textures.
#if defined(UNITY_FORCE_LINEAR_READ_FOR_RGBM)
    return pow ((unity_DynamicLightmap_HDR.x * color.a) * sqrt(color.rgb), unity_DynamicLightmap_HDR.y);
#else
    return pow ((unity_DynamicLightmap_HDR.x * color.a) * color.rgb, unity_DynamicLightmap_HDR.y);
#endif
}

該函數用來對Enlighten中間件實時生成的光照貼圖進行解碼。

七、把高精度數據編碼到低精度緩衝器的函數

EncodeFloatRGBA函數是把一個在區間[0,1]內的浮點數編碼成一個float4類型的RGBA值。這些RGBA值雖然使用float4類型存儲,使其每通道的值也是在區間[0,1]內,通常在使用時會將其乘以255後取整爲整數。DecodeFloatRGBA是該函數的逆操作。

7.1 EncodeFloatRGBA函數和DecodeFloatRGBA函數

// Encoding/decoding [0..1) floats into 8 bit/channel RGBA. Note that 1.0 will not be encoded properly.
inline float4 EncodeFloatRGBA( float v )
{
    float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0);
    float kEncodeBit = 1.0/255.0;
    float4 enc = kEncodeMul * v;
    enc = frac (enc);
    enc -= enc.yzww * kEncodeBit;
    return enc; //返回的是每分量的浮點數數值都在區間[0,1]內的浮點數
}
//把一個float4類型的RGBA紋素值解碼成一個float類型的浮點數
inline float DecodeFloatRGBA( float4 enc )
{
    float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/16581375.0);
    return dot( enc, kDecodeDot );
}

7.2 EncodeFloatRG函數和DecodeFloatRG函數

// Encoding/decoding [0..1) floats into 8 bit/channel RG. Note that 1.0 will not be encoded properly.
inline float2 EncodeFloatRG( float v )
{
    float2 kEncodeMul = float2(1.0, 255.0);
    float kEncodeBit = 1.0/255.0;
    float2 enc = kEncodeMul * v;
    enc = frac (enc);
    enc.x -= enc.y * kEncodeBit;
    return enc;
}
inline float DecodeFloatRG( float2 enc )
{
    float2 kDecodeDot = float2(1.0, 1/255.0);
    return dot( enc, kDecodeDot );
}

相比上面的函數,該函數只是使用了兩個通道去進行編碼。

7.3 EncodeDepthNormal函數和DecodeDepthNormal函數

inline float4 EncodeDepthNormal( float depth, float3 normal )
{
    float4 enc;
    enc.xy = EncodeViewNormalStereo (normal);
    enc.zw = EncodeFloatRG (depth);
    return enc;
}

inline void DecodeDepthNormal( float4 enc, out float depth, out float3 normal )
{
    depth = DecodeFloatRG (enc.zw);
    normal = DecodeViewNormalStereo (enc);
}

EncodeDepthNormal函數調用EncodeViewNormalStereo函數把flaot3類型的法線編碼到一個float4類型的前兩個分量,調用EncodeFloatRG函數把深度值編碼進一個float4類型分量的後兩個分量。DecodeDepth則是EncodeDepthNormal函數的逆操作。

 

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