以前一直沒有本身實現過陰影,只是概念上有所瞭解,此次經過Demo進行實際編寫操做。app
總的來講沒有什麼能夠優化的,卻是對於窗戶這種可用面片代替的物體彷佛能優化到貼圖上,以前arm有個象棋屋的demo作過這個編輯器
來講回Shadowmap,主要思想是經過深度圖可獲得世界座標位置,因此光源位置渲染一張場景深度圖以獲得光源位置像素點的世界座標,ide
再對比主相機的像素點世界座標,若是兩個世界座標距離小於偏差則說明二者都能看見這個點,則這個點不在陰影內,不然在陰影區域內函數
固然實際作起來有許多更高效的作法。而A相機內的像素點如何切換到B相機這樣的問題,能夠經過投影變換來實現,也就是學習
Camera.main.WorldToViewportPoint。將世界座標位置轉換爲視口座標,0-1的範圍,直接適用於貼圖採樣。優化
算是講的比較簡單直白,網上一些操做我都省了,那麼來看看具體的操做步驟。spa
1.在光源子節點上掛載一個相機,用正交顯示便可,但光源相機要可看見待投射陰影對象的模型。而後給這個光源相機掛載相機渲染深度的腳本,這裏偷懶直接用OnRenderImage。code
public class LightShadowMapFilter : MonoBehaviour { public Material mat; void Awake() { GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth; } void OnRenderImage(RenderTexture source, RenderTexture destination) { Graphics.Blit(source, destination, mat); } }
2.這個腳本須要一個材質球參數,這個材質球的shader即爲返回深度的Shader,但爲了方便這裏直接返回世界座標信息,以下:對象
Shader "ShadowMap/DepthRender"//渲染深度 { Properties { } SubShader { Tags { "RenderType"="Opaque" } LOD 100 ZTest Always Cull Off ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; sampler2D _CameraDepthTexture;//光源相機傳入的深度圖 #define NONE_ITEM_EPS 0.99//若是沒有渲染到物體就比較麻煩,因此設置一個EPS閾值 v2f vert(appdata_img v) { v2f o = (v2f)0; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy; return o; } float4 GetWorldPositionFromDepthValue(float2 uv, float linearDepth)//經過深度獲得世界座標位置 { float camPosZ = _ProjectionParams.y + (_ProjectionParams.z - _ProjectionParams.y) * linearDepth; float height = 2 * camPosZ / unity_CameraProjection._m11; float width = _ScreenParams.x / _ScreenParams.y * height; float camPosX = width * uv.x - width / 2; float camPosY = height * uv.y - height / 2; float4 camPos = float4(camPosX, camPosY, camPosZ, 1.0); return mul(unity_CameraToWorld, camPos); } fixed4 frag (v2f i) : SV_Target { float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); float linearDepth = Linear01Depth(rawDepth); if (linearDepth > NONE_ITEM_EPS) return 0;//若是沒有物體則返回0 return fixed4(GetWorldPositionFromDepthValue(i.uv, linearDepth).xyz, 1);//若是有物體則返回世界座標信息 } ENDCG } } }
3.編輯器內給光源相機綁定RenderTexture到RenderTarget,直接在Project面板裏建立,分辨率設置爲1024便可,光源這部分就結束了。blog
4.接下來開始處理主相機的邏輯。根據官方論壇的信息camera WorldToViewportPoint的等價實如今這兒:
https://forum.unity.com/threads/camera-worldtoviewportpoint-math.644383/
因爲合併到一個矩陣內不直觀,最後一步投影座標到NDC再到視口的操做放到shader中去處理。
那麼先處理光源深度圖和光源相機VP矩陣傳入的腳本,也就是論壇帖子裏前兩步操做,以下:
using System.Collections; using System.Collections.Generic; using System; using UnityEngine; public class ShadowMapArgumentUpdate : MonoBehaviour { public RenderTexture lightWorldPosTexture; public Camera depthCamera; void Update() { var viewMatrix = depthCamera.worldToCameraMatrix; var projMatrix = GL.GetGPUProjectionMatrix(depthCamera.projectionMatrix, false) * viewMatrix; Shader.SetGlobalTexture("_LightWorldPosTex", lightWorldPosTexture); Shader.SetGlobalMatrix("_LightProjMatrix", projMatrix); } }
注意投影矩陣要通過GL類的轉換函數處理,防止OpenGL和DX不一致。隨後這個腳本掛載至主相機或某個GameObject上均可
須要掛載的RenderTexture和深度相機就是以前建立的。
5.最後是接收Shadowmap的shader。並對兩個世界座標的像素位置進行距離上的比較,固然比較深度大小更常規一些。
Shader "ShadowMap/ShadowMapProcess" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 worldPos : TEXCOORD1; }; sampler2D _LightWorldPosTex; sampler2D _MainTex; matrix _LightProjMatrix; float4 _MainTex_ST; //https://forum.unity.com/threads/camera-worldtoviewportpoint-math.644383/ float3 Proj2ViewportPosition(float4 pos) { float3 ndcPosition = float3(pos.x / pos.w, pos.y / pos.w, pos.z / pos.w); float3 viewportPosition = float3(ndcPosition.x*0.5 + 0.5, ndcPosition.y*0.5 + 0.5, -ndcPosition.z); return viewportPosition; } 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).xyz;//世界座標 return o; } #define EPS 0.01//兩個像素點最小距離差 fixed4 frag (v2f i) : SV_Target { float4 col = tex2D(_MainTex, i.uv); float3 lightViewportPosition = Proj2ViewportPosition(mul(_LightProjMatrix, float4(i.worldPos,1))); //這個就是Camera.main.WorldToViewport的後半部分處理 float4 lightWorldPos = tex2D(_LightWorldPosTex, lightViewportPosition.xy); //既然是視口座標了直接採樣貼圖便可 if (lightWorldPos.a > 0 && distance(i.worldPos, lightWorldPos) > EPS) return col * 0.2; //兩個像素點距離大於偏差則爲陰影,固然小問題是免不了的,這個只出於學習目的。 return col;//不在陰影內則返回原始像素 } ENDCG } } }
6.完成效果以下。