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

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

ただいまの
回答率

88.23%

[Unity]深度バッファを使って表面のみに投影テクスチャマッピング

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,975

torano

score 91

やりたいこと

3Dモデルのテクスチャに直接書き込むようなペイントアプリを作成したいと思っています。方法としては、メインカメラの他にカメラを用意しそこからレイをとばしヒットした場所に投影テクスチャマッピングしてレンダーテクスチャに直接書き込もうと考えています。(毎フレームテクスチャでスタンプしていくようなイメージです。)
投影テクスチャマッピングしたものをそのモデルのテクスチャに反映する部分で苦戦していたのですが、ありがたいことにその問題はここで質問し、解決することができました。
[Unity]Graphics.Blitで投影テクスチャマッピングシェーダーをテクスチャに反映できない

問題点

しかし次の問題として、投影テクスチャマッピングの仕様上カメラから見えない場所(モデルの凹凸に隠れる場所や、裏側など)にも投影されてしまうことに気づきました。以下のようなものです。
イメージ説明
イメージ説明
Gameビューをマウスでクリックし、そこからレイをとばしてヒットポイントに塗っているのですが、ご覧の通り塗られるべきでない場所にも塗られてしまっているので、解決したいです。

試したこと

いろいろと調べてみた結果、深度バッファを使えば解決できそうだということがわかりました。カメラからピクセルまでの距離と深度情報を比べて深度のほうが小さければ塗らない、といった具合です。そして幸運にも似たようなことをやっている人を発見しました。
Unity デプスシャドウ技法を自前で書いて影を落としてみる
しかし、これを真似て実装してみたのですがうまくいきませんでした。

スクリプトと再現方法

前回の質問したときのスクリプトを、解決法にそって改善し、自分なりに上のことを実現しようとしたスクリプトです。前回と同様、塗られるモデルにこのスクリプトをアタッチし、各種パラメータを設定します。projectorには空のゲームオブジェクトにCameraコンポーネントを付けたものを入れます。Cameraの設定はOcclusion Culling, Allow HDR, Allow MSAAをオフにしてます。(orthoSizeなどはスクリプトから調節するようにしてます。)
前回と違い、今回は深度情報を使いたかったので疑似ではなくCameraコンポーネントを使用しています。またモデルには他の適当なマテリアル(スタンダードシェーダなど)を使用しています。Bakeファンクションで実際の塗りが発生しますが、これはテスト用のスクリプトなので毎フレーム塗っています。

先のデプスシャドウの実装では、予めRenderTextureをつくりCameraのTargetTextureにつけているようですが、この方法は自分ではなぜかうまく深度値がとれなかったのでスクリプトで動的にバッファを作成しカメラをDepthモードに切り替えています。

おそらくカメラからピクセルまでの距離か深度情報どちらかの値が間違っていると思うのですが、もしかしたら深度がうまくとれていないのかもしれません...
深度を扱うのは今回がはじめてで試行錯誤しながらやっていて、よくある例のようにポストエフェクトでメインカメラからの深度を直接画面に反映する方法で深度テクスチャの方法はうまくいっていました。しかし今回のようにシーンにカメラを二つ使用し、サブカメラのほうで深度をとろうとするとうまくいかなかったです。そもそも深度テクスチャはほぼ真っ黒なので、Inspectorだけではうまく深度とれてるか確認できず、デバッグのために深度テクスチャをGraphics.Blitで他のシェーダ(rの値が0と1意外の値のとき色をはっきりさせるもの)を使ってコピーし、uGUIで画面に表示してみたのですが、うまく深度テクスチャがコピーできなかったです...

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class DepthProjection : MonoBehaviour
{
    [SerializeField] Color brushColor;
    [SerializeField] Texture brushTex;
    [SerializeField] GameObject projector;
    [SerializeField] float zNear = 0.01f, zFar = 1, orthoSize = 0.5f;
    [SerializeField] Material paintMat;

    Material objMat;
    Renderer renderer;
    RenderTexture modelMainRT, colorBufferRT, depthBufferRT;
    public RenderTexture depthTex
    {
        get
        {
            return depthBufferRT;
        }
    }

    int ProjectorVPMat, brushColorId, projTexId, depthTexId;
    CommandBuffer bakeCommand;
    Camera projCam;

    private void OnValidate()
    {
        SetProjCam();
    }

    void SetProjCam()
    {
        if (projCam)
        {
            projCam.orthographic = true;
            projCam.orthographicSize = orthoSize;
            projCam.nearClipPlane = zNear;
            projCam.farClipPlane = zFar;
            projCam.aspect = 1;
        }
    }

    private void Awake()
    {
        renderer = GetComponent<Renderer>();
        objMat = renderer?.material;
        ProjectorVPMat = Shader.PropertyToID("ProjectorVPMat");
        brushColorId = Shader.PropertyToID("_BrushColor");
        projTexId = Shader.PropertyToID("_BrushTex");
        depthTexId = Shader.PropertyToID("_DepthTex");

        projCam = projector?.GetComponent<Camera>();

        if (projCam == null)
        {
            Debug.LogAssertion("projector needs Camera Component");
        }

        SetProjCam();

        InitProjectorAndDepthTexture();
        InitCommand();

    }

    void InitProjectorAndDepthTexture()
    {
        if (objMat)
        {
            var mainTex = objMat.mainTexture;

            if (mainTex && projCam)
            {
                modelMainRT = new RenderTexture(mainTex.width, mainTex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);

                projCam.depthTextureMode = DepthTextureMode.Depth;

                colorBufferRT = new RenderTexture(projCam.pixelWidth, projCam.pixelHeight, 0, RenderTextureFormat.ARGB32);
                colorBufferRT.Create();
                depthBufferRT = new RenderTexture(projCam.pixelWidth, projCam.pixelHeight, 24, RenderTextureFormat.Depth);
                depthBufferRT.Create();

                projCam.SetTargetBuffers(colorBufferRT.colorBuffer, depthBufferRT.depthBuffer);

                Graphics.Blit(mainTex, modelMainRT);
                objMat.mainTexture = modelMainRT;

                if (paintMat)
                {
                    paintMat.mainTexture = modelMainRT;
                    paintMat.SetTexture(depthTexId, depthBufferRT);
                }
            }
        }
    }

    void InitCommand()
    {
        if (renderer & modelMainRT & paintMat)
        {

            var c = new CommandBuffer();
            c.name = "Bake Projection Command";

            var RTBufferId = Shader.PropertyToID("_RTBuffer");

            c.GetTemporaryRT(RTBufferId, modelMainRT.descriptor);
            c.Blit(modelMainRT, RTBufferId);
            c.SetRenderTarget(RTBufferId);
            c.DrawRenderer(renderer, paintMat);
            c.Blit(RTBufferId, modelMainRT);
            c.ReleaseTemporaryRT(RTBufferId);

            bakeCommand = c;
        }
    }

    void SetMatProperties()
    {
        if (projector && paintMat && projCam != null)
        {
            Matrix4x4 projMat = GL.GetGPUProjectionMatrix(projCam.projectionMatrix, true);
            Matrix4x4 viewMat = projCam.worldToCameraMatrix;

            Matrix4x4 vpMat = projMat * viewMat;

            paintMat.SetMatrix(ProjectorVPMat, vpMat);
            paintMat.SetColor(brushColorId, brushColor);
            paintMat.SetTexture(projTexId, brushTex);
        }
    }

    // Update is called once per frame
    void Update()
    {
        SetMatProperties();
        Bake();
    }

    public void Bake()
    {
        if (bakeCommand != null)
        {
            Graphics.ExecuteCommandBuffer(bakeCommand);
        }
    }

    private void OnDestroy()
    {
        ReleaseAllRT();
    }

    void ReleaseAllRT()
    {
        ReleaseRT(modelMainRT);
        ReleaseRT(colorBufferRT);
        ReleaseRT(depthBufferRT);
    }

    void ReleaseRT(RenderTexture rt)
    {
        if (rt)
        {
            rt.Release();
        }
    }
}

深度を利用した投影テクスチャマッピングのシェーダーコードです。

Shader "Painter/Test/DepthProjectionTest"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white" { }
        _BrushColor("Brush Color", Color) = (1, 1, 1, 1)
        _BrushTex("Projection Texture", 2D) = "white" { }
        _DepthTex("Depth Texture", 2D) = "white" { }
    }

        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
        // 裏を向いた面に対応するためカリングを切る
        Cull Off

        CGPROGRAM

        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex: POSITION;
            float2 uv: TEXCOORD0;
        };

        struct v2f
        {
            float2 uv: TEXCOORD0;
            float4 vertex: SV_POSITION;
            float4 projUV: TEXCOORD1;
            float4 depthVertex : TEXCOORD2;
        };

        sampler2D _MainTex;
        sampler2D _BrushTex;
        sampler2D _DepthTex;
        float4 _MainTex_ST;
        float4 _BrushColor;

        uniform float4x4 ProjectorVPMat;

        v2f vert(appdata v)
        {
            v2f o;

            // ベイク時はメッシュのUV座標を頂点の位置と見なして描画させる
            float2 position = v.uv * 2.0 - 1.0;
            #if UNITY_UV_STARTS_AT_TOP
            // DirectX系の場合は上下を逆転させる
            position.y *= -1.0;
            #endif
            o.vertex = float4(position, 0.0, 1.0);

            o.depthVertex = ComputeGrabScreenPos(mul(ProjectorVPMat, v.vertex));

            // SkinnedMeshRendererに対応するため、モデル行列はUnity組み込みのものを用いる
            float4x4 mvpMat = mul(ProjectorVPMat, unity_ObjectToWorld);
            o.projUV = ComputeGrabScreenPos(mul(mvpMat, v.vertex));
            o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            return o;
        }

        fixed4 frag(v2f i) : SV_Target
        {
            fixed4 projColor = fixed4(0, 0, 0, 0);
            // depth textureから深度値をサンプリング。線形になるようLinear01Depthを使ったほうがいい?
            fixed4 depthColor = tex2D(_DepthTex, i.depthVertex.xy);
            float diff = i.depthVertex.z - depthColor.r;

            if (diff <= 0 && i.projUV.w > 0.0)
            {
                // projection to screen space
                i.projUV.x /= i.projUV.w;
                i.projUV.y /= i.projUV.w;

                if (i.projUV.x >= 0 && i.projUV.x <= 1 && i.projUV.y >= 0 && i.projUV.y <= 1)
                {
                    projColor = tex2D(_BrushTex, i.projUV);
                }
            }

            fixed4 mainColor = tex2D(_MainTex, i.uv);

            // if _BrushColor.a = 0 or projColor.a = 0, then mainColor. if both 1, then _BrushColor.
            return mainColor * (1 - _BrushColor.a * projColor.a) + _BrushColor * _BrushColor.a * projColor.a;
        }
        ENDCG
    }
    }
}

思いついた方法が新しくカメラを用意し深度テクスチャを使う、というものだったのですが、もし他に良い方法があれば教えてほしいです。できれば二度レンダリングしたくないので...

環境

Unity 2018.3.9f1で作成したプロジェクトです。(レンダリング設定はforwardだと思います。)
プラットフォームはデフォルトのものです。

よろしくお願いいたします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

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

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • stdio

    2019/03/25 10:07

    同じUV情報を使ってないならテクスチャ新調した方が速な感じがするのですが...

    キャンセル

  • torano

    2019/03/25 10:15

    どういうことでしょうか?

    キャンセル

  • stdio

    2019/03/25 10:27

    冷たいアドバイスになりますが、
    この程度の事が分からずに深度バッファをしているなら、もう一度勉強してきた方が良いですよ。

    キャンセル

回答 1

checkベストアンサー

+1

深度バッファを使うというのは的確なアイディアだと思います。確かに状況がシャドウマッピングとよく似ていますね。

おそらく、うまくいかなかった原因はデプステクスチャのサンプリング位置の狂いのような気がします。それを含め、いくつか変更を加えてみました。まずC#スクリプト側は...

  • デプステクスチャの解像度としてprojCam.pixelWidthprojCam.pixelHeightを使うと、サイズは画面解像度と同じになるかと思います。
    それはそれでいいとも思いますが、個人的には画面サイズによってエッジの精密さが変わってしまうのが気になり、さしあたりbrushTex.widthbrushTex.heightを使ってブラシテクスチャと同サイズにしました。これでは荒すぎるようでしたら、brushTex.width * 4といった風に何倍かしてやるのもいいでしょう。
  • projCamenabledSetProjCam内でfalseに変更して自動的レンダリングはさせないようにし、代わりにベイク用コマンドバッファ実行部分の手前で明示的にレンダリングさせるようにしました。順序的にはこちらの方が自然じゃないでしょうかね?
    当初のコードですと、レンダリングされたデプスがベイク結果に反映されるのは次回ベイク時...つまり次のフレームになると思われます。
  • projCamの描画に綺麗なシェーディングを施す必要はないので、projCam.renderingPathRenderingPath.VertexLitに差し替えました。スカイボックスもいらないでしょうから切っています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class DepthProjection : MonoBehaviour
{
    [SerializeField] Color brushColor;
    [SerializeField] Texture brushTex;
    [SerializeField] GameObject projector;
    [SerializeField] float zNear = 0.01f, zFar = 1, orthoSize = 0.5f;
    [SerializeField] Material paintMat;

    Material objMat;
    Renderer renderer;
    RenderTexture modelMainRT, colorBufferRT, depthBufferRT;
    public RenderTexture depthTex
    {
        get
        {
            return depthBufferRT;
        }
    }

    int ProjectorVPMat, brushColorId, projTexId, depthTexId;
    CommandBuffer bakeCommand;
    Camera projCam;

    private void OnValidate()
    {
        SetProjCam();
    }

    void SetProjCam()
    {
        if (projCam)
        {
            projCam.orthographic = true;
            projCam.orthographicSize = orthoSize;
            projCam.nearClipPlane = zNear;
            projCam.farClipPlane = zFar;
            projCam.aspect = 1;

            // カメラコンポーネントを非アクティブ化して自動レンダリングをさせなくする
            // また、projCam上でレンダリングする際のパスをVertexLitに差し替える
            // スカイボックスも描画不要
            projCam.enabled = false;
            projCam.renderingPath = RenderingPath.VertexLit;
            projCam.clearFlags = CameraClearFlags.Depth;
        }
    }

    private void Awake()
    {
        renderer = GetComponent<Renderer>();
        objMat = renderer?.material;
        ProjectorVPMat = Shader.PropertyToID("ProjectorVPMat");
        brushColorId = Shader.PropertyToID("_BrushColor");
        projTexId = Shader.PropertyToID("_BrushTex");
        depthTexId = Shader.PropertyToID("_DepthTex");

        projCam = projector?.GetComponent<Camera>();

        if (projCam == null)
        {
            Debug.LogAssertion("projector needs Camera Component");
        }

        SetProjCam();

        InitProjectorAndDepthTexture();
        InitCommand();

    }

    void InitProjectorAndDepthTexture()
    {
        if (objMat)
        {
            var mainTex = objMat.mainTexture;

            if (mainTex && projCam)
            {
                modelMainRT = new RenderTexture(mainTex.width, mainTex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);

                projCam.depthTextureMode = DepthTextureMode.Depth;

                // デプステクスチャのサイズをブラシテクスチャと同サイズに変更
                var depthBufferWidth = brushTex.width;
                var depthBufferHeight = brushTex.height;
                colorBufferRT = new RenderTexture(depthBufferWidth, depthBufferHeight, 0, RenderTextureFormat.ARGB32);
                colorBufferRT.Create();
                depthBufferRT = new RenderTexture(depthBufferWidth, depthBufferHeight, 24, RenderTextureFormat.Depth);
                depthBufferRT.Create();

                projCam.SetTargetBuffers(colorBufferRT.colorBuffer, depthBufferRT.depthBuffer);

                Graphics.Blit(mainTex, modelMainRT);
                objMat.mainTexture = modelMainRT;

                if (paintMat)
                {
                    paintMat.mainTexture = modelMainRT;
                    paintMat.SetTexture(depthTexId, depthBufferRT);
                }
            }
        }
    }

    void InitCommand()
    {
        if (renderer & modelMainRT & paintMat)
        {

            var c = new CommandBuffer();
            c.name = "Bake Projection Command";

            var RTBufferId = Shader.PropertyToID("_RTBuffer");

            c.GetTemporaryRT(RTBufferId, modelMainRT.descriptor);
            c.Blit(modelMainRT, RTBufferId);
            c.SetRenderTarget(RTBufferId);
            c.DrawRenderer(renderer, paintMat);
            c.Blit(RTBufferId, modelMainRT);
            c.ReleaseTemporaryRT(RTBufferId);

            bakeCommand = c;
        }
    }

    void SetMatProperties()
    {
        if (projector && paintMat && projCam != null)
        {
            Matrix4x4 projMat = GL.GetGPUProjectionMatrix(projCam.projectionMatrix, true);
            Matrix4x4 viewMat = projCam.worldToCameraMatrix;

            Matrix4x4 vpMat = projMat * viewMat;

            paintMat.SetMatrix(ProjectorVPMat, vpMat);
            paintMat.SetColor(brushColorId, brushColor);
            paintMat.SetTexture(projTexId, brushTex);
        }
    }

    // Update is called once per frame
    void Update()
    {
        SetMatProperties();
        Bake();
    }

    public void Bake()
    {
        // このタイミングでprojCamを手動描画
        projCam.Render();

        if (bakeCommand != null)
        {
            Graphics.ExecuteCommandBuffer(bakeCommand);
        }
    }

    private void OnDestroy()
    {
        ReleaseAllRT();
    }

    void ReleaseAllRT()
    {
        ReleaseRT(modelMainRT);
        ReleaseRT(colorBufferRT);
        ReleaseRT(depthBufferRT);
    }

    void ReleaseRT(RenderTexture rt)
    {
        if (rt)
        {
            rt.Release();
        }
    }
}

