2013年08月28日

うにばな (エフェクト用途のシェーダに関するアイデア 2)

 

前回からまた更新の時間があいてしまいましたが 見に来てくださってる方々ありがとうございます。

拍手コメント頂いてるのですが お返事も出来なくて申し訳ないです。

先週 一日ほど空き時間があったのでエフェクト用シェーダサンプルを作成してみました

ベースはPaticleAdditive.shader ですが様々なシェーダにも応用が効きそうなものを詰め込んでみた感じです

エフェクト作成時に 画面内で画質調整ができると非常に作業が簡略化できるのでそうした機能がメインですね

今回のシェーダーは多機能な反面 若干重ためなコードだと思いますが あくまでサンプルなので

大量描画するパーティクルエフェクトやスクリーンに対して大きな面積をとる用途の場合を想定してきません

実装するときには 必要に応じて未使用な機能をコメントアウトするなどして最適化をしてください。

 

 

デバッグもあまりできていないので型付けのおかしい部分もあるかなとは思いますが なにぶん時間がたりないので気づいた方はご指摘ください。

(要約: なるべくじりきでがんばってみてほしいなぁ)

 

■"Particles/~MultiPurpose"

Shader "Particles/~MultiPurpose" {
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

_RotateSpeed ("RotateSpeed", Float) = 0.0


_ColorIndices ("ColorIndices Texture", 2D) = "white" {}
_SelectColorIndices("SelectColorIndices", Range(0.0,1.0)) = 1.0

_SliceColor ("Slice Color", Color) = (0.5,0.5,0.5,0.5)
_SliceGuide ("Slice Guide (RGB)", 2D) = "white" {}
_SliceEdge ("Slice Edge (RGB)", 2D) = "Gray" {}
_SliceAmount ("Slice Amount", Range(-1.2, 1.2)) = 0.5


_Flowmap("Flowmap", 2D) = "black" {}
_FlowAmount("Flow Amount", Range(-2.0, 2.0)) = 0.0

_MaskTex ("Mask Texture", 2D) = "white" {}

_VertexFlowTex ("VertexFlow Texture ", 2D) = "white" {}
_VertexFlowAmount ("VertexFlow Amount", Range(0, 10.0)) = 0.0

_CC_Bright("CC_Bright", Range(0.0,2.0)) = 0.5
_CC_Saturate("CC_Saturate", Range(0.0,1.0)) = 0.5
_CC_Contrast("CC_Contrast", Range(0.0,3.0)) = 1.2

_CC_Hue("CC_Hue", Range(-10.0,10.0)) = 0.5
_CC_Saturation("CC_Saturation", Range(0.0,1.0)) = 0.8
_CC_Luminosity("CC_Luminosity", Range(0.0,2.0)) = 0.6
}

Category {

Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend One OneMinusSrcAlpha
ColorMask RGB
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,1) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}

// ---- Fragment program cards
SubShader {
Pass {

CGPROGRAM

#pragma glsl
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#pragma target 4.0
#include "UnityCG.cginc"

sampler2D _MainTex;

float _RotateSpeed;
float4x4 pulsateMatrix ;

sampler2D _ColorIndices;
float _SelectColorIndices;

fixed4 _SliceColor ;

sampler2D _SliceGuide;
sampler2D _SliceEdge;
float _SliceAmount;

sampler2D _Flowmap;
float _FlowAmount;

sampler2D _MaskTex;
sampler2D _VertexFlowTex;
float4 _VertexFlowTex_ST;
float _VertexFlowAmount;


float _CC_Bright;
float _CC_Saturate;
float _CC_Contrast;

float _CC_Hue;
float _CC_Saturation;
float _CC_Luminosity;


fixed4 _TintColor;



struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};

struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD1;
#endif
float2 texcoord_orig : TEXCOORD2;
};

float4 _MainTex_ST;


float2 Rotator(float2 UV)
{
float sine = sin(_Time*_RotateSpeed);
float cosine = cos(_Time*_RotateSpeed);

pulsateMatrix._m00 = pulsateMatrix._m11 = cosine;
pulsateMatrix._m10 = -sine;
pulsateMatrix._m01 = sine;
float4 tempUV = float4(UV.x-0.5,UV.y-0.5,0,0);
float4 temp = mul(pulsateMatrix,tempUV);
UV.x = temp.x+0.5;
UV.y = temp.y+0.5;
return UV;
}

