一個簡單的探照燈shader

寫在前面

前段時間美術提了個需求,需要製作一個車燈慢慢照向鏡頭的效果,受到shadowGun裡面God Rays的啟發,就有了這個shader。

有興趣的可以看看樂樂女神發的這篇文章:【Unity Shaders】ShadowGun系列之二——霧和體積光

裡面很詳細的分析了ShadowGun裡面的幾個體積光shader。

最終效果如下(錄屏的時候貌似把背景音樂也錄進去了,大家將就看吧,建議關掉聲音):

https://www.zhihu.com/video/920087089012436992

原理

這個shader的原理很簡單,就是用一個模型來模擬了一個體積光,當模型朝向攝像機時,增加亮度,並且讓部分頂點做一點偏移來模擬bloom的感覺,這裡我還加入了噪音讓光線看起來變化更豐富些。

準備工作

首先需要準備一個用來模擬體積光的模型,網格如下圖所示:

其實就是一個圓形的平面,內圈用來模擬光源,外圈用來模擬體積光,我們刷入定點色來控制那些頂點是需要做變化的,如下圖所示:

頂點法線要用來判斷燈光朝向,全部垂直這個平面即可。

最後再準備一張燈光的貼圖和噪音圖(噪音圖可以參考樂樂女神的這篇文章【圖形學】談談雜訊)

在unity中的實現

shader部分比較簡單,直接上代碼:

Shader "Mya/GodRays"{ Properties { _Color("Color" , Color) = (1,1,1,1) _MainTex ("Texture", 2D) = "white" {} _Noise("Noise" , 2D) = "Black"{} _NoiseIntensity("Noise Intensity" , float) = 0.2 _NoiseWave("Noise Wave" , vector) = (-1.0,2.0,2.0,-1.0) _MaxDistance("Distance Max" , float) = 20.0 _MinDistance("Distance Min" , float) = 1.0 _ViewAngleWeight("View Angle weight" , float) = 2 _MaxBrightness("Max Brightness" , Range(0,8)) = 2 _ViewClipRange("View SoftClip Range" , Range(0,1)) = 0.5 _ViewClipNear("Clip Near" , float) = 0.3 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } LOD 100 Blend One One //Blend One OneMinusSrcColor Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; fixed4 color : COLOR; half3 normal :NORMAL; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float4 uv_noise : TEXCOORD1; float4 viewPos : TEXCOORD2; fixed4 color : COLOR; }; fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _Noise; float4 _Noise_ST; half4 _NoiseWave; half _NoiseIntensity; half _MaxDistance; half _MinDistance; half _ViewAngleWeight; half _MaxBrightness; half _ViewClipRange; half _ViewClipNear; v2f vert (appdata v) { v2f o; //攝像機空間體中心點的向量 float3 viewSpacecenterDir = -normalize(float3(UNITY_MATRIX_MV[0].w, UNITY_MATRIX_MV[1].w, UNITY_MATRIX_MV[2].w)); //攝像機空間法線 half3 viewSpaceNormalDir = normalize(mul((float3x3)UNITY_MATRIX_MV,v.normal)); //法線與攝像機的夾角 VdotL half viewAngle = viewSpaceNormalDir.z *0.5 + 0.5; half powAngle = pow(viewAngle , _ViewAngleWeight); //把頂點轉換到攝像機空間 o.viewPos = mul(UNITY_MATRIX_MV , float4(v.vertex.xyz , 1)); //頂點偏移 o.viewPos.xyz += normalize(lerp( viewSpaceNormalDir , viewSpacecenterDir ,viewAngle ))* (_MinDistance + powAngle * _MaxDistance) * v.color.a; //轉換到裁切空間 o.pos = mul(UNITY_MATRIX_P , o.viewPos ); //主紋理uv o.uv = TRANSFORM_TEX(v.uv, _MainTex); //噪音圖uv float2 noiseUV = TRANSFORM_TEX(v.uv, _Noise); o.uv_noise.xy = noiseUV + _Time.x * _NoiseWave.xy; o.uv_noise.zw = noiseUV * 0.7 + _Time.x * _NoiseWave.zw; //用觀察角度來控制亮度 o.color = powAngle * _MaxBrightness ; o.color.a = 1-v.color.a; return o; } fixed4 frag (v2f i) : SV_Target { //當前像素到攝像機的距離 half viewDistance = length(i.viewPos.xyz); //攝像機軟裁剪值 half viewSoftClip = saturate(viewDistance * _ViewClipRange - _ViewClipNear); //採樣噪音圖 half noise = (tex2D(_Noise, i.uv_noise.xy) + tex2D(_Noise, i.uv_noise.zw) )* _NoiseIntensity; //採樣主紋理 fixed4 col = tex2D(_MainTex, i.uv) * _Color; //對亮度進行擾動 col.rgb = saturate((col.rgb - noise) * i.color.rgb * i.color.a) ; return col * viewSoftClip; } ENDCG } }}

我這裡是放在攝像機空間來計算,最初是單純的按照法線方向來作頂點偏移的,後來是感覺不是很想光的感覺,所以加入了攝像機的方向來作插值,這樣會有一點光線照向攝像機的感覺。

最後用頂點和攝像機的距離來做了一點過渡,防止模型和攝像機出現明顯的穿插。

在unity中預覽一下效果:

這裡面很多計算都是全憑感覺...大家理解思路就好...


推薦閱讀:

Unity中的單例模式、回調函數、消息分發的使用區別?

TAG:shader | 游戏开发 | unity |