Shaderでの繰り返し処理
解決済
回答 1
投稿
- 評価
- クリップ 0
- VIEW 2,739
この移動平均フィルタの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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
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の描画を防止する)を上下に横切るよう配置して、描画過程を見てみると下図のようになります。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.33%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
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をそのつど切り替える方向で考えたいと思います。固定回数処理の場合、下にいくつも同じ処理を書いていくやり方になるのでしょうか?