【Unity Shaders】學習筆記——SurfaceShader(九)Cubemap


【Unity Shaders】學習筆記——SurfaceShader(九)Cubemap

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

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


上一節中講述了制作Cubemap的方法。這一節講講怎么使用它。

Simple Cubemap

先來看一下最簡單的Cubemap。

Shader "Custom/SimpleReflection" 
{
    Properties 
    {
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Cubemap ("CubeMap", CUBE) = ""{}
        _ReflAmount ("Reflection Amount", Range(0.01, 1)) = 0.5
    }
    
    SubShader 
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        samplerCUBE _Cubemap;
        float4 _MainTint;
        float _ReflAmount;

        struct Input 
        {
            float2 uv_MainTex;
            float3 worldRefl;
        };

        void surf (Input IN, inout SurfaceOutput o) 
        {
            half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
            o.Emission = texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

其實就是用texCUBE()函數來對Cubemap采樣。

第二個參數應該傳入UV坐標,但實際傳入的是世界反射向量。這是Unity替我們計算了UV坐標。
Cubemap是由六張貼圖構成的,組成了一個類似天空盒的六面體,那這六張貼圖是如何映射到小球上的呢,使小球看起來像倒映着周圍的環境一樣?
將Cubemap想象成一個立方體,包裹着小球,從小球的中心點發射一條射線,穿過小球表面和立方體表面,射線與小球和立方體會各有一個交點,小球上的這個點對應的就是立方體上的這個點的紋理。那要怎樣計算各個點對應的UV坐標呢?從小球中心點發射的一條條射線其實就是法線,有個很明顯的事實就是,法線朝向法線坐標分量最大的坐標軸指向的立方體的面。也就是坐標(1,1,3)的法線朝向的是Z軸指向的立方體的面。這樣就由法線找到了點對應的面。那么UV坐標又該如何計算呢?法線另外兩個較小的坐標值和UV坐標是有關系的。舉個特殊點的例子,小球最頂點的法線的坐標應該是(0,0,1),對應的應該是立方體的頂面的中點,UV坐標應該是(0.5,0.5)。計算UV坐標的方法是(x/z×0.5+0.5,y/z×0.5+0.5),將特殊點代進去,答案是正確的。原理有點難說明,相當於把X、Y坐標投影到了對應的面上。乘0.5加0.5是為了讓法線坐標的區間從[-1,1]變為[0,1]。
在Unity里,我們不必自己計算,可以直接使用內置變量worldRefl來檢索Cubemap。

Normal Cubemap

Shader "Custom/NormalMappedReflection" 
{
    Properties 
    {
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _NormalMap ("Normal Map", 2D) = "bump" {}
        _Cubemap ("Cubemap", CUBE) = ""{}
        _ReflAmount ("Reflection Amount", Range(0,1)) = 0.5
    }
    
    SubShader
     {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf Lambert

        samplerCUBE _Cubemap;
        sampler2D _MainTex;
        sampler2D _NormalMap;
        float4 _MainTint;
        float _ReflAmount;

        struct Input 
        {
            float2 uv_MainTex;
            float2 uv_NormalMap;
            float3 worldRefl;
            INTERNAL_DATA
        };

        void surf (Input IN, inout SurfaceOutput o) 
        {
            half4 c = tex2D (_MainTex, IN.uv_MainTex);
            
            float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;
            o.Normal = normals;
            
            o.Emission = texCUBE (_Cubemap, WorldReflectionVector (IN, o.Normal)).rgb * _ReflAmount;
            o.Albedo = c.rgb * _MainTint;
            o.Alpha = c.a;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

就只是增加了法線貼圖而已。
因為法線信息的改變,所以要重新計算傳入texCUBE()函數的世界反射向量。float3 worldRefl;INTERNAL_DATA變量用於原本的法線信息不使用時,比如使用了法線貼圖,原來的法線信息就不使用了。要根據新的法線信息計算世界反射向量使用WorldReflectionVector()函數。

動態立方圖系統

有時候游戲里的物體從一個環境走到另一個環境,需要更換Cubemap,這樣顯示的反射效果才真實。
有兩種方法更換Cubemap,一種是實時更換Cubemap,這樣的效果最真實,但是要犧牲性能;第二種是當物體走到另一個環境的時候更換Cubemap,這就是我要講的方法。
方法很簡單,就是在C#里用SetTexture的方法動態更換Cubemap。

[ExecuteInEditMode]
public class SwapCubemaps : MonoBehaviour 
{
    public Cubemap cubeA;
    public Cubemap cubeB;
    
    public Transform posA;
    public Transform posB;
    
    private Material curMat;
    private Cubemap curCube;
    

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
        curMat = renderer.sharedMaterial;
        if(curMat)
        {
            curCube = CheckProbeDistance();
            curMat.SetTexture("_Cubemap", curCube);
            
        }
    }
    
    private Cubemap CheckProbeDistance() {
        float distA = Vector3.Distance(transform.position, posA.position);
        float distB = Vector3.Distance(transform.position, posB.position);
        
        if(distA < distB)
        {
            return cubeA;
        }
        else if(distB < distA)
        {
            return cubeB;
        }
        else
        {
            return cubeA;
        }
        
    }
        
    
    void OnDrawGizmos() {
        Gizmos.color = Color.green;
        
        if(posA)
        {
            Gizmos.DrawWireSphere(posA.position, 0.5f);
        }
        
        if(posB)
        {
            Gizmos.DrawWireSphere(posB.position, 0.5f);
        }
    }
}
  1. [ExecuteInEditMode]是為了讓腳本在編輯器狀態的時候也能執行,這樣就不必點擊Play調試,比較方便。

  2. OnDrawGizmos()是在Scene里畫一些可視化的東西方便調試。

  3. CheckProbeDistance()里用Distance判斷物體和A點、B點的距離,根據距離決定返回哪種Cubemap。

  4. 在Update()設置材質的Cubemap。

Cubemap效果

 

Cubemap

Cubemap

 

我有加個金屬的紋理。效果是這樣的。

 

NormalCubemap

NormalCubemap

 

這是加了法線貼圖的。


免責聲明!

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



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