【Unity Shaders】學習筆記——SurfaceShader(十一)光照模型


【Unity Shaders】學習筆記——SurfaceShader(十一)光照模型

轉載請注明出處:http://www.cnblogs.com/-867259206/p/5664792.html

  1. 如果你想從零開始學習Unity Shader,那么你可以看看本系列的文章入門,你只需要稍微有點編程的概念就可以。

  2. 水平有限,難免有謬誤之處,望指出。


LitSphere(Matcap)

發光球體光照模型就是將發光球體的紋理映射在球體上,來實現光照效果。這可以創造一些效果細膩的發光球體效果,但是它不受光照影響,改變光照的方向,球體的光照效果不變。如果要在固定視角的場景里制作細膩的球體光照,這會是一個不錯的選擇。
准備小球紋理貼圖:


  1. 定義Properties:

    Properties {  
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1)  
        _MainTex ("Base (RGB)", 2D) = "white" {}  
        _NormalMap ("Normal Map", 2D) = "bump" {}  
    }  
  1. 編寫預編譯命令:

        #pragma surface surf Unlit vertex:vert 
        #pragma target 3.0
  1. 在subshader里關聯properties:

        float4 _MainTint;  
        sampler2D _MainTex;  
        sampler2D _NormalMap;  
  1. 定義光照函數Unlit:

        inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)  
        {  
            half4 c = half4(1,1,1,1);  
            c.rgb = s.Albedo;  
            c.a = s.Alpha;  
            return c;  
        } 

這是一個無光照的光照函數,因為我們要用紋理上的光照效果,所以不需要計算光照。

  1. 定義Input結構體:

        struct Input {  
            float2 uv_MainTex;  
            float2 uv_NormalMap;  
            float3 tan1;  
            float3 tan2;  
        }; 
  1. 定義頂點函數:

        void vert (inout appdata_full v, out Input o)   
        {  
            UNITY_INITIALIZE_OUTPUT(Input,o);  
            
            TANGENT_SPACE_ROTATION;  
            o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);  
            o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);  
        } 

解釋:
NITY_INITIALIZE_OUTPUT(Input,o);在HLSLSupport.cginc文件里是這樣定義的:

#if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
#define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
#define UNITY_INITIALIZE_OUTPUT(type,name)
#endif

如果是HLSL編譯器或某些版本的Shader API,則將變量賦值為0,否則什么都不做。這樣編譯器就不會報錯說變量未初始化。
TANGENT_SPACE_ROTATION;是Unity提供的一個宏,它定義了一個rotation矩陣用於從Object Space變換到Tangent Space。它在UnityCG.cginc里的定義如下:

// 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 ));

binormal是切空間的Y軸,tangent是切空間的X軸,normal是切空間的Z軸。為什么它定義了一個切空間旋轉看這篇
UNITY_MATRIX_IT_MV是Object Space(or Model Space)變換到View Space(or Eye Space)的矩陣的逆轉置矩陣。View Space以攝像機為原點,攝像機朝向為Z軸。[]是取出矩陣的一列,UNITY_MATRIX_IT_MV[0].xyz是說將向量(1,0,0)(即X軸)左乘MV矩陣的逆轉置矩陣。UNITY_MATRIX_IT_MV[1].xyz則是向量(0,1,0)(即Y軸)左乘MV矩陣的逆轉置矩陣。
順便科普一下,OpenGL是列向量,變換矩陣應該左乘向量。如果要將法線從Object Space變換到View Space是不能用MV矩陣的,原因看這篇博文。要將法線從Object Space變換到View Space要用MV矩陣的逆轉置矩陣。那么,反過來,如果要將法線從View Space變換到Object Space只要將MV矩陣的逆轉置矩陣右乘法線即可。這里的代碼可以理解為將View Space的X軸和Y軸變換到了Object Space。變換到了Object Space以后再乘rotation變換到Tangent Space。

  1. 最后,編寫表面處理函數:

        void surf (Input IN, inout SurfaceOutput o)   
        {  
            float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));  
            o.Normal = normals;  
  
            float2 litSphereUV;  
            litSphereUV.x = dot(IN.tan1, o.Normal);  
            litSphereUV.y = dot(IN.tan2, o.Normal);  
          
            half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);  
            o.Albedo = c.rgb * _MainTint;  
            o.Alpha = c.a;  
        }  

這部分需要的解釋不多。在表面處理函數里將法線和tan1、tan2點乘,相當於是把法線投影到了View Space的X軸和Y軸上。后面就是將法線當作UV來采樣。乘0.5加0.5是把區間變到[0,1]。


LitSphere有點像把我們能看見的小球的那一面當作一層皮扒下來,然后平鋪在紋理上,使紋理上的球嚴絲合縫地投影在了小球上。效果如下:

 

LitSphere

LitSphere

如果我們用紋理本身的UV坐標的話,小球圖片是矩形,四周還有黑邊,那么采樣到小球上會是一個變形的四周有黑邊的圓球圖案。相當於是把畫了個球的紋理貼在球身上。而用法線作UV的話,就把紋理上有球的那部分映射到了我們看得見的球的表面。
我覺得把這理解為把球的表面投影到紋理上更好理解一點。這樣要把法線從切空間變換到視空間,每個頂點都要變換,計算量太大,所以換過來,轉換到切空間計算會cheap一點。
這個光照模型也叫Matcap(Material Capture)。
這個光照模型只能用於比較圓的物體,比如Sphere和Capsule,Cube是不能用的,因為Cube能看見的法線就只有三個,所以沒辦法使用這個光照模型。
另一個版本的Matcap

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM