Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

【Unite Tokyo 2018】カスタムシェーダーでモバイルでも最先端グラフィックスな格闘ゲームを!

2,602 views

Published on

講演者:冨澤 茂樹(株式会社バンダイナムコスタジオ)

こんな人におすすめ
・グラフィックスプログラマー
・モバイルのプログラマー

受講者が得られる知見
・60fpsでBRDFレンダリングを行った事例
・カスタムシェーダーの活用方法
・カスタム描画の活用方法

Published in: Technology
  • Be the first to comment

【Unite Tokyo 2018】カスタムシェーダーでモバイルでも最先端グラフィックスな格闘ゲームを!

  1. 1. カスタムシェーダーでモバイルでも 最先端グラフィックスな格闘ゲームを! D A Y 3 2 0 1 8 /5 /9 1 6 :3 0 冨澤 茂樹 株式会社バンダイナムコスタジオ
  2. 2. 2000年旧ナムコに同業他社より中途入社 内製ミドルウェア初代NUライブラリの立ち上げ開発・保守を行う 最適化エンジニアとして複数プロジェクトにて60fps化を行う 現在 TEKKEN™ MOBILE プロジェクトにてグラフィックス担当 株式会社バンダイナムコスタジオ 冨澤 茂樹
  3. 3. TEKKEN™ MOBILE 開発体制 弊社バンクーバースタジオと本社東京スタジオとの共同開発
  4. 4. グラフィックス担当としての命題 TEKKEN™ MOBILE 制作にあたり アーケード / PS4 / Xbox One / Steam のプレイヤーさんが見劣りしないクォリティー
  5. 5. Unity 採用 Android の多種多様なデバイス バージョン チップセット 端末メーカー キャリア iOS も多種多様なデバイス 解像度 アスペクト比 経験者がいる
  6. 6. Unity 採用 Unity は賢い Unity はなんでもやってくれる ただし頼れば頼るほど CPU 負荷が増す
  7. 7. Unity 採用 そこで Unity にすべて乗っかるのではなく 使う機能を取捨選択することにした
  8. 8. 取捨選択の例 Unity のライト Unity のリアルタイムライティングは行わない 静的なオブジェクト(ステージ)は事前ベイクライトマップ 動的なオブジェクト(キャラ)は独自ライティング Unity の影 Unity のリアルタイムシャドウイングは行わない 静的なオブジェクト(ステージ)はライトマップにベイク 動的なオブジェクト(キャラ)は独自シャドウイング
  9. 9. 本日のトピック ☆☆☆★ カスタムシェーダーを書く ☆☆★★ 自分でライトマップを貼る ☆★★★ 自分で影をレンダリングする ★★★★ モバイル用物理ベースレンダリング
  10. 10. カスタムシェーダーを書く
  11. 11. カスタムシェーダーを書く Asset から Unlit Shader を作る
  12. 12. カスタムシェーダーを書く • Surface Shader は扱いません • Unity (OpenGL 系) ではフラグメントシェーダーと呼ぶところを 本セッションではピクセルシェーダーと呼ばせてください • ShaderLab の記述スタイルは CGINCLUDE ~ ENDCG の間に シェーダーコードを書きますがご了承ください(次ページ参照)
  13. 13. 記述スタイル Shader "BandaiNamc o/ Ho ge ho ge" { Propertie s { [NoScaleOffs et ] _MainTex("B a se Texture", 2D) = "white" {} } CGINCLUDE #include "UnityCG.cginc" ENDCG SubShader { Tags { "Queue" = "Geometry" "IgnoreProj ec to r" = "True" "RenderType" = “Opaque” } Pass { CGPROGRAM #pragma vertex VSmain #pragma fragment PSmain #pragma target 2.5 ENDCG } } CustomEditor "BandaiNa mco .Ho g eho g eSh ad erInsp ec to r" } ここにコードを書く 関数名は VSmain と PSmain インスペクターは大事 適宜 Ztest や Cull を追加 シェーダー名は重複しないように
  14. 14. 記述スタイル sampler2D _MainTex; struct VSinput { float4 position : POSITION; float2 uv : TEXCOORD0; }; struct PSinput { half2 uv : TEXCOORD0; float4 position : SV_POSITION; }; PSinput VSmain(V Sinp ut i) { PSinput o; o.position = UnityObjectToClipP os (i.posit ion); o.uv = i.uv; return o; } fixed4 PSmain(PSinput i) : SV_Target { return fixed4(tex2D(_Ma inTe x, i.uv)); }
  15. 15. 自分でライトマップを貼る
  16. 16. ライトマップ Unity 内蔵の Enlighten でベイク Platform が Android / iOS だと LDR になってしまう PC, Mac & Linux Standalone なら HDR になる! HDR のテクスチャーを Android / iOS に持ち込む話は後ほど
  17. 17. PC, Mac & Linux Standalone で描画 struct VSinput { float4 position : POSITION; float2 uv : TEXCOORD0; float2 lmuv : TEXCOORD1; }; PSinput VSmain(VSinput i) { PSinput o; o.position = UnityObjectToClipPos(i.posit ion); o.uv.xy = i.uv; o.uv.zw = i.lmuv * unity_LightmapST.xy + unity_LightmapST.zw; return o; } PC, Mac & Linux Standalone でそのまま描画するなら簡単 UV値は TEXCOORD1 に入ってくる スケールオフセットは unity_LightmapST に入ってくる
  18. 18. PC, Mac & Linux Standalone で描画 sampler2D _MainTex; struct PSinput { half4 uv : TEXCOORD0; float4 position : SV_POSITION; }; fixed4 PSmain(PSinput i) : SV_Target { half4 result = tex2D(_MainTex, i.uv.xy); result.rgb *= DecodeLightmap(tex2D(u nity_L ight map , i.uv.zw); return fixed4(result); } PC, Mac & Linux Standalone でそのまま描画するなら簡単 テクスチャーは unity_Lightmap に入っている フェッチした値を DecodeLightmap() 関数を通すことで値を得る
  19. 19. Android / iOS に HDR テクスチャーを持ち込む トーンマップで使われる Reinhard 変換を利用[Reinhard et al. 2002] Reinhard 変換をかけ LDR で保存 シェーダー内で Reinhard 逆変換をかけて HDR に戻す 𝑓 𝑥 = 𝑥 1 + 𝑥 𝑓 𝑥 = 𝑥 1 − 𝑥 , 𝑥 ≠ 1
  20. 20. Android / iOS に HDR テクスチャーを持ち込む PSinput VSmain(VSinput i) { PSinput o; o.position = mul(UNITY_MATRIX_P, i.position); o.uv = i.uv; return o; } fixed4 PSmain(PSinput i) : SV_Target { fixed4 lightmap = tex2D(_MainTex, i.uv); half3 result = DecodeLightmap(lightmap); result = result / (1.0h + result); return fixed4(result, 1); } ライトマップに Reinhard 変換をかけるシェーダーを書く
  21. 21. Android / iOS に HDR テクスチャーを持ち込む Material material = new Material(m_shader); Texture2D inTex = LightmapSettings.lightmaps[ind ex].lightmapCo lor RenderTexture tmpRT = RenderTexture.GetTempor ary(inTe x.w idth, inTex.height, 0, RenderTextureFormat.ARG B32); Graphics.Blit(inT ex, tmpRT, material); outTex.ReadPixels(new Rect(0, 0, inTex.width, inTex.height), 0, 0, false); outTex.Apply(); RenderTexture.Rele aseTe mpor ary(t mpRT ); 先ほどのシェーダーでライトマップを保存
  22. 22. Android / iOS に HDR テクスチャーを持ち込む sampler2D _MainTex; sampler2D _Lightmap; fixed4 PSmain(PSinput i) : SV_Target { half4 result = tex2D(_MainTex, i.uv.xy); half3 lightmap = tex2D(_Lighttmap, i.uv.zw); lightmap = lightmap / (1.0h - min(0.9h, lightmap)); result.rgb *= lightmap; return fixed4(result); } 描画シェーダー テクスチャーは自分で変数を用意する フェッチした値を Reinhard 逆変換をかける
  23. 23. 自分で影をレンダリング
  24. 24. 影のレンダリング • まずはシャドウマップ • 基本的には光源位置からライティング方向へデプスを レンダリングする(シャドウカメラ≠視点カメラ) • 近年ではきれいな影をレンダリングするためにデプス以外にも いくつかのパラメーターを記録するようになってきた • モバイルではまだしばらくデプスシャドウマップか
  25. 25. シャドウマップ シャドウマップ技法の種類[Wimmer et al. 2004] USM PSM LSPSM [Wimmer et al. 2004]より引用
  26. 26. USM Uniform Shadow Maps 平行投影でシャドウマップをレンダリングする メリット 簡単 デメリット 視点近くの影の解像度が粗くなる
  27. 27. PSM Perspective Shadow Maps 視点カメラの Perspective を適用し視点近くの解像度を上げる メリット 視点近くの解像度が高い デメリット 場合分けする必要がある 視点より後ろの影を落とすオブジェクトの処理ができない
  28. 28. LSPSM Light Space Perspective Shadow Maps 視点近くほど解像度が高く 必要な全てのオブジェクトをレンダリングする メリット 視点近くの解像度が高い 全てのオブジェクトに対応する デメリット 視線ベクトルと光線ベクトルが平行に近いと使用できない
  29. 29. 影をレンダリングする手順 • シャドウマップ用カメラ(シャドウカメラ)を作る • シャドウマップ用 RenderTexture を作る • シャドウマップレンダリング用シェーダーを作る • シャドウマップレンダリングするシェーダーにキーワードをつける • メインカメラ(視点カメラ)に C# コードをコンポーネントとして追加 • 影を受けるシェーダーに影をレンダリングするコードを追加
  30. 30. シャドウカメラを作る GameObject の Camera を追加 disable にしておく orthographic に設定 範囲を調整
  31. 31. シャドウマップ用 RenderTexture を作る Assets で RenderTexture を作る フォーマットによる違い RenderTextureFormat.Depth 自分でデプスを比較して処理する必要がある RenderTextureFormat.Shadowmap ハードウェア比較機能を使うことができる 後者が圧倒的にラク
  32. 32. シャドウマップレンダリング用シェーダーを作る struct VSinput { float4 position : POSITION; }; struct PSinput { float4 position : SV_POSITION; }; PSinput VSmain(VSinput i) { PSinput o; o.position = UnityObjectToClipPos(i.posit ion); return o; } fixed4 PSmain(PSinput i) : SV_Target { return fixed4(0,0,0,0); }
  33. 33. シャドウマップレンダリングするシェーダーにキーワードをつける SubShader { Tags { "RenderType" = “CastShadow” } Pass { … } } シャドウマップ用シェーダーと 影を落とすモデルのシェーダーに “RenderType” のキーワードを同じものを指定する
  34. 34. 視点カメラに C# コードをコンポーネントとして追加 public class CastShadow : MonoBehaviour { public Camera m_shadowCamera; public Shader m_shader; public RenderTexture m_renderTexture; public Renderer[] m_receivers; void OnPreRender() { m_shadowCamera.tar getTexture = m_renderTexture; m_shadowCamera.Re nderW ithShade r(m_shader , “RenderType”); } // 続く CastShadow.cs を追加 OnPreRender メッセージで Camera.RenderWithShader() を呼ぶ
  35. 35. 視点カメラに C# コードをコンポーネントとして追加 void Update() { Matrix4x4 shadowView = m_shadowCamera.worldTo CameraM atrix; Matrix4x4 shadowProjection = m_shadowCamera.proje ctionM atrix; Matrix4x4 receiveMatrix = shadowProjection * shadowView; MaterialPropertyBlo ck prop = new MaterialPropertyBlock(); prop.SetTexture("_ShadowTe x", m_renderTexture); prop.SetMatrix("_ShadowM atrix", receiveMatrix); foreach (Renderer r in m_receivers) { r.SetPropertyBlock(p rop); } } } View マトリクスと Projection マトリクスを得て乗算する シャドウマップとマトリクスを影を受けるレンダラーに渡す
  36. 36. 影を受けるシェーダーに影をレンダリングするコードを追加 matrix4x4 _ReceiveMatrix; … half4 shadowpos : TEXCOORDn; … PSinput VSmain(VSinput i) { PSinput o; … float4 worldPos = mul(unity_ObjectToWorld, i.position); o.shadowpos = mul(_ReceiveMatrix, worldPos); … return o; } 頂点シェーダー 受け取ったマトリクスで position を変換
  37. 37. 影を受けるシェーダーに影をレンダリングするコードを追加 UNITY_DECLARE_SHADOWMAP(_ShadowT ex); fixed4 PSmain(PSinput i) : SV_Target { half4 shadowpos; shadowpos.xyz = i.shadowpos.xyz * 0.5h + 0.5h; shadowpos.w = i.shadowpos.w; #if defined(UNITY_REVERSED_ Z) shadowpos.z = 1.0h - shadowpos.z; #endif half shadow = UNITY_SAMPLE_SHADOW (_ShadowT ex, shadowpos); //half shadow = UNITY_SAMPLE_SHADOW_ PROJ (_Shad owTex, shadowpos); return fixed4(shadow.xxx, 1); } ピクセルシェーダー UNITY_SAMPLE_SHADOW() マクロで比較結果が返ってくる 影の中 == 0, 影の外 == 1
  38. 38. 自分でマトリクスを作る void Update() { Matrix4x4 shadowView = CreateLookAt(); Matrix4x4 shadowProjection = CreateProjection(); Matrix4x4 receiveMatrix = shadowProjection * shadowView; Matrix4x4 castMatrix = GL.GetGPUProjectionMatr ix(sh adowPr oje ction , true) * shadowView; MaterialPropertyBlo ck prop = new MaterialPropertyBlock(); … } } 自分でマトリクスを作れば各技法を実装可能 ただし GL.GetGPUProjectionMatrix() 関数を呼ぶ必要あり
  39. 39. 自分でマトリクスを作る matrix4x4 _CastMatrix; PSinput VSmain(VSinput i) { PSinput o; float4 worldPos = mul(unity_ObjectToWorld, i.position); o.position = mul(_CastMatrix, worldPos); return o; } シャドウマップ用シェーダーも 受け取ったマトリクスで position を変換するようにする
  40. 40. おまけ void OnPreRender() { m_commandBuffer .Clear(); m_commandBuffer .SetRende rTar get(m_renderT arget Id ); m_commandBuffer .ClearRe nderT arget(true , false, Color.clear, 1.0f); foreach (MeshRenderer r in m_renderers) { m_commandBuffer .Dr awRe nder er(r , m_material); } Graphics.Execut eCo mmand Buff er(m_ command Bu ffer ); } さらに最適化した結果こうなりました OnPreRender メッセージで Graphics.ExecuteCommandBuffer() を呼ぶ
  41. 41. モバイル用物理ベースレンダリング
  42. 42. 物理ベースレンダリング? 本アプリはフォトリアルを目指していない 光学物理シミュレーションを目指していない 物理ベースレンダリングで使われる関数 BRDF を利用 BRDF 法レンダリング BRDF : Bidirectional Reflectance Distribution Function
  43. 43. BRDF 法レンダリングの導入 リニア空間で HDR で行う ハイスペックハードウェアでは ハードウェアガンマ補正あり HDR レンダーターゲットあり マルチプルレンダーターゲットあり MRT 使えますが Forward Rendering で考えてみます
  44. 44. ハイスペックの BRDF 法レンダリング HDR で計算して HDR レンダーターゲットに 書き込む ハードウェアリニア変換 BRDF 計算 HDR レンダーターゲット sRGB テクスチャー
  45. 45. ハイスペックの BRDF 法レンダリング 全て描き終わったら HDR レンダーターゲットの 輝度から目標の輝度を求め トーンマッピングを行う 輝度判定 トーンマッピング ハードウェアガンマ補正 LDR フレームバッファ
  46. 46. モバイルのスペック どれくらいのスペックに合わせるか? OpenGL ES バージョン? Vulkan / Metal に対応するか? GPU メーカー固有フォーマットに対応するか?
  47. 47. 想定スペック ハードウェアガンマ補正なし テクスチャーは ETC / ETC2 / PVRTC メーカー固有フォーマットや新しい ASTC は使わない アルファチャンネルは本来の透過度で使用 ETC はアルファチャンネルなし PVRTC はアルファチャンネル含めると画質が落ちる マルチプルレンダーターゲットなし レンダーターゲットは RGBA8 GPU のスペックを考慮
  48. 48. モバイル版 BRDF 法レンダリング モバイル(想定スペック)では 入力(テクスチャー)はハードウェアガンマ補正なしで LDR 出力(レンダーターゲット)もハードウェアガンマ補正なしで LDR 入力出力ともアルファチャンネルは本来の透過度で使用 ならば一つのシェーダー内で先ほどの全てを賄おう
  49. 49. モバイル版 BRDF 法レンダリング 通常テクスチャー HDR 向けテクスチャー リニア空間へ変換 Reinhard 逆変換 BRDF 計算 テクスチャーはリニア空間で HDR に変換し HDR で計算
  50. 50. モバイル版 BRDF 法レンダリング あらかじめ設定しておいた EV 補正値で露出補正 同時にトーンマッピング 同時にガンマ補正 露出補正 トーンマッピング sRGB へ変換 LDR フレームバッファ
  51. 51. ガンマ補正 “UnityCG.cginc” のインライン関数を利用 sRGB からリニア空間への変換 リニア空間から sRGB への変換 half4 srgbColor = tex2D(_MainTex, i.uv); half3 linearColor = GammaToLinearSpace(srgbColor.rgb); half3 srgbColor = LinearToGammaSpace(linearColor);
  52. 52. 露出補正 & トーンマッピング EV ± 値はあらかじめアーティストが設定 露出補正トーンマッピングは Filmic Tonemapping 近似式を使用[Hejl 2010] half3 x = max(0, linearColor – 0.004h); half3 result = (x * (6.2h * x + 0.5h)) / (x * (6.2h * x * 1.7h) + 0.06h);
  53. 53. モバイル版 BRDF 法レンダリング これでひとつのシェーダー内で BRDF 法レンダリングができた 他のリニア空間ではないオブジェクトとも調和 アルファブレンディングとの相性も良し
  54. 54. まとめ • Unity はかなりカスタマイズができる • CPU が重ければ自分で書くこともできる • 自分でシェーダーを書くと GPU 負荷コントロールにもなる
  55. 55. 参考文献 E. Reinhard, M. Stark, P. Shirley, J. Ferwerda “Photographic Tone Reproduction for Digital Images” SIGGRAPH 2002 http://www.cs.utah.edu/~reinhard/cdrom/ M. Wimmer, D. Scheizer, W. Purgathofer “Light Space Perspective Shadow Maps” Eurographics Symposium on Rendering 2004 https://www.cg.tuwien.ac.at/research/vr/lispsm/ J. Hejl, R. Burgess-Dawson, J. Hable “Filmic Tonemapping for Real-time Rendering” SIGGRAPH 2010 Color Course by H.P. Duiker https://www.slideshare.net/hpduiker/filmic-tonemapping-for-realtime-rendering- siggraph-2010-color-course
  56. 56. ご清聴ありがとうございました

×