float3 ContrastSaturationBrightness(float3 color, float brt, float sat, float con)
{

float AvgLumR = 0.5;
float AvgLumG = 0.5;
float AvgLumB = 0.5;

float3 LumCoeff = float3(0.2125, 0.7154, 0.0721);

float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
float3 brtColor = color * brt;
float intensityf = dot(brtColor, LumCoeff);
float3 intensity = float3(intensityf, intensityf, intensityf);
float3 satColor = lerp(intensity, brtColor, sat);
float3 conColor = lerp(AvgLumin, satColor, con);
return conColor;
}

float3 RGBToHSL(float3 color)
{
float3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part)

float fmin = min(min(color.r, color.g), color.b); //Min. value of RGB
float fmax = max(max(color.r, color.g), color.b); //Max. value of RGB
float delta = fmax - fmin; //Delta RGB value

hsl.z = (fmax + fmin) / 2.0; // Luminance

if (delta == 0.0) //This is a gray, no chroma...
{
hsl.x = 0.0; // Hue
hsl.y = 0.0; // Saturation
}
else //Chromatic data...
{
if (hsl.z < 0.5)
hsl.y = delta / (fmax + fmin); // Saturation
else
hsl.y = delta / (2.0 - fmax - fmin); // Saturation

float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;

if (color.r == fmax )
hsl.x = deltaB - deltaG; // Hue
else if (color.g == fmax)
hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue
else if (color.b == fmax)
hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue

if (hsl.x < 0.0)
hsl.x += 1.0; // Hue
else if (hsl.x > 1.0)
hsl.x -= 1.0; // Hue
}

return hsl;
}

float HueToRGB(float f1, float f2, float hue)
{
if (hue < 0.0)
hue += 1.0;
else if (hue > 1.0)
hue -= 1.0;
float res;
if ((6.0 * hue) < 1.0)
res = f1 + (f2 - f1) * 6.0 * hue;
else if ((2.0 * hue) < 1.0)
res = f2;
else if ((3.0 * hue) < 2.0)
res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
else
res = f1;
return res;
}

float3 HSLToRGB(float3 hsl)
{
float3 rgb;

if (hsl.y == 0.0)
rgb = float3(hsl.z, hsl.z, hsl.z); // Luminance
else
{
float f2;

if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);

float f1 = 2.0 * hsl.z - f2;

rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0));
rgb.g = HueToRGB(f1, f2, hsl.x);
rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0));
}

return rgb;
}





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_orig = Rotator( v.texcoord.xy);
o.texcoord = Rotator( v.texcoord);
o.texcoord = TRANSFORM_TEX( o.texcoord,_MainTex);

half2 VertexFlowUV = TRANSFORM_TEX( v.texcoord, _VertexFlowTex);
half3 VertexFlowVec = tex2Dlod(_VertexFlowTex, float4(VertexFlowUV.xy,0,0)).rgb * 2.0-1.0;

o.vertex.xyz += VertexFlowVec.xyz * _VertexFlowAmount;


return o;

}



sampler2D _CameraDepthTexture;
float _InvFade;





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 *= fade;
#endif




////FlowMap////////////////////////////////////////////////////////////

half2 FlowtexUV = tex2D( _Flowmap,i.texcoord_orig).rg * 2.0-1.0;

half2 texUV;

texUV.x =clamp( ( i.texcoord_orig.x *2.0 -1.0)+ (FlowtexUV.x *_FlowAmount ),-1,1);
texUV.y =clamp( ( i.texcoord_orig.y *2.0 -1.0)+ (FlowtexUV.y *_FlowAmount ),-1,1);

texUV.x =( texUV.x +1.0)/2.0*_MainTex_ST.x +_MainTex_ST.z;
texUV.y =( texUV.y +1.0)/2.0*_MainTex_ST.y +_MainTex_ST.w;


// Mask///////////////////////////////////////////////////////////////////////
fixed4 tex = tex2D(_MainTex, texUV);

float MaskVal;
half4 MaskColor=half4(1,1,1,1);


MaskColor = tex2D ( _MaskTex,i.texcoord_orig);

