[unity/shaderlab]使用世界坐標(biāo)對貼圖采樣/溶解世界代碼學(xué)習(xí)01

前言

? ? ? 這段時間在玩一款叫第七史詩的游戲,里面有一種怪物身上部分材質(zhì)不是通過正常的模型UV值進行采樣的,怪物魔性的動作和不正常的紋理采樣讓這個怪物看起來非常的喜感有趣。

第七史詩中運用非模型UV對紋理采樣

? ? ? 剛好最近有讀到關(guān)于溶解的代碼,里面也有實現(xiàn)這種效果的功能,本來想自己實現(xiàn)一下,但是還是遇到了挺多問題的。這篇文章主要也是說一下自己遇到的一些問題,也好以后回顧學(xué)習(xí)。

先貼上溶解教程的源連接


1.讓我們先實現(xiàn)一個可以貼圖片的shader把!

? ? ? 首先,我們先新建一個船新的shader文件,刪除那些多余的東西,只保存一個圖片變量,并把圖片貼在模型的uv上,代碼如下:

Shader "Custom/StandardTexture" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        Pass {
            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uvMainTex : TEXCOORD0;
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
                return fixed4(albedo, 1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

? ? ? 上面的代碼功能很簡單,就是把圖片貼在模型上。我們可以得到一個這樣的cube

一個普通的cube

2.使用世界坐標(biāo)代替模型UV對圖片進行采樣

? ? ? 因為采樣過程是在片元著色器進行的,我們要把頂點的世界坐標(biāo)傳給片元著色器。首先,在頂點輸出結(jié)構(gòu)體里添加用來存儲世界坐標(biāo)的字段。

      struct v2f {
          float4 pos : SV_POSITION;
          float2 uvMainTex : TEXCOORD0;
          float3 worldPos : TEXCOORD1;
      };

? ? ? 然后還要在頂點著色器中把計算好的世界坐標(biāo)傳給片元著色器。

      v2f vert(a2v v) {
          v2f o;
          o.pos = UnityObjectToClipPos(v.vertex);
          o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
          o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
          return o;
      }

? ? ? 最后在片元著色器中使用世界坐標(biāo)代替之前的模型uv值。

      fixed4 frag(v2f i) : SV_Target {
          fixed3 albedo = tex2D(_MainTex, i.worldPos).rgb;
          return fixed4(albedo, 1);
      }
使用世界坐標(biāo)采樣圖片的cube

? ? ? 我們可以看到,只有前后兩面是正確對圖片采樣的(其實并不能看到后面,但是后面確實是正確的),cube的頂面和左側(cè)的面拿到的是錯誤的信息,為什么會這樣呢?
? ? ? 這是因為我們在使用tex2D函數(shù)的時候,第二個參數(shù)其實需要的是一個二維的參數(shù),但是我們傳入了一個三維的世界坐標(biāo)。我們沒有指明使用世界坐標(biāo)的哪些分量對圖片進行采樣,unity默認就會使用世界坐標(biāo)的xy坐標(biāo)進行采樣。
? ? ? cube的頂面y值都是相同的,所以要使用世界坐標(biāo)的xz分量對圖片采樣我們才能拿到正確的效果。相對的,左右兩面需要使用世界坐標(biāo)的yz分量進行采樣。

3.讓cube的各個面使用不同的世界坐標(biāo)進行采樣

? ? ? 關(guān)于讓不同面使用不同坐標(biāo)進行采樣,還是通過看了網(wǎng)上的做法才有了思路,地址在這里
? ? ? 文章中使用了頂點的法線信息與固定軸向點積,得到法線在軸上的投影來決定采樣的坐標(biāo)。所以我們也通過把法線分解(點積)為三個坐標(biāo)軸方向的分量,再通過三個分量的大小對世界坐標(biāo)進行分解與組合。我們在頂點著色器中進行計算,這樣可以減少計算量。

      v2f vert(a2v v) {
          v2f o;
          o.pos = UnityObjectToClipPos(v.vertex);
          o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

          float NX = dot(v.normal, float3(1.0, 0.0, 0.0));
          float NY = dot(v.normal, float3(0.0, 1.0, 0.0));
          float NZ = dot(v.normal, float3(0.0, 0.0, 1.0));

          o.uvMainTex = o.worldPos.yz * NX + o.worldPos.zx * NY + o.worldPos.xy * NZ;

          return o;
      }

? ? ? 還要把片元著色器中原來對世界坐標(biāo)采樣的代碼修改為使用我們組合過的新UV值進行采樣。

      fixed4 frag(v2f i) : SV_Target {
          fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
          return fixed4(albedo, 1);
      }
使用新的UV對圖片進行采樣

? ? ? 這個cube看起來以切正常,各個面在移動的時候都會根據(jù)世界坐標(biāo)不同對紋理進行采樣。
? ? ? 但是,當(dāng)這個cube旋轉(zhuǎn)起來的時候,有的面就會出現(xiàn)采樣不正常的情況。


旋轉(zhuǎn)的cube

4.處理旋轉(zhuǎn)問題

? ? ? 為了查找上面的問題,我們把計算后的法線直接作為顏色輸出到cube上,檢查一下我們計算的過程是否有問題。首先,我們把上面NX,NY,NZ當(dāng)做顏色的RGB值進行輸出:


旋轉(zhuǎn)法線

? ? ? 我們發(fā)現(xiàn),物體的法線并沒有根據(jù)物體的旋轉(zhuǎn)而旋轉(zhuǎn),我個人覺得這應(yīng)該是因為旋轉(zhuǎn)了模型,對應(yīng)的旋轉(zhuǎn)了模型的頂點,而模型頂點的法線信息是存儲在頂點內(nèi)的,所以旋轉(zhuǎn)頂點并不會對模型法線造成任何影響。所以我嘗試著把模型法線轉(zhuǎn)到了世界坐標(biāo)下:

          float3 worldNormal = mul(unity_ObjectToWorld, v.normal);
          float NX = abs(dot(worldNormal, float3(1.0, 0.0, 0.0)));
          float NY = abs(dot(worldNormal, float3(0.0, 1.0, 0.0)));
          float NZ = abs(dot(worldNormal, float3(0.0, 0.0, 1.0)));

? ? ? 更改后的效果如下:


世界坐標(biāo)下旋轉(zhuǎn)法線

? ? ? 我們把全新的法線應(yīng)用到之前的代碼中,可以發(fā)現(xiàn)我們已經(jīng)一定程度上解決了之前的問題。注意上面使用了abs函數(shù),是因為點積后的結(jié)果是分正負的,如果不取絕對值會導(dǎo)致cube的三個面的值是負的,輸出到cube上的結(jié)果是黑色的。如果我們使用了取絕對值的分量,在角度達到某個值的時候旋轉(zhuǎn)會反轉(zhuǎn):


取絕對值后的旋轉(zhuǎn)

? ? ? 取消絕對值后的旋轉(zhuǎn):


正常的旋轉(zhuǎn)

? ? ? 最后附上完整的shader代碼:

Shader "Custom/StandardTexture" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        Pass {  
            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uvMainTex : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 worldNormal = mul(unity_ObjectToWorld, v.normal);

                float NX = dot(worldNormal, float3(1.0, 0.0, 0.0));
                float NY = dot(worldNormal, float3(0.0, 1.0, 0.0));
                float NZ = dot(worldNormal, float3(0.0, 0.0, 1.0));

                o.uvMainTex = o.worldPos.yz * NX + o.worldPos.zx * NY + o.worldPos.xy * NZ;

                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
                return fixed4(albedo, 1);
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

番外:基于屏幕坐標(biāo)采樣

? ? ? 得到了熱心網(wǎng)友的反饋說,Epic7中可能不是通過世界坐標(biāo)采樣的,而是通過屏幕坐標(biāo)進行采樣的。好像確實是這樣,那我們就在這里補充一下如何對屏幕坐標(biāo)進行采樣。我們可以使用unity提供的獲取屏幕坐標(biāo)的函數(shù)ComputeScreenPos(),然后再對得到的屏幕坐標(biāo)進行一下齊次運算就可以得到視口空間下的坐標(biāo)。最后使用視口坐標(biāo)對圖片采樣就可以啦。

使用屏幕坐標(biāo)進行采樣

使用屏幕坐標(biāo)進行采樣

效果就是這樣啦,最后是代碼:

            v2f vert(a2v v) {
                v2f o;
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.scrPos = ComputeScreenPos(o.pos);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                float2 wcoord = i.scrPos.xy/i.scrPos.w;
                fixed3 albedo = tex2D(_MainTex, wcoord).rgb;
                return fixed4(albedo, 1);
            }

結(jié)語

? ? ? 雖然我們在一定程度上解決了第三個問題,但是在cube旋轉(zhuǎn)到一定程度上還是會有幾個面采樣結(jié)果很奇怪的問題,在單一軸向旋轉(zhuǎn)時都會有一些問題。
? ? ? 由于我也是剛剛開始學(xué)習(xí)shader,個人水平有限,暫時還沒想到好的解決辦法,如果有好的解決方案也希望大神不吝賜教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容