teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

1

改善案を追記

2017/12/13 15:16

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -1,3 +1,115 @@
1
1
  見た感じではおかしいところはないように思います...
2
2
  期待通りにならない原因の可能性として思いつくところとしては、カメラとの距離を頂点単位で求めていることですかね。これをフラグメント単位で求めてみると変化はあるでしょうか(フラグメントシェーダーにdistの代わりにvertexWorldPosを送って、フラグメントシェーダー内で_WorldSpaceCameraPosとの距離を求めてdistとするような感じで...)?
3
- 十分に細かいメッシュなら頂点単位でもいいでしょうが、たとえばカメラ正面に画面を覆うような四角形を置いたような状況を想定すると、四角形中央のdistは四隅のdistよりも小さくなるべきなのに、四隅のdistが線形補間された結果四隅と同じdistになってしまう...といったことがあるかもしれません。
3
+ 十分に細かいメッシュなら頂点単位でもいいでしょうが、たとえばカメラ正面に画面を覆うような四角形を置いたような状況を想定すると、四角形中央のdistは四隅のdistよりも小さくなるべきなのに、四隅のdistが線形補間された結果四隅と同じdistになってしまう...といったことがあるかもしれません。
4
+
5
+ [追記]
6
+ どうやらイメージエフェクト用シェーダーを作成されているようですね。でしたら、アプローチを変える必要があるかと思います。
7
+ イメージエフェクトシェーダーの段階ではすでに個々のモデルの描画は終わってしまっており、いわば「描画後の映像が貼り付けられた画面を覆う板」を描画しようとしている段階と言えると思います。バーテックスシェーダーに与えられる頂点は個々のモデルの頂点ではなく画面を覆う板の四隅であり、_WorldSpaceCameraPosとの距離を求めても有用ではないでしょう。
8
+
9
+ プランとしては、まずシェーダーに変数を追加してカメラの投影行列の逆行列を渡してやり、クリッピング座標からカメラ座標を復元できるようにして、そしてそれに渡すべきクリッピング座標のxとyはUV座標から算出、zは_CameraDepthTextureから取得した値を使う...という感じでいかがでしょう。あとは復元した座標のカメラからの距離、つまりその座標ベクトルの長さをdistとすればいけそうな気がします。
10
+
11
+ カメラ用スクリプト
12
+ ```C#
13
+ using UnityEngine;
14
+
15
+ [RequireComponent(typeof(Camera))]
16
+ public class DepthOfField : MonoBehaviour
17
+ {
18
+ public Material DepthOfFieldMaterial; // パブリック変数にシェーダーのマテリアルをセットする方式にしましたが、Shader.Findで取ってきたシェーダーからマテリアルを作ってもいいでしょう
19
+
20
+ private void Start()
21
+ {
22
+ // カメラの投影行列を実行環境に合わせた形に変換し、さらにその逆行列をシェーダーに渡す
23
+ var cam = this.GetComponent<Camera>();
24
+ var invProjMat = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false).inverse;
25
+
26
+ this.DepthOfFieldMaterial.SetMatrix("_InverseProjectionMatrix", invProjMat);
27
+ }
28
+
29
+ private void OnRenderImage(RenderTexture src, RenderTexture dest) {
30
+ Graphics.Blit(src, dest, this.DepthOfFieldMaterial);
31
+ }
32
+ }
33
+ ```
34
+
35
+ シェーダーコード
36
+ ```HLSL
37
+ Shader "Effect/DepthOfField"
38
+ {
39
+ Properties
40
+ {
41
+ _MainTex ("Texture", 2D) = "white" {}
42
+ _Depth ("Depth", FLOAT) = 0.8 // 距離しきい値0.8mとなるとかなりカメラに近い気がしますが、これは適宜調整すればいいでしょう
43
+ }
44
+ SubShader
45
+ {
46
+ // No culling or depth
47
+ Cull Off ZWrite Off ZTest Always
48
+
49
+ Pass
50
+ {
51
+ CGPROGRAM
52
+ #pragma vertex vert
53
+ #pragma fragment frag
54
+
55
+ #include "UnityCG.cginc"
56
+
57
+ float _Depth;
58
+ float4x4 _InverseProjectionMatrix; // カメラ投影行列の逆行列用に変数を追加
59
+
60
+ struct appdata
61
+ {
62
+ float4 vertex :POSITION;
63
+ float2 uv : TEXCOORD0;
64
+ };
65
+
66
+ struct v2f
67
+ {
68
+ float2 uv : TEXCOORD0;
69
+ float4 vertex : SV_POSITION;
70
+ };
71
+
72
+ v2f vert (appdata v)
73
+ {
74
+ v2f o;
75
+ o.vertex = UnityObjectToClipPos(v.vertex);
76
+ o.uv = v.uv;
77
+
78
+ return o;
79
+ }
80
+
81
+ sampler2D _MainTex;
82
+ sampler2D _CameraDepthTexture;
83
+
84
+ fixed4 frag (v2f i) : SV_Target
85
+ {
86
+ float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
87
+ float4 col = tex2D(_MainTex, i.uv);
88
+
89
+ // クリッピング座標をカメラ投影行列の逆行列で変換すれば、カメラを原点とするフラグメントの座標が得られるはず
90
+ float4 position = mul(_InverseProjectionMatrix, float4(i.uv * 2 - 1, depth, 1.0));
91
+ position /= position.w;
92
+
93
+ float dist = length(position.xyz);
94
+
95
+ // とりあえず、距離が_Depth以上なら真っ黒にしてみました
96
+ if (dist < _Depth)
97
+ {
98
+ return col;
99
+ }
100
+ else
101
+ {
102
+ return fixed4(0.0, 0.0, 0.0, col.a);
103
+ }
104
+
105
+ /*
106
+ // 別パターンとして、距離0〜_Depthでなめらかにに黒くするのも面白そうです
107
+ // アイディア次第でいろんな表現ができそうですね
108
+ return fixed4(lerp(col.rgb, 0.0, dist / _Depth), col.a);
109
+ */
110
+ }
111
+ ENDCG
112
+ }
113
+ }
114
+ }
115
+ ```