Unity——UI光照(模擬光照)

前言
????????沒錯,這是UI光照不是2D光照。是Image不是SpriteRenderer。
????????本文的內容更偏向于花活和騷操作,為了不耽誤讀者的時間,請?zhí)崆爸獣裕?/span>
????????不管是Buind-In管線還是URP管線下,可能都沒有特別提供針對UI的光照功能,可能也確實沒有必要,但是本著“玩”技術的心理,我在這里給出一個UI模擬光照的“魔道功法”。
????????其實就是Shader 實現經驗型光照模型的衍生,因為本文屬于Shader光照部分的衍生,所以本文并不會非常詳細的講解光照相關的知識,那部分知識可能會單獨做一期,詳細的講解知識點以及計算過程。
????????老規(guī)矩!此方案是否具有實用性,各位讀者請自行衡量。
????????作者也認為實用性較小,所以當做一個騷操作來寫。
???????? 先來看效果:



????????如果部分讀者不太了解Shader方面的知識也沒有關系,因為是個花活,用的的東西也比較簡單,閱讀完本文也是可以實現的。
????????最后會給出完整的Shader代碼。
思路
????????單純讓Image模擬光照的思路其實比較簡單,就是將3D模型實現光照的那一套挪用到UI上即可,修改的地方相當少。比如我示例中使用的就是【蘭伯特(Lambert)光照模型】。
????????至于為什么采用蘭伯特光照模型,那是因為蘭伯特光照模型比較簡單。
實現
????????蘭伯特光照模型是一個經驗光照模型。他不符合真正的物理,只是看起來可能是對的,根據計算機圖形學第一定律:(⊙o⊙)…。

蘭伯特光照模型: Diffuse=Ambient+Kd*LightColor*Dot(N,L)
Diffuse:最終漫反射光強(顏色)????????????Ambient:環(huán)境光強(環(huán)境色)
Kd:材質對光的反射系數???????????????????????LightColor:燈光的顏色
N:頂點法線向量???????????????????????????????????L:頂點到光源位置的單位向量

????????如果對光照這部分不太了解的讀者,可以先理解為L與N形成的夾角越小,那就表示更亮,反之就更暗,在90°及以上的時候我們認為當前點不接受光照。
????????知道了思路,實現起來就很簡單了,我們直接動手開始寫Shader。
????????我們這里只寫最簡的功能,把貼圖渲染出來,支持光照。其他的功能比如什么調整Color,圖片透明,還有因為引入cginc導致變體變多等問題因為與本文無關就不處理了。
????????我們要用到編輯器為我們提供的一些數據,需要引用 #include "Lighting.cginc"
? ??????環(huán)境色:fixed4 Ambient=unity_AmbientSky;
????????反射系數:這里用1,half Kd=1;
????????燈光顏色:fixed4 LightColor=_LightColor0;
????????N法線:這里只考慮最簡單情況,讓法線面向【前】fixed3 N=fixed3(0,0,-1);
????????L光源單位向量:fixed3 L=_WorldSpaceLightPos0;
????????然后只需要將以上數值帶入公式,就可以了。
????????fixed4 Diffuse=Ambient+Kd*LightColor*max(0,dot(N,L));
????????為了避免一些因為負值導致的問題,我們使用Max()限制一下范圍。
部分代碼
? ? Properties
? ? {
????????//為了與UI的Sprite對接,圖片的名字盡量不要動
? ? ? ?_MainTex ("Texture", 2D) = "white" {}
? ? }
? ? SubShader
? ? {
? ? ? ? Tags { "RenderType"="Transparent" }
? ? ? ? Pass
? ? ? ? {
? ? ? ? ? ? Tags{"LightMode"="ForwardBase"}
? ? ? ? ? ? CGPROGRAM
? ? ? ? ? 略?......
? ? ? ? ? ? #include "Lighting.cginc"
????????????略......
? ? ? ? ? ? fixed4 frag (v2f i) : SV_Target
? ? ? ? ? ? {
? ? ? ? ? ? ? ? fixed4 col = tex2D(_MainTex, i.uv);
? ? ? ? ? ? ? ? fixed4 Ambient=unity_AmbientSky;
? ? ? ? ? ? ? ? half Kd=1;
? ? ? ? ? ? ? ? fixed4 LightColor=_LightColor0;
? ? ? ? ? ? ? ? fixed3 N=fixed3(0,0,-1);
? ? ? ? ? ? ? ? fixed3 L=_WorldSpaceLightPos0;
? ? ? ? ? ? ? ? fixed4 Diffuse=Ambient+Kd*LightColor*max(0,dot(N,L));
? ? ? ? ? ? ? ? col*=Diffuse;
? ? ? ? ? ? ? ? return col;
? ? ? ? ? ? }
? ? ? ? ? ? ENDCG
? ? ? ? }
? ? }
現在平行光就已經實現了。
點光源與聚光燈
????????平行光和點光源與聚光燈不同,點光源與聚光燈發(fā)出的光會隨著距離做出衰減,距離光源越遠,光強越弱,這個衰減并不是一個線性的。這個衰減可以自己計算也可以使用Unity提供的方法UNITY_LIGHT_ATTENUATION(“返回的衰減值”,“頂點世界坐標”)
我們以點光源為例,看一下返回的衰減值是個什么樣子的。


? ? ? ? 我們要做的就是把這個衰減值*燈光顏色附加到圖像上。就可以得到最后的結果。
????????我們在第二Pass中實現效果。此處要用到#include "AutoLight.cginc"空間中的方法。

完整代碼
Shader "UI/UILight"
{
? ? Properties
? ? {
? ? ? ? [PerRendererData]_MainTex ("Texture", 2D) = "white" {}
? ? }
? ? SubShader
? ? {
? ? ? ? Tags { "RenderType"="Transparent" }
? ? ? ? Pass
? ? ? ? {
? ? ? ? ? ? Tags{"LightMode"="ForwardBase"}
? ? ? ? ? ? CGPROGRAM
? ? ? ? ? ? #pragma vertex vert
? ? ? ? ? ? #pragma fragment frag
? ? ? ? ? ? #include "UnityCG.cginc"
? ? ? ? ? ? #include "Lighting.cginc"
? ? ? ? ? ? struct appdata
? ? ? ? ? ? {
? ? ? ? ? ? ? ? float4 vertex : POSITION;
? ? ? ? ? ? ? ? float2 uv : TEXCOORD0;
? ? ? ? ? ? };
? ? ? ? ? ? struct v2f
? ? ? ? ? ? {
? ? ? ? ? ? ? ? float2 uv : TEXCOORD0;
? ? ? ? ? ? ? ? float4 vertex : SV_POSITION;
? ? ? ? ? ? };
? ? ? ? ? ? sampler2D _MainTex;
? ? ? ? ? ? float4 _MainTex_ST;
? ? ? ? ? ? v2f vert (appdata v)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? v2f o;
? ? ? ? ? ? ? ? o.vertex = UnityObjectToClipPos(v.vertex);
? ? ? ? ? ? ? ? o.uv = TRANSFORM_TEX(v.uv, _MainTex);
? ? ? ? ? ? ? ? return o;
? ? ? ? ? ? }
? ? ? ? ? ? fixed4 frag (v2f i) : SV_Target
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? ? ? fixed4 col = tex2D(_MainTex, i.uv);
? ? ? ? ? ? ? ? clip(col.w-0.01);
? ? ? ? ? ? ? ? fixed4 Ambient=unity_AmbientSky;
? ? ? ? ? ? ? ? half Kd=1;
? ? ? ? ? ? ? ? fixed4 LightColor=_LightColor0;
? ? ? ? ? ? ? ? fixed3 N=fixed3(0,0,-1);
? ? ? ? ? ? ? ? fixed3 L=_WorldSpaceLightPos0;
? ? ? ? ? ? ? ? fixed4 Diffuse=Ambient+Kd*LightColor*max(0,dot(N,L));
? ? ? ? ? ? ? ? col*=Diffuse;
? ? ? ? ? ? ? ? return col;
? ? ? ? ? ? }
? ? ? ? ? ? ENDCG
? ? ? ? }
? ? ? ? ? ?Pass
? ? ? ? {
? ? ? ? ? ? Tags{"LightMode"="ForwardAdd"}
? ? ? ? ? ? Blend One One
? ? ? ? ? ? CGPROGRAM
? ? ? ? ? ?#pragma vertex vert
? ? ? ? ? ? #pragma fragment frag
? ? ? ? ? ? #pragma multi_compile_fwdadd
? ? ? ? ? ? #include "UnityCG.cginc"
? ? ? ? ? ? #include "Lighting.cginc"
? ? ? ? ? ? #include "AutoLight.cginc"
? ? ? ? ? ? struct appdata
? ? ? ? ? ? {
? ? ? ? ? ? ? ? float4 vertex : POSITION;
? ? ? ? ? ? ? ? float2 uv : TEXCOORD0;
? ? ? ? ? ? ? ? float3 normal:NORMAL;
? ? ? ? ? ? };
? ? ? ? ? ? struct v2f
? ? ? ? ? ? {
? ? ? ? ? ? ? ? ?float2 uv : TEXCOORD0;
? ? ? ? ? ? ? ? float4 vertex : SV_POSITION;
? ? ? ? ? ? ? ? float3 worldPos:TEXCOORD2;
? ? ? ? ? ? };
? ? ? ? ? ? sampler2D _MainTex;
? ? ? ? ? ? float4 _MainTex_ST;
? ? ? ? ? ? v2f vert (appdata v)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? v2f o;
? ? ? ? ? ? ? ? o.vertex = UnityObjectToClipPos(v.vertex);
? ? ? ? ? ? ? ? o.uv = TRANSFORM_TEX(v.uv, _MainTex);
? ? ? ? ? ? ? ? o.worldPos=mul(unity_ObjectToWorld,v.vertex);
? ? ? ? ? ? ? ? return o;
? ? ? ? ? ? }
? ? ? ? ? ? fixed4 frag (v2f i) : SV_Target
? ? ? ? ? ? {
? ? ? ? ? ? ? ? fixed4 col = tex2D(_MainTex, i.uv);
? ? ? ? ? ? ? ? UNITY_LIGHT_ATTENUATION(atten,0,i.worldPos)
? ? ? ? ? ? ? ? fixed4 LightColor=_LightColor0*atten;
? ? ? ? ? ? ? ? return col*LightColor;
? ? ? ? ? ? }
? ? ? ? ? ? ENDCG
? ? ? ? }
? ? }
}
現在就已經實現了我一開始那三張圖的效果了。

????????最后可能會出現一些Scene和Game窗口顯示不一樣的情況。比如在平行光是正常的,但是點光源和聚光燈不正常。還有就是這種方式的開銷相對來說會變大。


可以將Canvas的渲染模式(Render Mode)改為Screen Space-Camera。新建一個相機,讓他只渲染UI層,為了讓UI顯示在模型之前,相機的Depth調高,并且將Clear Flags設置為Depth Only。


???????

?????????到這里文章的內容就全部結束。
????????本期的內容是個騷操作,目的是與各位讀者分享一下有趣的操作。因為本期的定位不是[教程]系列,可能會有些不是仔細,希望各位讀者理解。
????????如果本文對您有一絲絲的幫助請賞給作者一個贊好嗎。
????????最后想說一點,以后我打算每期專欄配一個演示視頻,可能會更好的幫助有需要的讀者,能讓有需要的讀者更直觀的看到我的操作過程。
????????我們下期再見。