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

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

ただいまの
回答率

88.58%

Shaderでの繰り返し処理

解決済

回答 1

投稿

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

shiroshiro_me

score 19

この移動平均フィルタのshaderプログラムにRepeatの回数フィルタ走査を繰り返して平滑化の度合いを上げる機能を追加したいと考えています。
しかし、一度処理をしたものをもう一度同じ処理をする方法が分かりません。どのように実装すればいいでしょうか。
回答よろしくお願いします。

Shader "Custom/RepeatAve" {
    Properties
    {
        _Color("Color", Color) = (1, 1, 1, 1)

        _MainTex("Diffuse", 2D) = "white" {}
        _Noise("Noise", 2D) = "black" {}

        _Range("Range", Float) = 0.025
        _Blur("Blur", Float) = 0.005
        _Repeat("Repeat", int) = 1
        _Size("Size", int) = 3
    }

    SubShader
    {
        Cull Off

        GrabPass{ "_Frost" }

        CGINCLUDE
        #include "UnityCG.cginc"

        half4 _Color;

        sampler2D _MainTex;
        float4 _MainTex_ST;

        sampler2D _Frost;
        sampler2D _Noise;
        float4 _Noise_ST;

        half _Range;
        half _Blur;

        int _Size;
        int _Repeat;

        ENDCG

        Pass
        {
        CGPROGRAM
        #pragma target 3.0
        #pragma vertex vert
        #pragma fragment frag

        struct v2f
        {
        float4 pos : SV_POSITION;

        float3 uv : TEXCOORD;
        float4 screenPos : TEXCOORD1;
        float3 ray : TEXCOORD2;
        };

        v2f vert(appdata_full v)
        {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.uv = v.texcoord;
        o.screenPos = ComputeScreenPos(o.pos);
        o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1);
        return o;
        }

        half4 frag(v2f i) : SV_Target
        {
            i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
            float2 uv = i.screenPos.xy / i.screenPos.w;

            float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;

            frostUV -= 0.5;
            frostUV *= _Range;
            frostUV += uv;

            half4 frost = tex2D(_Frost, frostUV);
            for (int m = -(_Size - 1) / 2; m <= (_Size - 1) / 2; m++) {
                for (int n = -(_Size - 1) / 2; n <= (_Size - 1) / 2; n++) {
                    frost += tex2D(_Frost, frostUV + float2(_Blur * m, _Blur * n));
                }
            }

            frost /= _Size * _Size;

            half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);

            half alpha = _Color.a * diffuse.a;

            return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
        }

        ENDCG
        }
    }

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • Bongo

    2018/10/11 06:25

    固定回数の処理なら「グラブパス」→「移動平均パス」→「グラブパス」→「移動平均パス」→...といった具合に何度もグラブと移動平均を繰り返してやることができるかもしれませんが、可変回数だと困難そうに思います。シェーダーファイル単独ではなく、「ガウシアンブラーの実装」に投稿した新Frostのように、コマンドバッファ操作用の補助スクリプトがあってもいいなら可変回数対応にしやすいと思いますが、どうでしょうか...?

    キャンセル

  • shiroshiro_me

    2018/10/11 16:47

    その方向で検索してみましたが、CommandBufferの具体的な使い方や紐づけの方法などいまいち分かりませんでした。c#側でshaderをfor文で何度も実行する形になるのでしょうか?

    キャンセル

  • Bongo

    2018/10/11 18:43

    繰り返し部分はC#スクリプト側で面倒をみてやるのが順当な手段だろうと思います。ですが確かにCommandBufferは少々ややこしいかもしれません。もし、必ずしも曇りガラスのような3Dオブジェクトの特殊効果として利用する必要がなく、移動平均エフェクトが画面全体にかかってしまってもかまわないならば、イメージエフェクトシェーダーのやり方が使えるかと思います。こちらの方がCommandBufferよりシンプルですし、解説サイトも多いように思いますが、いかがでしょうか?

    キャンセル

  • shiroshiro_me

    2018/10/15 11:19

    曇りガラスのような3Dオブジェクトで実装したいと考えています。難しいようなら固定回数のものをいくつか作成し、shaderをそのつど切り替える方向で考えたいと思います。固定回数処理の場合、下にいくつも同じ処理を書いていくやり方になるのでしょうか?

    キャンセル

回答 1

checkベストアンサー

+1

「グラブ」→「平均化」→「グラブ」→「平均化」→...→「最終処理」方式だとこんな感じでしょうかね?
コードの再利用のため「RepeatAve.shader」ファイルと「RepeatAve.cginc」ファイルに分かれていますが、シェーダーとしては単一ですので、補助スクリプトなしでマテリアルをアタッチするだけとなります。ちょっと異様な形になってしまいましたが...

RepeatAve.cginc

