はじめまして。2012年入社の渡邉です。
現在は雀王でUnityエンジニアをしております。雀王はiOS,Android向けの本格的な2人麻雀ゲームであり、Unityを用いて開発されました。

雀王公式ページ

この記事の内容は少し発展した使用方法であるため、基本的な部分に関しては以前の記事である中川さんの
非エンジニアが知ってると得するUnityの知識
で細かく書かれておりますので、そちらをご参照ください。


1. Unityにおけるシェーダ


Unityとは様々なプラットフォームに書き出すことが可能なゲームエンジンであり、多彩な表現をすることができます。その一つとしてシェーダという機能が用意されています。
シェーダとは画面に画像や図形を描画する機構で、そこに自分で書いたプログラムを組み込むことで光や影の表現やぼかしなど様々な効果を付けることができます。

UnityはShaderLabという記述文法を採用しています。OpenGLのシェーダ言語であるGLSLとは違い、描画系の設定が指定できるようになっているのが特徴です。
ShaderLabの記述は非常に多機能で様々なサイトでも説明されておりますので、今回は雀王のゲーム画面で作成した表現の概要だけに絞って書かせていただきます。

シェーダ - Unity公式リファレンス


図1. 雀王の麻雀プレイ画面

2. 影の表現


麻雀牌が置かれる盤面はゲーム中最も重要な部分ですが、違和感を減らすための表現の一つに「影」があります。
影がある場合とない場合では印象に大きな差が生じます。


図2. 影を適用した場合としない場合の見た目の違い

Unity標準でも影をサポートしていますが負荷が高く、ライトの設定に依存してしまうため独自の影を作成しました。
今回のゲームでは盤面が平らであることが保証されているので、公式から影の位置を求めることが可能です。
平面を P、ライトの位置 L、麻雀牌の頂点 V の時、直線 LV が平面 P と交差する点をV'とするとVからV'に座標変換する行列を生成することで影を表現することができます。


図3. 平面P, ライトL, 頂点V, 影V'の位置関係

こういったタイプの影がちょうどDirectX(Windowsで利用されているグラフィックスライブラリ)にあるのでそれが参考になります。
D3DXMatrixShadow」という関数が用意されていて、
マイクロソフト公式リファレンスに算出方法が載っています。
手順は以下の通りです。

1. 現在のライト情報から行列をC#で生成
2. その行列をシェーダにセット
3. 頂点シェーダのModel-View-Projection変換のModel-Viewの間に投影する行列を挟む
4. 頂点の位置が高いほど色が薄くなるように乗算
5. 影専用のカメラがRenderTextureに結果を書き込み

v2f vert (appdata_t v) {
v2f o;
float4 wv = mul(_Object2World, v.vertex);
float4 pv = mul(_ProjectMatrix, wv);
o.vertex = mul(UNITY_MATRIX_VP, pv);
float d = (wv.y * _FadeLength - 1) * -1;
o.color = _Color * d;
return o;
}



図4. 影専用カメラに書き込まれた影画像

すると図4のような画像が出来上がります。あとはこれを盤面に配置してスケールを合わせれば簡易影の完成です。
ライトの値を変化させることで図5のようなインパクトの有る影を生成することも可能です。


図5. ライトのwパラメータを変更した場合

影の投影先が平面であれば頂点シェーダに行列計算を任せることで低負荷かつインパクトの有る3Dオブジェクトの影をつくることができます。

3. 麻雀牌の表現


麻雀牌自体にも自作のシェーダを適用しています。

一般的な3Dモデルに用いられるDiffuseシェーダでは、ライトの情報のみに基づいて計算されます。そのため、図6のように思った通りの見た目にすることはなかなか難しいです。


図6. Unity標準のMobile/Diffuseシェーダを利用した場合

逆にライト情報も使わない単純に3Dモデルを表示する高速なシェーダでは図7のように見えます。


図7. Unity標準のUnlit/Textureシェーダを利用した場合

これではいかにも3D感が出てしまっています。
雀王では2.5Dのゲーム画面を目指したかったため今回作成したシェーダでは影と馴染むように盤面に近いほど暗くしました。また、アニメ調にすることで1枚画像のような見た目にしています。

Unityでは次のような短いコードで実現できます。
void vert(inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT(Input, o);
half s = min(_Max, mul(_Object2World, v.vertex).y) / _Max;
o.fadeColor = lerp(_FadeColor, 1, s);
}
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * IN.fadeColor;
}
half4 LightingRamp (SurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot (s.Normal, lightDir);
half diff = NdotL * 0.5 + 0.5;
half3 ramp = tex2D (_Ramp, half2(diff)).rgb;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2) * _Color;
c.a = s.Alpha;
return c;
}


この計算によって、雀王の実際の画面では図8のように表示されています。


図8. 自作の麻雀牌シェーダを利用した場合

表面は白く、盤面に近いほど暗くなっているのがわかるかと思います。また、ライティング結果を段階的にする(トゥーン)処理を挟んでいるため明るい部分と陰になっている部分のメリハリもついています。

4. 最後に


シェーダの全てに関しては説明しきれないため今回は紹介程度になってしまいましたが、工夫次第で様々な表現ができることが伝われば幸いです。
Unityは物理演算や描画処理等を統合的に扱え、個人であれば一部制限付きですが無料でも利用することが可能です。
デフォルトの機能のみで作成してもいいですが、シェーダを活用することで一気に出来る表現が広がります。
公式のサンプルもありますので、試してみると面白いと思います。

最後まで読んでいただきありがとうございました。

Blog: 太郎Work