次に、シェーダー側は...

  • depthVertexは廃止しました。ブラシテクスチャとデプステクスチャはともに同じUV座標をサンプリングするべきだと思いますので、ともにprojUVを使用することにしました。
  • フラグメントの前後判定の際に、環境によるクリップ空間の違いを考慮するようにしてみました。おそらくこれでいけるかとは思うのですが、もし前後判定の狂いがあるようでしたら、どのような描画結果になってしまったか、グラフィックスシステムは何を使っているかコメントいただければ、可能であれば直してみようと思います。
    ご提示いただいた渋谷ほととぎす通信さんの製作例の途中に、デプステクスチャの内容を画面上に表示してみせている画像がありますが、そこではカメラに近い部分ほど色が暗くなっています。一方、【Unity】【シェーダ】カメラから見た深度を描画する - LIGHT11にもデプスの可視化を試みている例がありますが、こちらは逆に近い部分が明るくなっています。プラットフォーム特有のレンダリングの違い - Unity マニュアルの「クリップスペース座標」や「シェーダーの深度 (Z) の向き」をご覧いただくと、ごちゃごちゃぶりを察していただけるでしょうか...
Shader "Painter/Test/DepthProjectionTest"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white" { }
        _BrushColor("Brush Color", Color) = (1, 1, 1, 1)
        _BrushTex("Projection Texture", 2D) = "white" { }
        _DepthTex("Depth Texture", 2D) = "white" { }
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
            // 裏を向いた面に対応するためカリングを切る
            Cull Off

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex: POSITION;
                float2 uv: TEXCOORD0;
            };

            struct v2f
            {
                float2 uv: TEXCOORD0;
                float4 vertex: SV_POSITION;
                float4 projUV: TEXCOORD1;
            };

            sampler2D _MainTex;
            sampler2D _BrushTex;
            sampler2D _DepthTex;
            float4 _MainTex_ST;
            float4 _BrushColor;

            uniform float4x4 ProjectorVPMat;

            v2f vert(appdata v)
            {
                v2f o;

                // ベイク時はメッシュのUV座標を頂点の位置と見なして描画させる
                float2 position = v.uv * 2.0 - 1.0;
                #if UNITY_UV_STARTS_AT_TOP
                // DirectX系の場合は上下を逆転させる
                position.y *= -1.0;
                #endif
                o.vertex = float4(position, 0.0, 1.0);

                // SkinnedMeshRendererに対応するため、モデル行列はUnity組み込みのものを用いる
                float4x4 mvpMat = mul(ProjectorVPMat, unity_ObjectToWorld);
                o.projUV = ComputeGrabScreenPos(mul(mvpMat, v.vertex));
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // 環境に応じて奥行き値を加工する際に使う
                #ifdef UNITY_REVERSED_Z
                float inverseZ = 1.0;
                #else
                float inverseZ = 0.0;
                #endif

                fixed4 projColor = fixed4(0, 0, 0, 0);
                float depth = 1.0 - inverseZ;

                if (i.projUV.w > 0.0)
                {
                    // projection to screen space
                    i.projUV /= i.projUV.w;

                    if (i.projUV.x >= 0 && i.projUV.x <= 1 && i.projUV.y >= 0 && i.projUV.y <= 1)
                    {
                        // _BrushTexも_DepthTexも同じ位置をサンプリングする
                        projColor = tex2D(_BrushTex, i.projUV);
                        depth = tex2D(_DepthTex, i.projUV).r;
                    }
                }

                // まずブラシ視点からのクリップ座標z(w除算済み)とdepthを、奥が1・手前が0となるよう加工する
                float near = UNITY_NEAR_CLIP_VALUE;
                float far = 1.0 - inverseZ;
                float normalizedZ = (i.projUV.z - near) / (far - near);
                float normalizedDepth = inverseZ + (1.0 - 2.0 * inverseZ) * depth;

                // 両者を比較して前後を判断する
                // 判定に落ちたフラグメントはclipで破棄する
                // また、Zファイティング現象防止のため、判定値は少しだけ増やしてやる
                clip(normalizedDepth - normalizedZ + 0.001);

                fixed4 mainColor = tex2D(_MainTex, i.uv);

                // if _BrushColor.a = 0 or projColor.a = 0, then mainColor. if both 1, then _BrushColor.
                return mainColor * (1 - _BrushColor.a * projColor.a) + _BrushColor * _BrushColor.a * projColor.a;
            }
            ENDCG
        }
    }
}

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/03/28 11:27

    回答ありがとうございます。DirectX12で試してみたのですが一応動きました。

    シェーダーコードを一通り見たのですがクリップ空間のところで混乱しています。DirectXだとUNITY_REVERSED_Zが定義されフラグメントシェーダーでの計算結果はnear=0、far=0となるのですよね?コードみた時点ではおかしくなると思ったのですが正常に動いています。なぜなんでしょうか?
    公式リファレンスのプラットフォームの違いのところをみたのですが、

    「クリップスペース座標」では
    Direct3D 類:クリップ空間の深度の範囲は、ニアクリップ面の 0.0 からファークリップ面の +1.0 までになります。

    このようにあります。

    「シェーダーの深度 (Z) の向き」では
    DirectX12のクリップ空間範囲は、 [near,0]
    さらに、「他のプラットフォーム: 伝来の方向」では
    クリップ空間は、Direct3D のようなプラットフォームでは、範囲は [0,far]

    さらに下には以下のようにあります。
    そのため、逆方向深度 (Z) を使ったプラットフォームのシェーダーを使用する場合は(略)クリップ空間範囲は “near” (ニア) から 0 (ファー) の範囲内です。

    これをみて結局1(near)から0(far)なのか逆なのか混乱してわからなかったのですが、
    https://docs.unity3d.com/ja/current/Manual/SL-BuiltinMacros.html
    こちらではUNITY_NEAR_CLIP_VALUEはDirect3D のようなプラットフォームでは 0.0とあるので0(near)から1(far)と思っていましたがどうなんでしょう。

    キャンセル

  • 2019/03/28 12:57

    よくよく考えてみたらわかりました。DirectXだとnear=1でそれ以外のDirect3Dだとnear=0ということなんですね。UNITY_NEAR_CLIP_VALUEもHLSLSupport.cgincみたらDirectXだと1になってました。
    リファレンスの説明だけみたらすごくわかりずらいですね...

    他に質問です。
    i.projUV /= i.projUV.w;
    float normalizedZ = (i.projUV.z - near) / (far - near);
    ここではzにもwで割っているのでx, yが0から1のときzも0から1だと思うのですが、それをnormalizedZとしてそのまま使わないのはなぜなのでしょう?

    キャンセル

  • 2019/03/28 15:00

    その点については、wで割っただけでは同次座標が3次元座標につぶれるのみで、環境による違い...手前と奥が-1~1か1~0か...といった差異は残るかと思います。
    といいますのも、projUVはクリッピング座標をComputeGrabScreenPosで加工したものですが、これはUnityCG.cgincをご覧いただきますと...

    inline float4 ComputeGrabScreenPos (float4 pos) {
    #if UNITY_UV_STARTS_AT_TOP
    float scale = -1.0;
    #else
    float scale = 1.0;
    #endif
    float4 o = pos * 0.5f;
    o.xy = float2(o.x, o.y*scale) + o.w;
    #ifdef UNITY_SINGLE_PASS_STEREO
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
    #endif
    o.zw = pos.zw;
    return o;
    }

    このようになっており、zとwは元のクリッピング座標の値のまま残されるようです。そのため別途環境に応じた対策を加えてから使用することにしました。

    キャンセル

  • 2019/03/29 14:29

    なるほどです。今回もありがとうございました!

    キャンセル

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

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

関連した質問

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