tex.a =MaskColor.a ;


fixed4 col;



// ColorIndices/////////////////////////////////////////////////////


half2 ColorIndicesUV ;

ColorIndicesUV.x = tex.r;
ColorIndicesUV.y =1.0-_SelectColorIndices ;

fixed4 ColorIndices = tex2D(_ColorIndices, ColorIndicesUV );


col.rgb =_TintColor.rgb * ColorIndices.rgb * tex.rgb * i.color.rgb * 2.0f;


// Dissolve with Alpha channel/////////////////////////////////////////


float AbsSliceAmount = abs(_SliceAmount );

clip(tex2D (_SliceGuide, i.texcoord_orig).rgb - AbsSliceAmount );

half2 SliceEdgeUV;

SliceEdgeUV.x = (tex2D (_SliceGuide, i.texcoord_orig).r -AbsSliceAmount );
SliceEdgeUV.y = 0.5;

fixed3 EdgeColor = _SliceColor* tex2D (_SliceEdge, SliceEdgeUV).rgb* 1.8;

if (_SliceAmount >0.1 )
col.rgb += EdgeColor.rgb;
else if (_SliceAmount < -0.1 )
col.rgb *= 1- EdgeColor.rgb*0.5;




// ColorCollect /////////////////////////////////////////////////////////////////////

col.rgb= ContrastSaturationBrightness(col.rgb, _CC_Bright, _CC_Saturate, _CC_Contrast);

float3 HSL = RGBToHSL(col.rgb);

HSL.r *= _CC_Hue;
HSL.g *= _CC_Saturation;
HSL.b *= _CC_Luminosity;

col.rgb = HSLToRGB(HSL);

//////////////////////////////////////////////////////////////////////////////////////


col.a = (1 - tex.a) * (_TintColor.a * i.color.a * 2.0f) ;


return col;



}
ENDCG


}
}

// ---- Dual texture cards
SubShader {
Pass {
SetTexture [_MainTex] {
constantColor [_TintColor]
combine constant * texture, constant * primary DOUBLE
}
SetTexture [_MainTex] {
combine previous * primary DOUBLE, one - texture * previous
}
}
}

// ---- Single texture cards (does not do color tint)
SubShader {
Pass {
SetTexture [_MainTex] {
combine texture * primary DOUBLE, one - texture * primary
}
}
}
}
}


shader_slot

 

 

●Color_Indices

image1

カラーのR成分の数値 0~1に対して テクスチャのカラーをUV成分のU方向のカラーを割り当てます

テクスチャサイズどのようなものを使用しても良いのですが Rチャンネルのカラーは256階調なので インデックスカラーテクスチャのU方向は最大256までです

連続でカラーアニメーションさせたい場合はV方向にカラーバーを並べてUVのアニメーションをするなどのように使用します

テクスチャのインポート設定でwrapMode をClamp指定します Repeatのままですとテクスチャの端でカラーが隣のピクセル(たとえばテクスチャのU最大のときU最小のカラー)から侵食されてしまいますので注意してください。

 

image5

 

 

●Dissolve_Filter

image2


Dissolveアルファ値の境界にグラデーションがかかるようにしてあります スライダーを中心から右にずらすと加算 左にずらすと乗算でグラデーションはテクスチャでコントロールしています。 左側が外(アルファ値の低い方)の1D表現ですのでV方向のサイズはいくつでも構いません

clip(tex2D (_SliceGuide, i.texcoord_orig).rgb - AbsSliceAmount );

カットアウトアルファの しきい値指定はClip関数を用いていますが グラフィックカード(モバイルなど)によっては使用できないことがあるようです。 その場合はAlphaTest を使用してください。

 Unity - ShaderLab syntax- Alpha testing

 

 

補足>

シェーダ内でif命令を使用しています。 シェーダーで if ~else構文は使用できますが if命令は実行速度があまり早くありません。 (理由は「 SIMD 」で検索してみてください)

ピクセルシェーダ(フラグメントシェーダ)で分岐処理をおこなうとスクリーン表示ピクセル分だけ分岐命令の処理をする必要がありシェーダ自体がとても重くなります。その場合ShadowGunのサンプルに見られるように 頂点シェーダに計算部分を移動してしまう方法がまずひとつの方法です。 (頂点シェーダは頂点数分だけ計算が行われますが ピクセルシェーダはピクセル数分だけの計算量になるため)

 

