筆記八——利用頂點投射生成陰影

最近在喵神的專欄里看到了這個實現方法:

zhuanlan.zhihu.com/myas

自己試著寫了下看看效果,主要思想是在頂點著色器內根據光源,頂點和地面的位置關係,利用相似原理計算投影點,xz平面即為場景中的固定高度平面,即:

完整的計算思路大家也可以去參考喵神的專欄。

Shader完整代碼:

Shader "Custom/VertShadow" {Properties { _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2D) = "white" {} _BumpTex("BumpTex",2D)="bump"{} _BumpScale("BumpScale",Float)=1.0 _Specular("Specular",Color)=(1,1,1,1) _Gloss("Gloss",Range(8.0,256))=20 _LightDir("LightPos&PlaneHeight",vector)=(0,0,0,0) //這裡使用Float4類型變數存儲場景中的光源位置和地面高度 _ShadowColor("ShadowColor",Color)=(0,0,0,1) _ShadowFalloff("ShadowFalloff",Float)=1.0}SubShader { //第一個Pass正常渲染模型 Pass{ Tags{"LightMode"="ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpTex; float4 _BumpTex_ST; float _BumpScale; fixed4 _Specular; float _Gloss; struct a2v{ float4 vertex:POSITION; float3 normal:NORMAL; float4 tangent:TANGENT; float4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float4 uv:TEXCOORD0; float3 lightDir:TEXCOORD1; float3 viewDir:TEXCOORD2; }; v2f vert(a2v v){ v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.uv.xy=TRANSFORM_TEX(v.texcoord,_MainTex); o.uv.zw=TRANSFORM_TEX(v.texcoord,_BumpTex); float3 biNormal=cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w; float3x3 rotation=float3x3(v.tangent.xyz,biNormal,v.normal); o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex).xyz); o.viewDir=mul(rotation,ObjSpaceViewDir(v.vertex).xyz); return o; } fixed4 frag(v2f i):SV_Target{ fixed3 tangentLightDir=normalize(i.lightDir); fixed3 tangentViewDir=normalize(i.viewDir); fixed3 tangentNormal=UnpackNormal(tex2D(_BumpTex,i.uv.zw)); tangentNormal.xy*=_BumpScale; tangentNormal.z=sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy))); fixed3 albedo=_Color.rgb*tex2D(_MainTex,i.uv.xy); fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir)); fixed3 halfDir=normalize(tangentLightDir+tangentViewDir); fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss); return fixed4(ambient+diffuse+specular,1.0); } ENDCG } //第二個Pass計算世界空間下頂點的的陰影投影點 Pass{ //設置透明混合模式 Blend SrcAlpha OneMinusSrcAlpha //關閉深度寫入 ZWrite off //深度偏移防止陰影與地面穿插 Offset -1,0 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float4 _LightDir; float4 _ShadowColor; float4 _ShadowFalloff; struct a2v{ float4 vertex:POSITION; }; struct v2f{ float4 pos:SV_POSITION; float4 color:COLOR; }; //計算陰影投影點 float3 ShadowProjectPos(float4 vertexPos){ float3 shadowPos; //計算頂點的世界空間坐標 float3 worldPos=mul(unity_ObjectToWorld,vertexPos).xyz; //燈光方向 float3 lightDir=normalize(_LightDir.xyz); //計算陰影的世界空間坐標(如果頂點低於地面,則陰影點實際就是頂點在世界空間的位置,不做改變) shadowPos.y=min(worldPos.y,_LightDir.w); shadowPos.xz=worldPos.xz-lightDir.xz*max(0,worldPos.y-_LightDir.w)/(lightDir.y-_LightDir.w); return shadowPos; } v2f vert(a2v v){ v2f o; //得到陰影的世界空間坐標 float3 shadowPos=ShadowProjectPos(v.vertex); //將陰影點轉換到裁剪空間 o.pos=UnityWorldToClipPos(shadowPos); //得到模型在世界空間地面投影點的位置,然後與地面上的陰影點計算距離算衰減 float3 center=float3(unity_ObjectToWorld[0].w,_LightDir.w,unity_ObjectToWorld[2].w); //這裡的unity_ObjectToWorld矩陣前三行的最後一個分量存儲的是子對象在父空間下的坐標位置 float falloff=1-saturate(distance(shadowPos,center)*_ShadowFalloff); o.color=_ShadowColor; o.color.a*=falloff; return o; } fixed4 frag(v2f i):SV_Target{ return i.color; } ENDCG } } }

實例效果:

只使用第一個Pass:

使用第二個Pass後:

這種陰影效果基本能滿足要求,而且非常節約性能,只是需要注意的是場景中的主光源以及地面高度平整且不發生變化的情況下比較適用。


相關參考

《UnityShader入門精要》 馮樂樂

相關學習鏈接

關於ZTest和ZWrite :

cnblogs.com/ljx12138/p/

Unity著色器訓練營(1):

入門篇 http://forum.china.unity3d.com/thread-27522-1-1.html

小小的頂點變換能實現大大的效果

(出處: Unity官方中文論壇)

分享Shader實現思路和源代碼的專欄:

zhuanlan.zhihu.com/myas

zhuanlan.zhihu.com/Meow


推薦閱讀:

想要進遊戲公司進行開發,到底需要什麼能力呢,該不該培訓呢。。。?
用MonoDevelop寫代碼有什麼提高效率的竅門?
在上海,做U3D遊戲開發,工作5年半,期望薪水45K/月,需要怎樣的水平?
用Unity引擎開發遊戲,如何提升編程能力?
如何評價使用Unity引擎製作的《琪亞娜·極樂凈土》?

TAG:Unity游戏引擎 | shader |