みなさんUnityしてますか?僕はここ5年くらいはUnityしまくってます。
すっかりモバイル開発はUnityばっかりになりました。もちろん他にも有力なゲームエンジンはあるのですけど、いろいろなハードに出すということや運営面を考えると、セガでは現状Unityがとても強い状況です。
はじめまして、セガゲームス開発統括部アート&デザイン部TAセクションの樋口と申します。スマホ向けゲーム開発を専門的に行う部署に所属しています。先日のCEDEC2017で「GameVFX Bootcamp 2017 ゲームエフェクトの今を4つの視点から」におきまして、弊社の綿貫が紹介していました「Effect Uber Shader(エフェクト統一シェーダー)」についてお話しします。
エフェクトのワークフロー
ワークフローという程でもないのですが、一般的にエフェクトを作る場合には以下のような段階があります。
- 作成内容のパーツ分解
- パーツの試作
- 組み合わせて調整
- 組み込んで調整
たとえば爆発のエフェクトを作る場合、イントロの閃光、放射状に広がる火花、少し遅れて広がる煙、衝撃波というような感じで(1)分解して、(2)それぞれを作っていくというような感じです。その後、(3)それらを組み合わせて、同期させて調整を行なっていき、一旦は完成となりますが、ゲームに組み込んでみてから(4)最終的に調整を行うことになります。
エフェクト統一シェーダーが活躍するのは、主に(3)と(4)になります。調整の段階ではパラメータを変化させて目的に合った状態に設定していくという作業を行いますが、変化の自由度が少ないとデータの作り直しからとなってしまうため、調整作業が非常に高カロリーです。
調整作業を簡略化したい
例えば、シェーダーが統一されておらず、各機能別にそれぞれシェーダーが用意されていたとすると、個別の機能の切り替えの際に選択するシェーダーを探し当てないとならず、間違いのもととなります。
ありそうなシェーダー名の例だとこんな感じでしょうか。機能別にどんどん名前が追加されて似たようなものもあったりで、大変です。
- ParticleAdditive
- ParticleAlphaBlended
- ParticleAdditiveZOffset
- ParticleAdditiveOvertop
- ParticleAdditiveUVScroll
- ParticleAdditiveUVScrollZOffset
- ParticleAdditiveUVScrollZOffsetCutoff
- ParticleAdditiveUVScrollZOffsetCutoffMasked
- ...などなど
これが、使うシェーダーは1つだけとして、各機能別にシェーダー内の設定で必要な機能を選んだりできると調整用途に合わせて、こっちにしたらどうかな?あるいはこれは?というような形で素早く試すことができます。
複数の機能を1つのシェーダーにまとめるUnityの機能
実は、Unityのシェーダー記法(ShaderLab)では複数の機能を1つのシェーダー内にまとめておくことができます。また、一部の機能についてはパラメータとして与えることだけで変更可能だったりします。この2つを見ていきましょう。
パラメーターだけで変更可能な機能
はじめは、パラメータだけで変更可能にできるものを紹介します。こちらはシェーダーの書き方だけで対応できるので、省コストで使いではあるようなものです。以下の指定はパラメータを設定するだけ切り替えることができます。後述の「複数の機能を1つのシェーダーにまとめる」の場合では内部的にシェーダーバリアント数が増えてしまう問題が出ますが、以下の機能の切り替えについてはバリアント数は全く影響しませんので、どんどん使うと良いと思います。
- アルファブレンドモード指定
- ZTestのモード指定
- ZWriteのON, OFF
- カリングモード指定
これはブレンドモードをパラメータで変更した例です。上が標準ブレンド(SrcAlpha, OneMinusSrcAlpha)で、下が加算(One, One)です。設定する数字はUnityの定義するBlendModeという列挙体のもつ値で決まっています。ここではSrcAlpha = 5, OneMinusSrcAlpha = 6, One = 1となります。
シェーダーソース側では以下のように書きます。Propertiesブロックで定義した値をBlend指定に与えてあげます。
Properties { _MainTex("Texture", 2D) = "white" {} _BlendSrc("Blend Src", Float) = 0 _BlendDst("Blend Dst", Float) = 0 }
Blend [_BlendSrc] [_BlendDst]
値を調べて入れるのは手間が多く間違いを誘発しますので、プロパティの値を列挙体の名前から選んで指定する機能を使うとより便利になります。以下のように書きます。
Properties { _MainTex("Texture", 2D) = "white" {} [Enum(UnityEngine.Rendering.BlendMode)]_BlendSrc("Blend Src", Float) = 0 [Enum(UnityEngine.Rendering.BlendMode)]_BlendDst("Blend Dst", Float) = 0 }
同じように、ZTestについても与える値はCompareFunctionという列挙体の値を使いますが、指定は似たようなものです。
[Enum(UnityEngine.Rendering.CompareFunction)]_ZTestMode("ZTest Mode", Float) = 0
ZTest [_ZTestMode]
ZWriteはOFF = 0, ON = 1です。0と1を切り替える場合は、[Toggle]という指定を付けるとインスペクター上ではチェックボックスとなるので便利です。
[Toggle]_ZWriteParam("ZWrite", Float) = 0
ZWrite [_ZWriteParam]
カリング指定はCullModeという列挙体の値を使います。
[Enum(UnityEngine.Rendering.CullMode)]_CullMode("Cull Mode", Float) = 0
Cull [_CullMode]
全体のシェーダーコードは以下のようになります。
複数の機能を1つのシェーダーにまとめる
こちらはかなり骨太な感じの内容となっていますが、実装方法をマスターすると可能性が広がります。
サンプルとして、グレースケールの元テクスチャに対して、白と黒の部分を別の色に差し替えて利用したいとします。以下のものはフラグメントシェーダーを内容が分かる程度に書いたものです。
fixed4 frag(v2f i) : COLOR { fixed4 t = tex2D(_MainTex, i.uv); //テクスチャの色を取得 return t; }
上のシェーダーがテクスチャの色を表示するだけものに対し、下のものは色を差し替えて表示するものです。
fixed4 frag(v2f i) : SV_Target { fixed4 t = tex2D(_MainTex, i.uv); //テクスチャの色を取得 fixed lum = Luminance(t.rgb); //テクスチャの色から輝度を計算 t = lerp(_LerpColorBlack, _LerpColorWhite, lum); //黒に入れる色、白に入れる色を輝度で補間する return t; }
これを1つに合体させるには、#ifで処理を切り替えて書き分けるというのがまず最初の作業です。こんな感じになります。
fixed4 frag(v2f i) : SV_Target { fixed4 t = tex2D(_MainTex, i.uv); //テクスチャの色を取得 #if _USE_LERP_COLOR fixed lum = Luminance(t.rgb); //テクスチャの色から輝度を計算 t = lerp(_LerpColorBlack, _LerpColorWhite, lum); //黒に入れる色、白に入れる色を輝度で補間する #endif return t; }
あとは、#pragma multi_compileという指定でキーワードに応じたシェーダーのバリアント(バリエーション)を用意することができます。見かけは1つのシェーダーですが、指定されたキーワードに応じて選ばれるシェーダーが自動的に切り替わるというものです。以下のように書きます。
#pragma multi_compile _ _USE_LERP_COLOR
全体のシェーダーコードは以下のようになります。
これだけだと機能の切り替えがGUIから出来ないので、そちらの部分を作っていきます。機能の切り替えは、マテリアルに対しShaderKeywordを設定することで切り替えることが出来ます。Material.EnableKeywordや、Material.DisableKeywordを使用します。あるいは、Material.shaderKeywordsを直接書き換える方法もとれます。
GUIから操作させるのに一番いいのは、MaterialのInspectorを乗っ取ることで、機能に合わせて必要なパラメータだけを出したり、それに合わせて上記のShaderKeywordを切り替えたりが行えます。
Inspectorを乗っ取らずにキーワードON、OFFだけを行うのであれば、プロパティに指定を加えるだけで簡単に設定が可能ですが、その場合は不必要なパラメータを隠すなどの凝った機能を付けることはできません。プロパティだけで行うには以下にようにします。
[Toggle(_USE_LERP_COLOR)]_UseLerpColor("Use Lerp Color", Float) = 0
ON、OFFだけの切り替えは上の方法で良いですが、複数の機能を切り替える場合は以下のように書きます。この場合は、Aを選択すると、_USEMODE_AというキーワードがONとなり、Bの場合は、_USEMODE_BがONとなります。
[KeywordEnum(A, B, C)]_UseMode("Mode Change", Float) = 0
さて、一番凝った機能を実装できる方法として、MaterialのInspectorを乗っ取るには、シェーダー側で使用するInspectorのクラス名を指定しておく必要があります。指定があると、そのシェーダーをInspectorで表示した際には指定のクラスで描画される事になります。シェーダーの最後に以下のように書き加えます。詳細についてはUnity公式のこちらを見てください。
CustomEditor "LerpColorInspector"
出来上がったものは以下です。いろいろゴニョゴニョやってますけど、肝となるのはOnInspectorGUI()の中で、shaderKeywordsを取得してきて現在の設定値を得て、それを元にToggleを描画して切り替えスイッチの機能を提供して、また設定値をshaderKeywordsとして書き戻すというところです。
以下の例では、先程のLerpColor機能のオンオフを切り替えるトグルスイッチを描画して、機能を使用しない場合には関連パラメータを表示しないように細工しています。使わないパラメータを隠すことでアーティストに無駄なことをさせないで済むというやつです。こんな感じで、機能ごとに切り替えを作っていくと良いです。
Inspector用のコードは以下のようになります。
社内で使用しているエフェクト統一シェーダーはこんな感じです
機能別にチェックボックスやドロップダウンでモードが切り替えられるようになっており、選択されたモードに応じて有効な設定値が出るようになっています。色々設定してパラメータがいっぱい出ると以下のように長くなります。
使える機能を列挙すると以下のような感じですが、一部の機能はすでにUnityに標準で備わっていますのでそろそろ使用頻度を考慮しつつ内容の再検討したほうがいいかもしれないとは思っていますが、ご参考になれば幸いです。
- マスク機能
- 色変更機能
- 輝度の暗い方に色を載せる
- 明るい方、暗い方にそれぞれ色を差し替える
- ブレンド機能
- 標準ブレンド
- 加算ブレンド
- 事前α乗算式ブレンド(設定値次第で加算も可能)
- Z値の操作機能
- Zテスト無し(最前面描画)
- Zオフセット
- ソフトパーティクル
- UVの操作機能
- UVスクロール
- パターンチェンジ
- UVミラーリング
- カットオフ機能
- パンチスルー(1bit抜き)
- 半透明カットオフ
- HDR輝度スケーリング
- RenderQueueの値操作
みなさんも一緒に働きませんか?
ここまで読んでくださった方、ありがとうございます。
TAセクションではこのようにアーティストの制作に役立つ環境を提供できるよう力を注いでおります。やりたいことを遠回りせずに行える環境でお仕事したい方はぜひ下記の弊社グループ採用サイトをご確認ください。いっしょに働きましょう!
現在いろいろな職種でアーティストを多数募集しております!