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

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

ただいまの
回答率

90.87%

  • Unity

    3361questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • HLSL

    13questions

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

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 526

donafudo

score 38

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

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
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/13 19:11

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

    キャンセル

  • 2017/12/14 01:12

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

    キャンセル

  • 2017/12/14 07: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)。

    キャンセル

  • 2017/12/14 19:07

    沢山の情報をありがとうございます!

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

    キャンセル

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

  • ただいまの回答率 90.87%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    UnityでSpriteを使って一部を透明化する方法

    最近、シーンチェンジを実装するために基礎となる画像をSpriteを用いて一部透明化しようと考えたのですが、当たり前ですが単純に配置しても基礎画像が表示されてしまいます。(以下参考画

  • 解決済

    WebCamTextureを使ってRGBの値をリアルタイムで変更したいです。

    これで指定キーを押したら指定したRGBの値に変更するというコードを書きたいのですが、よくよくは白黒やセピアのようなものにしたいとも思ってますが初歩的なところで躓いてしまい先に進めま

  • 解決済

    Unity:角度・アングルでのシーンチェンジ方法

    前提・実現したいこと Unityにて、以下の画像ように、角度やアングルを変えることで別のシーンが見えるというものを作りたいです。 しかし、どのようにすればこのような二画面を実装で

  • 解決済

    Unityで影だけを表示させたい

    Unityでゲームオブジェクトを、ライトで真上から照らした光沢にしつつ、それに付く影はライトを上斜めから当てた感じで表示させたいと考えています。 Dlirectional ligh

  • 解決済

    PostProcessingStackの効果が重複してしまう

    Unityでスマホ向けのアプリを制作しています。 アセットのPostProcessingStackを使って画面効果を付与しているのですが、メインカメラとは別にUI用のカメラを用

  • 受付中

    shaderについて

    現在unityでshaderを勉強しているものです。 shaderで2値表示をしたいと思っています。 以下のサイト https://ics.media/entry/5535 を参考

  • 解決済

    Transparentなunlitシェーダに影を落としたい

    初心者なので、一部とんちんかんなことを言っているかもしれませんがご容赦くださいmm  解決したいこと Unlit/Transparentなシェーダのマテリアルを適用したquad

  • 解決済

    Unityで2D画像を旋回するパーティクルの表現

    Unity 2017を使用しています。 Scene上にSprite Rendererで描画した画像を前後に旋回し、軌跡が残るエフェクト(Particle System)を実装したい

同じタグがついた質問を見る

  • Unity

    3361questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • HLSL

    13questions

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