UnityのShaderで、カメラとの距離に応じて画面の色合いを変える処理を実装しようとしています
その際に、カメラとカメラに移されている頂点との距離を、以下のコードで取得しようとしています
コード全文に変更
Shader "Hidden/DepthOfField"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Depth("Depth",FLOAT) = 0.8
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Depth;
struct appdata
{
float4 vertex :POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 vertexWorldPos: TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.vertexWorldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return o;
}
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
fixed4 frag (v2f i) : SV_Target
{
float depth= tex2D(_CameraDepthTexture,i.uv);
float4 col = tex2D(_MainTex, i.uv);
float dist = length(i.vertexWorldPos - _WorldSpaceCameraPos);
if (dist < _Depth) {
return depth;
}
else {
return col;
}
}
ENDCG
}
}
}
調べた限りでは、モデルマトリクスに(ローカル座標の)頂点位置を掛けると、その頂点のワールド座標が求められるので
distance関数で、カメラとの距離を求められると思ったのですが
期待した結果になりません
私が勘違いしているところや別の方法はありませんでしょうか?
上:実行結果(カメラを動かすと描画が分かれている部分も移動する)
下:理想(これは直線的なので距離フォグのようにしたい)
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
見た感じではおかしいところはないように思います...
期待通りにならない原因の可能性として思いつくところとしては、カメラとの距離を頂点単位で求めていることですかね。これをフラグメント単位で求めてみると変化はあるでしょうか(フラグメントシェーダーにdistの代わりにvertexWorldPosを送って、フラグメントシェーダー内で_WorldSpaceCameraPosとの距離を求めてdistとするような感じで...)?
十分に細かいメッシュなら頂点単位でもいいでしょうが、たとえばカメラ正面に画面を覆うような四角形を置いたような状況を想定すると、四角形中央のdistは四隅のdistよりも小さくなるべきなのに、四隅のdistが線形補間された結果四隅と同じdistになってしまう...といったことがあるかもしれません。
[追記]
どうやらイメージエフェクト用シェーダーを作成されているようですね。でしたら、アプローチを変える必要があるかと思います。
イメージエフェクトシェーダーの段階ではすでに個々のモデルの描画は終わってしまっており、いわば「描画後の映像が貼り付けられた画面を覆う板」を描画しようとしている段階と言えると思います。バーテックスシェーダーに与えられる頂点は個々のモデルの頂点ではなく画面を覆う板の四隅であり、_WorldSpaceCameraPosとの距離を求めても有用ではないでしょう。
プランとしては、まずシェーダーに変数を追加してカメラの投影行列の逆行列を渡してやり、クリッピング座標からカメラ座標を復元できるようにして、そしてそれに渡すべきクリッピング座標のxとyはUV座標から算出、zは_CameraDepthTextureから取得した値を使う...という感じでいかがでしょう。あとは復元した座標のカメラからの距離、つまりその座標ベクトルの長さをdistとすればいけそうな気がします。
カメラ用スクリプト
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class DepthOfField : MonoBehaviour
{
public Material DepthOfFieldMaterial; // パブリック変数にシェーダーのマテリアルをセットする方式にしましたが、Shader.Findで取ってきたシェーダーからマテリアルを作ってもいいでしょう
private void Start()
{
// カメラの投影行列を実行環境に合わせた形に変換し、さらにその逆行列をシェーダーに渡す
var cam = this.GetComponent<Camera>();
var invProjMat = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false).inverse;
this.DepthOfFieldMaterial.SetMatrix("_InverseProjectionMatrix", invProjMat);
}
private void OnRenderImage(RenderTexture src, RenderTexture dest) {
Graphics.Blit(src, dest, this.DepthOfFieldMaterial);
}
}
シェーダーコード
Shader "Effect/DepthOfField"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Depth ("Depth", FLOAT) = 0.8 // 距離しきい値0.8mとなるとかなりカメラに近い気がしますが、これは適宜調整すればいいでしょう
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Depth;
float4x4 _InverseProjectionMatrix; // カメラ投影行列の逆行列用に変数を追加
struct appdata
{
float4 vertex :POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
fixed4 frag (v2f i) : SV_Target
{
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
float4 col = tex2D(_MainTex, i.uv);
// クリッピング座標をカメラ投影行列の逆行列で変換すれば、カメラを原点とするフラグメントの座標が得られるはず
float4 position = mul(_InverseProjectionMatrix, float4(i.uv * 2 - 1, depth, 1.0));
position /= position.w;
float dist = length(position.xyz);
// とりあえず、距離が_Depth以上なら真っ黒にしてみました
if (dist < _Depth)
{
return col;
}
else
{
return fixed4(0.0, 0.0, 0.0, col.a);
}
/*
// 別パターンとして、距離0〜_Depthでなめらかにに黒くするのも面白そうです
// アイディア次第でいろんな表現ができそうですね
return fixed4(lerp(col.rgb, 0.0, dist / _Depth), col.a);
*/
}
ENDCG
}
}
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 91.05%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/12/13 19:11
頂点シェーダーとフラグメントシェーダーでそのような違いが出るのですね。
やってみたところ描写が変わっているところが滑らかにはなったのですが、
期待していたものにはなりませんでした、すみません。
質問にもう少し詳細な内容を追記いたしますので、もし分かったことがあればご回答していただけると幸いです。
2017/12/14 01:12
私はだいぶ勘違いをしていたみたいですね。
試しにオブジェクトのマテリアルを、勘違いしていたシェーダのマテリアルに変えると、近いと黒くなるオブジェクトができました。そういうことだったのかっていう感じです笑
ここまでしてもらって非常に図々しいことを言いますと
どのような流れでHLSLやunityShaderの勉強をしたのでしょうか(書籍等)
2017/12/14 07:24
ダウンロードアーカイブ(https://unity3d.com/jp/get-unity/download/archive)で入手できる、Unityのビルトインシェーダーのソースコードを見てみるのもご参考になるかと思います。
より発展的な内容となると、Unityに限定せずに3Dグラフィックス関連のサイトや書籍を探してみるといいかと思います。私の場合ですと、以前OpenGLで3D描画に挑戦してみたことがあり、シェーダーの概念や動作の流れ、応用例などにについてはその時の知識が助けになりました。
Webサイトでは、少々古いですが【連載】3Dグラフィックス・マニアックス | マイナビニュース(https://news.mynavi.jp/series/graphics)などは面白いと思いました。
書籍ですと、こちらも古い本ですみませんが(さらに価格も少々高めですが)GPU GemsシリーズやGame Programming Gemsシリーズも役立ちそうです。ですがこれらは基礎的な部分よりも様々な応用例の紹介が多く、入門時点では少々とっつきにくいかもしれません。
GPU Gemsシリーズの方は、英語版でしたらWebサイト上で無償で読めるようです(https://developer.nvidia.com/gpugems/GPUGems/gpugems_pref01.html)。
2017/12/14 19:07
3Dグラフィックス・マニアックスなどは全体像を掴むのに良い感じのサイトですね。
ここまでしていただき本当にありがとうございました。