sampler2D _GrabTexture;
half _Blur;
int _Size;
struct v2fAve
{
    float4 pos: SV_POSITION;
    float4 screenPos: TEXCOORD1;
};
v2fAve vertAve(appdata_full v)
{
    v2fAve o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.screenPos = ComputeScreenPos(o.pos);
    return o;
}
half4 fragAve(v2fAve i): SV_Target
{
    float2 uv = i.screenPos.xy / i.screenPos.w;
    half4 frost = 0.0;
    #ifdef _AVE
        for (int m = - (_Size - 1) / 2; m <= (_Size - 1) / 2; m ++)
        {
            for (int n = - (_Size - 1) / 2; n <= (_Size - 1) / 2; n ++)
            {
                frost += tex2D(_GrabTexture, uv + float2(_Blur * m, _Blur * n));
            }
        }
        frost /= _Size * _Size;
    #else
        frost = tex2D(_GrabTexture, uv);
    #endif
    return half4(frost.xyz, 1);
}

RepeatAve.shader

Shader "Custom/RepeatAve"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _MainTex ("Diffuse", 2D) = "white" {}
        _Noise ("Noise", 2D) = "gray" {}
        _Range ("Range", Float) = 0.025
        _Blur ("Blur", Float) = 0.005
        [KeywordEnum(R0,R1,R2,R3,R4,R5,R6,R7,R8)] _Repeat ("Repeat", Float) = 0
        _Size ("Size", int) = 3
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" }
        Cull Off

        CGINCLUDE
        #pragma target 3.0
        #include "UnityCG.cginc"
        ENDCG

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R1 _REPEAT_R2 _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R1 | _REPEAT_R2 | _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R2 _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R2 | _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAve
            #if _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            ENDCG
        }

        GrabPass{}
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            half4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _Noise;
            float4 _Noise_ST;
            sampler2D _GrabTexture;
            half _Range;
            struct v2f
            {
                float4 pos: SV_POSITION;
                float3 uv: TEXCOORD;
                float4 screenPos: TEXCOORD1;
                float3 ray: TEXCOORD2;
            };            
            v2f vert(appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.screenPos = ComputeScreenPos(o.pos);
                o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1);
                return o;
            }
            half4 frag(v2f i): SV_Target
            {
                i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
                float2 uv = i.screenPos.xy / i.screenPos.w;
                float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;
                frostUV -= 0.5;
                frostUV *= _Range;
                frostUV += uv;
                half4 frost = tex2D(_GrabTexture, frostUV);
                half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
                half alpha = _Color.a * diffuse.a;
                return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
            }
            ENDCG        
        }
    }
    Fallback Off
}

_Sizeは規定値の3、_Blurも規定値の0.005で、RepeatをR0~R8に切り替えると下図のようになりました。

反復回数切り替え

今回は単一のシェーダーとしましたので、反復回数を切り替えても常に「グラブ」→「描画」のプロセスが9回行われます(とはいえ設定した反復回数を超えた部分では「グラブ」→「そのまま描画」となりサンプリング回数が1回となるため、反復回数を小さくすれば負荷は小さくなるでしょう)。
ご質問者さんのおっしゃる「反復回数の異なる複数のシェーダーを作る」という方式なら、小反復回数のときの無駄な「グラブ」→「描画」部分を丸ごと削除することができるため、より高速化できると思います。

やはり、補助的なC#スクリプト抜きだとちょっと非効率的になってしまいますね。特に多数の曇りガラスオブジェクトがある場合は、オブジェクトの数だけグラブパスが増えますので負荷が気になりそうです。
ただ、この形ですと「曇りガラスオブジェクトの手前に別の曇りガラスオブジェクトがある場合、手前のオブジェクトを透かして見た背後の曇りガラスオブジェクトはさらにぼやけて見える」という視覚的もっともらしさがあると思います。

 追記

1回目の平均化処理を行う部分までを下記のように変更してみました。

RepeatAve.shader

