景深效果的原理是,在錄影機的近裁剪平面和遠裁剪平面之間可以設定一個焦距,在這個距離所在的平面上的物體最為清晰,而這個距離之前或之後的物體成像是一種模糊狀态(根據距離逐漸模糊,最終達到最為模糊的狀态)。
在shader中,需要一張清晰的場景圖和一張模糊的場景圖,可以通過每個像素相對焦距的距離來判定這個像素最終的清晰程度。在清晰圖和模糊圖之間做關于深度變化的插值運算。
關于錄影機的近裁剪平面和遠裁剪平面,可以直接在Camera元件的屬性面闆中調節(預設的遠裁剪平面距離是1000):

模糊圖可以直接采用高斯模糊實作,具體參考:
https://www.cnblogs.com/koshio0219/p/11152534.html
清晰圖也就是未經過任何處理的渲染圖,直接就可以得到;
關鍵問題在于如何得到每個像素在錄影機近裁剪平面和遠裁剪平面之間的位置,而這個位置資訊就是渲染紋理的深度值。
在Unity中,可以不用自己計算深度值,Unity提供了直接提取錄影機深度值的宏:
float depth=SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
depth=Linear01Depth(depth);
并可以直接利用内置函數将其轉化為線性深度值(對于透視錄影機來說,原本的深度值是非線性的),以友善後續的插值計算。
在提取錄影機深度值之前,需要将錄影機的深度紋理模式設定為Depth,同時在Shader中提前聲明_CameraDepthTexture變量:
MyCamera.depthTextureMode |= DepthTextureMode.Depth;
sampler2D _CameraDepthTexture;
C#控制腳本如下:
1 using UnityEngine;
2
3 public class DepthOfFieldCrtl : ScreenEffectBase
4 {
5 private const string _BlurSize = "_BlurSize";
6 private const string _FocusDistance = "_FocusDistance";
7 private const string _BlurTex = "_BlurTex";
8
9 //用于控制高斯模糊的參數
10 [Range(.2f,3)]
11 public float blurSize = .6f;
12 [Range(0, 4)]
13 public int iterations = 3;
14 [Range(1, 8)]
15 public int downSample = 2;
16
17 //歸一化後的焦距,0表示焦距處于錄影機近裁剪平面,1表示處于遠裁剪平面;為了更好的調整效果,可以将[0,1]範圍适當擴大
18 [Range(-.02f, 1.02f)]
19 public float focusDistance = .5f;
20
21 private Camera myCamera=null;
22 public Camera MyCamera
23 {
24 get
25 {
26 if (myCamera == null)
27 {
28 myCamera = GetComponent<Camera>();
29 }
30 return myCamera;
31 }
32 }
33
34 //開啟錄影機深度模式
35 private void OnEnable()
36 {
37 MyCamera.depthTextureMode |= DepthTextureMode.Depth;
38 }
39
40 //不用時還原
41 private void OnDisable()
42 {
43 MyCamera.depthTextureMode &= ~DepthTextureMode.Depth;
44 }
45
46 private void OnRenderImage(RenderTexture source, RenderTexture destination)
47 {
48 if (Material)
49 {
50 //傳遞焦距
51 Material.SetFloat(_FocusDistance, focusDistance);
52
53 var w = source.width / downSample;
54 var h = source.height / downSample;
55
56 var buffer0 = RenderTexture.GetTemporary(w, h, 0);
57 buffer0.filterMode = FilterMode.Bilinear;
58
59 Graphics.Blit(source, buffer0);
60
61 //前兩個Pass做高斯模糊處理,結果儲存在buffer0中
62 for(int i = 0; i < iterations; i++)
63 {
64 Material.SetFloat(_BlurSize, blurSize*i+1.0f);
65
66 var buffer1 = RenderTexture.GetTemporary(w, h, 0);
67 buffer1.filterMode = FilterMode.Bilinear;
68 Graphics.Blit(buffer0, buffer1, Material, 0);
69 RenderTexture.ReleaseTemporary(buffer0);
70
71 buffer0 = RenderTexture.GetTemporary(w, h, 0);
72 Graphics.Blit(buffer1, buffer0, Material, 1);
73 RenderTexture.ReleaseTemporary(buffer1);
74 }
75
76 //最後一個Pass用原圖像和模糊圖(buffer0)進行關于深度的插值計算,具體見Shader腳本
77 Material.SetTexture(_BlurTex, buffer0);
78 Graphics.Blit(source, destination,Material,2);
79 RenderTexture.ReleaseTemporary(buffer0);
80 }
81 else
82 Graphics.Blit(source, destination);
83
84 }
85 }
複制
基類腳本見:
https://www.cnblogs.com/koshio0219/p/11131619.html
Shader腳本:
1 Shader "MyUnlit/DepthOfFiled"
2 {
3 Properties
4 {
5 _MainTex ("Texture", 2D) = "white" {}
6 }
7 SubShader
8 {
9 CGINCLUDE
10
11 #include "UnityCG.cginc"
12
13 sampler2D _MainTex;
14 sampler2D _BlurTex;
15 //聲明錄影機深度
16 sampler2D _CameraDepthTexture;
17 half4 _MainTex_TexelSize;
18 fixed _FocusDistance;
19
20 struct appdata
21 {
22 float4 vertex : POSITION;
23 float2 uv : TEXCOORD0;
24 };
25
26 struct v2f
27 {
28 half4 uv:TEXCOORD0;
29 half2 uv_depth:TEXCOORD1;
30 float4 vertex:SV_POSITION;
31 };
32
33 v2f vert(appdata v)
34 {
35 v2f o;
36 o.vertex=UnityObjectToClipPos(v.vertex);
37
38 //xy存儲清晰紋理,zw存儲模糊紋理
39 o.uv.xy=v.uv;
40 o.uv.zw=v.uv;
41 o.uv_depth=v.uv;
42
43 //需要對模糊紋理和深度紋理進行平台差異化處理
44 #if UNITY_UV_STARTS_AT_TOP
45 if(_MainTex_TexelSize.y<0){
46 o.uv.w=1.0-o.uv.w;
47 o.uv_depth.y=1.0-o.uv_depth.y;
48 }
49 #endif
50
51 return o;
52 }
53
54 fixed4 frag(v2f i):SV_Target
55 {
56 fixed4 col = tex2D(_MainTex, i.uv.xy);
57 fixed4 bcol=tex2D(_BlurTex,i.uv.zw);
58
59 //得到深度值并線性歸一化
60 float depth=SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
61 depth=Linear01Depth(depth);
62
63 //因為焦距也是設定的歸一化的值,直接相減取絕對值即得到模糊系數,傳回插值結果
64 fixed bVa=abs(depth-_FocusDistance);
65 return lerp(col,bcol,bVa);
66 }
67
68 ENDCG
69
70 ZTest Always
71 Cull Off
72 ZWrite Off
73
74 //前兩個Pass高斯模糊
75 UsePass "MyUnlit/GaussianBlur/GAUSSIANBLUR_V"
76
77 UsePass "MyUnlit/GaussianBlur/GAUSSIANBLUR_H"
78
79 Pass
80 {
81 CGPROGRAM
82
83 #pragma vertex vert
84 #pragma fragment frag
85
86 ENDCG
87 }
88 }
89 FallBack Off
90 }
複制
效果如下:
(注意調整錄影機近裁剪平面和遠裁剪平面的值處于合适的區間)