質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

Q&A

解決済

1回答

1662閲覧

Unity Cg/HLSLで頂点とカメラ間の距離

donafudo

総合スコア46

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

0グッド

0クリップ

投稿2017/12/12 16:15

編集2017/12/13 10:16

UnityのShaderで、カメラとの距離に応じて画面の色合いを変える処理を実装しようとしています
その際に、カメラとカメラに移されている頂点との距離を、以下のコードで取得しようとしています

コード全文に変更

lang

1Shader "Hidden/DepthOfField" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Depth("Depth",FLOAT) = 0.8 7 } 8 SubShader 9 { 10 // No culling or depth 11 Cull Off ZWrite Off ZTest Always 12 13 Pass 14 { 15 CGPROGRAM 16 #pragma vertex vert 17 #pragma fragment frag 18 19 #include "UnityCG.cginc" 20 21 float _Depth; 22 23 struct appdata 24 { 25 float4 vertex :POSITION; 26 float2 uv : TEXCOORD0; 27 }; 28 29 struct v2f 30 { 31 float2 uv : TEXCOORD0; 32 float4 vertex : SV_POSITION; 33 float3 vertexWorldPos: TEXCOORD1; 34 }; 35 36 v2f vert (appdata v) 37 { 38 v2f o; 39 o.vertex = UnityObjectToClipPos(v.vertex); 40 o.uv = v.uv; 41 42 o.vertexWorldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 43 44 return o; 45 } 46 47 sampler2D _MainTex; 48 sampler2D _CameraDepthTexture; 49 50 fixed4 frag (v2f i) : SV_Target 51 { 52 float depth= tex2D(_CameraDepthTexture,i.uv); 53 float4 col = tex2D(_MainTex, i.uv); 54 float dist = length(i.vertexWorldPos - _WorldSpaceCameraPos); 55 56 if (dist < _Depth) { 57 return depth; 58 } 59 else { 60 return col; 61 } 62 } 63 ENDCG 64 } 65 } 66} 67

調べた限りでは、モデルマトリクスに(ローカル座標の)頂点位置を掛けると、その頂点のワールド座標が求められるので
distance関数で、カメラとの距離を求められると思ったのですが
期待した結果になりません
私が勘違いしているところや別の方法はありませんでしょうか?
現実理想
上:実行結果(カメラを動かすと描画が分かれている部分も移動する) 
下:理想(これは直線的なので距離フォグのようにしたい)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

見た感じではおかしいところはないように思います...
期待通りにならない原因の可能性として思いつくところとしては、カメラとの距離を頂点単位で求めていることですかね。これをフラグメント単位で求めてみると変化はあるでしょうか(フラグメントシェーダーにdistの代わりにvertexWorldPosを送って、フラグメントシェーダー内で_WorldSpaceCameraPosとの距離を求めてdistとするような感じで...)?
十分に細かいメッシュなら頂点単位でもいいでしょうが、たとえばカメラ正面に画面を覆うような四角形を置いたような状況を想定すると、四角形中央のdistは四隅のdistよりも小さくなるべきなのに、四隅のdistが線形補間された結果四隅と同じdistになってしまう...といったことがあるかもしれません。

[追記]
どうやらイメージエフェクト用シェーダーを作成されているようですね。でしたら、アプローチを変える必要があるかと思います。
イメージエフェクトシェーダーの段階ではすでに個々のモデルの描画は終わってしまっており、いわば「描画後の映像が貼り付けられた画面を覆う板」を描画しようとしている段階と言えると思います。バーテックスシェーダーに与えられる頂点は個々のモデルの頂点ではなく画面を覆う板の四隅であり、_WorldSpaceCameraPosとの距離を求めても有用ではないでしょう。

プランとしては、まずシェーダーに変数を追加してカメラの投影行列の逆行列を渡してやり、クリッピング座標からカメラ座標を復元できるようにして、そしてそれに渡すべきクリッピング座標のxとyはUV座標から算出、zは_CameraDepthTextureから取得した値を使う...という感じでいかがでしょう。あとは復元した座標のカメラからの距離、つまりその座標ベクトルの長さをdistとすればいけそうな気がします。

カメラ用スクリプト

C#

1using UnityEngine; 2 3[RequireComponent(typeof(Camera))] 4public class DepthOfField : MonoBehaviour 5{ 6 public Material DepthOfFieldMaterial; // パブリック変数にシェーダーのマテリアルをセットする方式にしましたが、Shader.Findで取ってきたシェーダーからマテリアルを作ってもいいでしょう 7 8 private void Start() 9 { 10 // カメラの投影行列を実行環境に合わせた形に変換し、さらにその逆行列をシェーダーに渡す 11 var cam = this.GetComponent<Camera>(); 12 var invProjMat = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false).inverse; 13 14 this.DepthOfFieldMaterial.SetMatrix("_InverseProjectionMatrix", invProjMat); 15 } 16 17 private void OnRenderImage(RenderTexture src, RenderTexture dest) { 18 Graphics.Blit(src, dest, this.DepthOfFieldMaterial); 19 } 20}

シェーダーコード

HLSL

1Shader "Effect/DepthOfField" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Depth ("Depth", FLOAT) = 0.8 // 距離しきい値0.8mとなるとかなりカメラに近い気がしますが、これは適宜調整すればいいでしょう 7 } 8 SubShader 9 { 10 // No culling or depth 11 Cull Off ZWrite Off ZTest Always 12 13 Pass 14 { 15 CGPROGRAM 16 #pragma vertex vert 17 #pragma fragment frag 18 19 #include "UnityCG.cginc" 20 21 float _Depth; 22 float4x4 _InverseProjectionMatrix; // カメラ投影行列の逆行列用に変数を追加 23 24 struct appdata 25 { 26 float4 vertex :POSITION; 27 float2 uv : TEXCOORD0; 28 }; 29 30 struct v2f 31 { 32 float2 uv : TEXCOORD0; 33 float4 vertex : SV_POSITION; 34 }; 35 36 v2f vert (appdata v) 37 { 38 v2f o; 39 o.vertex = UnityObjectToClipPos(v.vertex); 40 o.uv = v.uv; 41 42 return o; 43 } 44 45 sampler2D _MainTex; 46 sampler2D _CameraDepthTexture; 47 48 fixed4 frag (v2f i) : SV_Target 49 { 50 float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); 51 float4 col = tex2D(_MainTex, i.uv); 52 53 // クリッピング座標をカメラ投影行列の逆行列で変換すれば、カメラを原点とするフラグメントの座標が得られるはず 54 float4 position = mul(_InverseProjectionMatrix, float4(i.uv * 2 - 1, depth, 1.0)); 55 position /= position.w; 56 57 float dist = length(position.xyz); 58 59 // とりあえず、距離が_Depth以上なら真っ黒にしてみました 60 if (dist < _Depth) 61 { 62 return col; 63 } 64 else 65 { 66 return fixed4(0.0, 0.0, 0.0, col.a); 67 } 68 69 /* 70 // 別パターンとして、距離0〜_Depthでなめらかにに黒くするのも面白そうです 71 // アイディア次第でいろんな表現ができそうですね 72 return fixed4(lerp(col.rgb, 0.0, dist / _Depth), col.a); 73 */ 74 } 75 ENDCG 76 } 77 } 78}

投稿2017/12/12 17:06

編集2017/12/13 15:16
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

donafudo

2017/12/13 10:11

回答ありがとうございます。 頂点シェーダーとフラグメントシェーダーでそのような違いが出るのですね。 やってみたところ描写が変わっているところが滑らかにはなったのですが、 期待していたものにはなりませんでした、すみません。 質問にもう少し詳細な内容を追記いたしますので、もし分かったことがあればご回答していただけると幸いです。
donafudo

2017/12/13 16:12

ありがとうございます!欲しかった結果が見事にできました。 私はだいぶ勘違いをしていたみたいですね。 試しにオブジェクトのマテリアルを、勘違いしていたシェーダのマテリアルに変えると、近いと黒くなるオブジェクトができました。そういうことだったのかっていう感じです笑 ここまでしてもらって非常に図々しいことを言いますと どのような流れでHLSLやunityShaderの勉強をしたのでしょうか(書籍等)
Bongo

2017/12/13 22:24

シェーダーに関する入門記事をご覧になるとよいかと思います。検索してみると、面白そうなサイトがいろいろありますね(https://www.google.co.jp/search?q=Unity%20%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%80%E3%83%BC%E5%85%A5%E9%96%80)。 ダウンロードアーカイブ(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)。
donafudo

2017/12/14 10:07

沢山の情報をありがとうございます! 3Dグラフィックス・マニアックスなどは全体像を掴むのに良い感じのサイトですね。 ここまでしていただき本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問