Shader "Custom/RepeatAve"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _MainTex ("Diffuse", 2D) = "white" {}
        _Noise ("Noise", 2D) = "gray" {}
        _Range ("Range", Float) = 0.025
        _Blur ("Blur", Float) = 0.005
        [KeywordEnum(R0,R1,R2,R3,R4,R5,R6,R7,R8)] _Repeat ("Repeat", Float) = 0
        _Size ("Size", int) = 3
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" }
        Cull Off
        ZWrite Off
        Stencil
        {
            Ref 0
            Comp Equal
        }

        CGINCLUDE
        #pragma target 3.0
        #include "UnityCG.cginc"
        ENDCG

        GrabPass{"_FirstGrabTexture"}
        pass
        {
            CGPROGRAM
            #pragma multi_compile _ _REPEAT_R1 _REPEAT_R2 _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
            #pragma vertex vertAve
            #pragma fragment fragAveFirst
            #if _REPEAT_R1 | _REPEAT_R2 | _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
                #define _AVE
            #endif
            #include "RepeatAve.cginc"
            sampler2D _FirstGrabTexture;
            half4 fragAveFirst(v2fAve i): SV_Target
            {
                float2 uv = i.screenPos.xy / i.screenPos.w;
                half4 frost = 0.0;
                #ifdef _AVE
                    for (int m = - (_Size - 1) / 2; m <= (_Size - 1) / 2; m ++)
                    {
                        for (int n = - (_Size - 1) / 2; n <= (_Size - 1) / 2; n ++)
                        {
                            frost += tex2D(_FirstGrabTexture, uv + float2(_Blur * m, _Blur * n));
                        }
                    }
                    frost /= _Size * _Size;
                #else
                    frost = tex2D(_FirstGrabTexture, uv);
                #endif
                return half4(frost.xyz, 1);
            }
            ENDCG
        }

        // 省略...残り7回のフィルタ処理および最終合成処理には変更なし
    }
    Fallback Off
}

RepeatAve.cgincに変更はありません。
1回目のGrabPassのみグラブ先を_FirstGrabTextureとして平均化もそのテクスチャを参照するようにし、2回目以降は先と同様にテクスチャ名指定なしでグラブ・平均化しています。
テクスチャ名指定ありでグラブすると最初の1回だけグラブが行われるので、フィルタ処理の起点は毎回最初のグラブ結果となります。このため、後で描画されるマテリアルにそれまでのフィルタ処理の影響が及ばなくなり、「曇りガラスを重ねるほどぼけ幅が大きくなる」という現象を回避できるかと思います。
また、ついでにコメントで申し上げたZWrite Offを加え、さらにステンシルテストを有効にしてステンシルバッファの内容が0の場合だけ描画を行うようにしました。

これと併せて、ステンシル値を加算してステンシルバッファの内容を0でなくするマテリアルをマスク用オブジェクトに使用すれば、キューの大小に基づいたマスキングが可能かと思います。

StencilMask.shader

Shader "Custom/StencilMask"
{
    Properties
    {
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" }
        Cull Off
        ZWrite Off
        Stencil
        {
            Pass IncrSat
        }
        ColorMask 0

        Pass
        {
            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct v2f
            {
                float4 pos: SV_POSITION;
            };            
            v2f vert(appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            half4 frag(v2f i): SV_Target
            {
                return 0.0;
            }
            ENDCG        
        }
    }
    Fallback Off
}

テストとして、下記3種のマテリアルを作成し...

  • マテリアルA...赤ガラス、ぼけ幅中、ノイズ効果小、キュー2501
  • マテリアルB...緑ガラス、ぼけ幅大、ノイズ効果大、キュー2511
  • マテリアルC...青ガラス、ぼけ幅小、ノイズ効果なし、キュー2521

これらを、_FirstGrabTextureを使わない従来通りの方法で描画すると、下図のように先に描画した曇りガラスの影響が後段にも引き継がれますが...

旧描画方式

_FirstGrabTextureを使う方式にすると、下図のような結果となります。

新描画方式

また、キュー2510のマスクオブジェクト(Aの次に描画され、描画領域へのB・Cの描画を防止する)を左右に横切るよう配置、キュー2520のマスクオブジェクト(Bの次に描画され、描画領域へのCの描画を防止する)を上下に横切るよう配置して、描画過程を見てみると下図のようになります。

マスク処理

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/10/18 22:06

    説明不足ですみません。
    私が実装したいこととしましてはキューの違う曇りガラスオブジェクトいくつか同じ座標に重ね、そこをある一定のキュー以上のオブジェクトをマスクするオブジェクトを用意し、マスクした部分はそのキュー以下で一番大きいキューのオブジェクトのぼかしが適用されるようにしたいと考えています。
    例えばキューが2521,2511,2501のオブジェクトを重ね、2520以上をマスクする場合、2501のオブジェクトのぼかしが強くても、2511のぼかしが0であればマスク部分は高解像度になるようにしたいと考えています。

    キャンセル

  • 2018/10/19 07:02

    修正案を検討してみましたが、いかがでしょうか?
    こんな挙動だろうと想像して作りましたが、意図を読み違えていましたらお詫びします...

    キャンセル

  • 2018/10/19 15:08

    おかげ様でまさしく私がやりたいことを実装することができました。ありがとうございます。
    ただ、Stencilを用いると、今回は実行前で例のブラーの消失が起こってしまったため、Stencilを用いない方法で実装をさせていただきました。しかし、Stencilやグラブパス、ZWriteなど知らないことばかりで大変勉強になりました。また、自分でも勉強していきたいと思います。
    重ねてお礼申し上げます。誠にありがとうございました。

    キャンセル

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

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

関連した質問

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