こんにちは 今回はパーティクルなどのエフェクト用シェーダーをテストで書いてみました。
動画はイメージ映像 ドリキャス版 斑鳩です。
パーティクル加算(additive)シェーダは明るい背景に重ねた時 単純にDstカラー(ここでは背景色のこと)
に対してスクリーン加算を行うためエフェクトを重ねるごとに色が白く飛びます。いわゆる芯のないエフェクト
というやつです。 シーン背景を暗くしたりといったゲーム性に関わる部分で対策をしたりもする方法もあるのですが
これを回避するために よく使われる手法が加算エフェクトの後ろに乗算テクスチャを一枚敷く
方法で、まだ固定シェーダしか無かったPS2やドリームキャストの時期のエフェクトはこのやり方が用いられていました。(ここで斑鳩の動画のどこで使われているか参照してみてください)
その後 プログラマブルシェーダーが搭載された次世代機になると 乗算と加算の2パスシェーダーで色の白とびを
抑える方法が使われるようになりました。 今回のシェーダーはこの2パスのパーティクルシェーダです。
Unityにビルトインシェーダーとして付属しているパーティクルシェーダーはおおまかに加算(Addiive)と減算(Multiply)。
そのほかにAdditiveMuitiplyのような種類もあります これは1パスでテクスチャの明度を見て背景と加算するもので色は正確にテクスチャの色を反映しますが暗い色の部分は表示できません。
今回のシェーダは乗算と加算を重ねて表示するため 色を正確表示するパーティクルとして使用できます。
図fig1を参照
fig1:
さらに
通常のビルボードテクスチャシェーダとしても機能する エフェクト用途に様々使用出来る利便性があります。
図fig2の通常シェーダを使用したテクスチャイメージが右側で 今回のシェーダを使用したイメージが左側です。
加算によってコントラストが強調されています。 爆発などのテクスチャを描画するのにも効果はありそうです。
さらにグローのイメージエフェクトを被せればリッチな画像が得られるかと思います。
fig2:
シェーダーのコード "Custom/AddMultiply+.shader”
Shader "Custom/AddMultiply+" {
Properties {
_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
_MainTex ("Particle Texture", 2D) = "white" {}
_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
_Contrast("Contrast Factor", Range(0.5,1.0)) = 0.1
}
CGINCLUDE
#pragma exclude_renderers gles
#pragma target 3.0
#pragma vertex vert
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD1;
#endif
};
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
#ifdef SOFTPARTICLES_ON
o.projPos = ComputeScreenPos (o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
#endif
o.color = v.color;
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
sampler2D _CameraDepthTexture;
float _InvFade;
sampler2D _MainTex;
fixed4 _TintColor;
float _Contrast;
float bias (float val, float b)
{
return (b>0) ? pow(abs(val),log(b) / log(0.5)) : 0;
}
float gain (float val, float g)
{
return 0.5 * ((val<0.5) ? bias(2.0*val, 1.0-g) : (2.0 - bias(2.0-2.0*val, 1.0-g)));
}
ENDCG
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}
SubShader {
Pass {
Blend Zero SrcColor
CGPROGRAM
#pragma fragment frag
fixed4 frag (v2f i) : COLOR
{
#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color.a *= fade;
#endif
half4 prev = i.color * tex2D(_MainTex, i.texcoord);
prev.r =gain( prev.r , _Contrast);
prev.g =gain( prev.g , _Contrast);
prev.b= gain( prev.b , _Contrast);
return lerp(half4(1,1,1,1), prev, prev.a);
}
ENDCG
SetTexture [_MainTex] {
combine texture * primary
}
SetTexture [_MainTex] {
constantColor (1,1,1,1)
combine previous lerp (previous) constant
}
}//pass
Pass {
Blend SrcAlpha One
AlphaTest Greater .01
ColorMask RGB
CGPROGRAM
#pragma fragment frag
fixed4 frag (v2f i) : COLOR
{
#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color.a *= fade;
#endif
fixed4 prev = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
prev.r =gain( prev.r , _Contrast);
prev.g =gain( prev.g , _Contrast);
prev.b= gain( prev.b , _Contrast);
return prev;
}
ENDCG
SetTexture [_MainTex] {
constantColor [_TintColor]
combine constant * primary
}
SetTexture [_MainTex] {
combine texture * previous DOUBLE
}
}
}
}
}
シェーダーの基本的な構造はビルトインシェーダーのソースコード『ParticleAdd.shader』『ParticleMultiply.shader』
を2パスで列挙しただけです。シェーダーのパスは下に行くほどカメラから見て手前に向かって上描きされていきます。
●UnityのShaderLabではCgコードの記述はCGPROGRAM~ENDCG 間という決まりがありますが
Shaderの内部で何度か呼び出される変数や関数を記述する場合はCGINCLUDE~ENDCG を使用します。
Cgのライブラリのインクルード#include”UnityCG.cginc”や#pragmaのオプション設定などもCGINCLUDE内に
まとめて記述してしまいます。
●#ifdef SOFTPARTICLE_ON ~#endif 行はソフトパーティクル使用のブーリアン値をチェックしています
ソフトパーティクルはオフスクリーンパーティクルに関する過去記事で解説していますが深度テクスチャ
(CameraDepthTexture)の値を基にピクセルカラーのアルファ値を設定するものです。
この行はシェーダ内で2回呼び出されていますが CGINCLUDE文の中にまとめてしまって構いません。
※ちょっと時間がなかったもので手が回りませんでした
●テクスチャカラーにバイアス値(_Contrast)を掛けて色を持ち上げることができるようにしてあります。
Pow()などを使用してもうすこし短いコードにもできますが、予想していた色味にならなかったのでbias()関数
を設定しています。ここは最適化したほうがいいかもしれません。
最後になりましたがUnityはマテリアルスロットで複数マテリアルを指定することが出来ますので 図fig3
のようにマテリアルを2つ登録することで同じような効果を得ることはできます。 Unityは知識のないユーザーにも
様々な回避方法が用意されていて助かります。
このときにシェーダーパスと同様に上に登録したものが先に描画されることに気をつけてください。
fig3:
補足)
マテリアルを複数使う場合 Unityのダイナミックバッチが適用されないためドローコールは当然ですが通常に比べて2倍発生します。 もちろん今回のシェーダーも2パスですので ドローコール×2-ダイナミックバッチ(期待されるドローコールの削減) となるわけですが、 これは内臓のパーティクルエディタを使用する場合の値です。
パーティクル自体のドローコールを減らす方向で幾つか考えられる手法があります。
●GPUシェーダでパーティクルを描画する方法。 生成されたテクスチャをフレームバッファに展開するだけですので
パーティクルすべてを1パスでレンダリングするのと同等です。 パーティクルの計算もGPUで行なってしまうため
高速なパーティクルレンダラーが書けると思います。
●SHURIKENなどパーティクルエディターから座標データだけを取得してシェーダーで一枚のスクリーンテクスチャに
まとめて描画する方法。 考え方はイメージエフェクトに近いです。エディターを自前で用意する手間がかかりません。
●ビルボードポリゴンの表示を高速化する方法。 unityのフォーラムでマインクラフトのテンプレートを取得した方はわかるかと思いますが
ポリゴンメッシュデータはMeshRendererにセットされたメッシュ配列によって描画されています。
メッシュ描画の高速化手法としては例えばMeshMergerスクリプトではStart()時にモデルメッシュを一つの配列にまとめてMeshRenderに転送しています。
この方法では動きのあるメッシュオブジェクトを毎回転送するのはコストが高いので、Mesh データにインデックスをつけて管理することで インデックスに連なるメッシュデータのみを直接操作することで速度の改善をはかることができます。
メッシュデータの移動変換は頂点データにトランスフォームのマトリックス を通すだけです。
おそらく2DGUIの最適化にもこのようなやり方が用いられているのではないでしょうか。
などなど最適化手法は様々あると思われますので アプリケーションの形態に合わせて選択すると幸せかもしれません。
というわけでエフェクトに便利そうなシェーダーを書いてみたの回でした。エフェクトの作成方法はまたいずれ
やっていこうかと思いますが とにかくも時間がないのでいつになるんでしょうねぇw
それから背景関連の記事ですが 予定の背景シェーダーをいろいろと書いてテストしている関係でちょっと時間かかっていますが 近いうちにアップしようと思っています。
シェーダ以外でも質問があれば コメントしていただければ分かる範囲で回答していきますので ご自由にどうぞ。
ではまた
【追記】
(13. 12.12) 関連記事を更新しました
うにばな (エフェクト用のシェーダに関するアイデア1+)
http://blog.livedoor.jp/akinow/archives/52389229.html
うにばな (エフェクト用のシェーダに関するアイデア2)
http://blog.livedoor.jp/akinow/archives/52372883.html