そのほか if命令を 標準のシェーダー関数で置き換えてしまうことで最適化をはかる方法もあります

例1)

これ↓を

if  (distance > _Value) { cfinal = c2; } 
else cfinal = c;

 

この↓ように

v = max(0,sign(distance  -  _Value))  
cfinal = lerp(c, c2, v);

比較したい2つの値の差分をsignで(負 or 0 or 正) を(−1 or 0 or 1)の値に変換して

maxで0か1の値にまとめて leap(a, b, val) 「val=0のときa val=bのときb をとる関数」 を使用して

値を割り付けます

vは0または1をとるブーリアン型相当の値をとればよいので int、uint型などで代用します

 

例2)

fixed mixFactor = saturate(1 - ((distance - _ColorDecay) / _FadeDistance + 1) * 0.5);

cfinal = lerp(c, c2, mixFactor);

 

例のように 計算の値をsaturate()を使用して値を0~1の範囲にたたんでしまうようにすればよいかと思います

shader if else performance - Unity Answers

SIMD - Wikipedia, the free encyclopedia

 

●FlowMap_Filter

image3

FlowMapというテクスチャを用いて水面などをアニメーションさせる方法があります。

考え方はNormalMapと少し似ていますが NormalMapがカラーデータを使用して法線の傾きを

与えるのに対してFlowMapはテクスチャUV座標に変位を与えます。


詳しくはリンク先を参照してください
https://www.dropbox.com/s/ii2x077vj64lyhl/Water%20Flow%20For%20UDK.pdf

image8 flowsheet1

 


float2 flowmap = tex2D( _FlowMap, uv ).rg * 2.0 - 1.0;

テクスチャのアンパック化はこのような式で行われます
flowmapテクスチャからrとgの成分を読みだして カラーの値0~1の範囲を -1~1に拡張しています。
テクスチャ中心からベクトルUV方向に-1 ~ +1 となるように加工したほうが使い勝手が良いので

image9


このような形の式(突然数値を増減し始める)が出てきたら 値を調整している部分だと考えていいかと思います。 多分


その他 水面を表現する場合FlowMapを使用してNormalMapの合成など加工をおこなう場合には以下のような記述

ALGOholic ? A Coder's Blog - Another Flow Field Editor Update

 

    phase0 = cycleOffset * 0.5 + FlowOffset0;

    phase1 = cycleOffset * 0.5 + FlowOffset1;

    float3 norm0 = tex2D(NormTex0, (Uv * タイリング数) + flowmap * phase0);

    float3 norm1 = tex2D(NormTex1, (Uv * タイリング数) + flowmap * phase1);

VFX_Flowmap

左側FlowMapを使用して Leap0時(テクスチャまま):Leap1(オフセット変位100%)




 

 

●VertexModify

FlowMap2

今回はテクスチャの値を使用してメッシュ頂点をアニメーションさせています

頂点のアニメーションを制御しているのはこの行です

half2  VertexFlowUV= TRANSFORM_TEX( v.texcoord,  _VertexFlowTex);

half3  VertexFlowVec = tex2Dlod (_VertexFlowTex, float4(VertexFlowUV.xy,0,0)).rgb * 2.0-1.0;

 

o.vertex.xyz += VertexFlowVec.xyz * _VertexFlowAmount;

TRANSFORM_TEX はテクスチャUVの値を   _VertexFlowTex_ST  で設定される値にUVタイリング値と

オフセットを再計算させるUnityのマクロ関数です。

シェーダに float4 ”テクスチャ名”_ST  と記述することでシェーダ実行時に値が帰ってきます

テクスチャスロットと 帰ってくる値の関係は下図のとおりです

image4

”テクスチャ名”_ST .xy           =   Tiling値 UV方向のタイリング数

”テクスチャ名”_ST .wz          = Offset値 UV方向のオフセット値

UV.xy = TRANSFORM_TEX ( v.texcoord,  _MainTex );

これをマクロを使用しないで記述すると

UV.x  =  v.texcoord.x  * _MainTex_ST.x +_MainTex_ST.z;
UV.y  =  v.texcoord.y  * _MainTex_ST.y +_MainTex_ST.w;

となります。

 

vertexシェーダ内でテクスチャを参照する場合は tex2D の代わりに tex2Dlod を使用します。

tex2Dlod( テクスチャ, float4(U値, V値, 0, w =( lod 値の指定0~7 )

これは テクスチャlodの値がdepth値(カメラからの距離)によって決定されるため depth値がvertexシェーダでメッシュのソートが解決されてPixelシェーダにデータが渡るときに計算が行われるので vertexシェーダ内ではlodの自動割り当てが出来なくなり 直接lod値を指定してあげる必要があるからです。

※ Tex2Dlod関数はとても役立つので今後もとりあげていく予定。

image7

最後に 頂点データxyzにカラーデータを-1~+1範囲に加工したものを加算します 変化率をコントロールできるように

_VertexFlowAmount として掛けてあげます。

 

o.vertex.xyz += VertexFlowVec.xyz * _VertexFlowAmount;

 

頂点アニメーションというと三角関数を使用したものが多いですが テクスチャデータを使用したアニメーションは

テクスチャデータを呼び出して簡単な計算を行うだけなので重たい三角関数ほど負荷もなく複雑なアニメーションも見た目でコントロールできるので応用がありそうです。 ほかにはスクリプトに不慣れなデザイナーにとってもテクスチャを入れ替えるだけで扱いやすいというメリットもあると思います。

 

 

●COLOR_Collect_Filter

おまけ機能  色調整機能です ネットで公開されていたフォトショップの機能をシェーダで記述した関数を

そのまま使用してあります。

その他カラーコントロールのシェーダーサンプルはnVidiaの公式サイトを参照してみてください。

NVIDIA Shader Library

「Bright と Luminosity」「Saturate と Saturation」の変数がダブっているのですが どちらか片方しか実装しないと コントロールが難しかったため 両方記述してあります。(調整にあまり時間も掛けたくなかったもので..)

Bright と Contrast は上げ過ぎると色飛びしますので Saturateを下げてバランスを整えてると良いような気がします。 Contrastはアルファ部分のカラーが持ち上がりすぎを防ぐのに有効です。

カラーセーフ的な安全装置をつけても良いのですが エディタにくせがあると使いにくくなるので自由にカスタマイズしてみてください。

 

CC_Bright :明度 CC_Saturate  :彩度 CC_Contrast   :コントラスト


CC_Hue :色相 CC_Saturation  :彩度2 CC_Luminosity  :明度2

image10

 

■テクスチャのアニメーション

過去記事を読んでいただくかリンク先を参照してください

Animating Tiled texture - Unify Community Wiki

 

 

■シェーダーに関するそのほかのお話

 

■Unity Shaders and Effects Cookbook [Kindle版]

Kenneth Lammers (著)

Unity Shaders and Effects Cookbook

 

各所で非常に評価の高い本書 英語版ですけどコード部分だけながめても わりと分かりやすいのではないかなと思いました リンク先↓ のWikiも合わせて読むと 理解が深まるかもしれません

Cg Programming-Unity - Wikibooks, open books for an open world

よくリンクされているGLバージョンではなくCGバージョンのほうですね。3Dソフトで開発する場合最近だとCGFXに対応していることが多いので CGFXに似たCGでシェーダーを書いておくといろいろ捗ります。

 

 

■Shader Forge - A visual, node-based shader editor plugin for Unity

Shader Forge - A visual, node-based shader editor

現在開発中らしき フリー?のノードベースシェーダーエディター。 ロードマップを見るとまだこれからのようですがUI表示がUDKのマテリアルエディターに似た感じでアイコン表示になっているのはわかりやすくてGoodです。

ただ見た目の簡便さと裏腹に ノードベース型って複雑なシェーダが作りにくいんですよね 吐いてくるコードを実装できるまで最適化するのが一苦労だったりして 今のところテキストエディタしかつかってなかったりw いいものになるといいな

 

■参照リンクなど

View Profile- TheJamsh - Epic Games Forums

How Do You Make a Flow Map- [Archive] - Polycount Forum

Flow Map Painting Script - Early Alpha - Polycount Forum

akinow at 10:11│Comments(0)TrackBack(0) Clip to Evernote Unity3d | シリーズ講座

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