1.6K Views
May 09, 18
スライド概要
講演者:冨澤 茂樹(株式会社バンダイナムコスタジオ)
こんな人におすすめ
・グラフィックスプログラマー
・モバイルのプログラマー
受講者が得られる知見
・60fpsでBRDFレンダリングを行った事例
・カスタムシェーダーの活用方法
・カスタム描画の活用方法
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
DAY3 2018/5/9 16:30 カスタムシェーダーでモバイルでも 最先端グラフィックスな格闘ゲームを! 冨澤 茂樹 株式会社バ ンダ イナムコス タ ジオ
冨澤 茂樹 株式会社バンダイナムコスタジオ 2000年旧ナムコに同業他社より中途入社 内製ミドルウェア初代NUライブラリの立ち上げ開発・保守を行う 最適化エンジニアとして複数プロジェクトにて60fps化を行う 現在 TEKKEN™ MOBILE プロジェクトにてグラフィックス担当
TEKKEN™ MOBILE 開発体制 弊社バンクーバースタジオと本社東京スタジオとの共同開発
グラフィックス担当としての命題 TEKKEN™ MOBILE 制作にあたり アーケード / PS4 / Xbox One / Steam のプレイヤーさんが見劣りしないクォリティー
Unity 採用 Android の多種多様なデバイス バージョン チップセット 端末メーカー キャリア iOS も多種多様なデバイス 解像度 アスペクト比 経験者がいる
Unity 採用 Unity は賢い Unity はなんでもやってくれる ただし頼れば頼るほど CPU 負荷が増す
Unity 採用 そこで Unity にすべて乗っかるのではなく 使う機能を取捨選択することにした
取捨選択の例 Unity のライト Unity のリアルタイムライティングは行わない 静的なオブジェクト(ステージ)は事前ベイクライトマップ 動的なオブジェクト(キャラ)は独自ライティング Unity の影 Unity のリアルタイムシャドウイングは行わない 静的なオブジェクト(ステージ)はライトマップにベイク 動的なオブジェクト(キャラ)は独自シャドウイング
本日のトピック ☆☆☆★ ☆☆★★ ☆★★★ ★★★★ カスタムシェーダーを書く 自分でライトマップを貼る 自分で影をレンダリングする モバイル用物理ベースレンダリング
カスタムシェーダーを書く
カスタムシェーダーを書く Asset から Unlit Shader を作る
カスタムシェーダーを書く • Surface Shader は扱いません • Unity (OpenGL 系) ではフラグメントシェーダーと呼ぶところを 本セッションではピクセルシェーダーと呼ばせてください • ShaderLab の記述スタイルは CGINCLUDE ~ ENDCG の間に シェーダーコードを書きますがご了承ください(次ページ参照)
記述スタイル シェーダー名は重複しないように S h ader " Ban daiNamc o / Ho ge h o ge" { Pro pertie s { [ No S c aleOf f s et ] _Main T ex ( " B a se T ex tu re" , 2D ) = " wh ite" { } } CG INCLUD E #in c lu de " Un ityCG .c gin c " E ND CG ここにコードを書く S u bS h ader { T ags { " Qu eu e" = " G eo metry" " Ign o rePr o j ec to r" = " T ru e" " Ren derT ype" = “ Opaqu e” } Pass { CG PROG RA M #pragma vertex V S main #pragma f ragmen t PS main #pragma target 2.5 E ND CG } } 適宜 Ztest や Cu ll を追加 関数名は VSma i n と PSma i n } Cu sto mE dito r " Ban daiNa mc o .Ho g eh o g eS h ad er I n sp ec to r" インスペクターは大事
記述スタイル s amp l e r2D _ MainTe x; s t ruct VSinp ut { fl oat 4 p os it ion : P OSI TI ON; fl oat 2 uv : TEXC OORD0; }; s t ruct P Sinp ut { hal f2 uv : TEXC OORD0; fl oat 4 p os it ion : SV_ P OSI TI ON; }; P Sinp ut VSmain(V Si np ut i) { P Sinp ut o; o. p os it ion = U nit y Ob je ct To C l ip P os ( i. p o s it i o n ); o. uv = i. uv ; re t urn o; } fixe d 4 P Smain(P S i np ut i) : SV_ Targe t { re t urn fixe d 4(t e x2D( _ Ma i n Te x, i. uv )); }
自分でライトマップを貼る
ライトマップ Unity 内蔵の Enlighten でベイク Platform が Android / iOS だと LDR になってしまう PC, Mac & Linux Standalone なら HDR になる! HDR のテクスチャーを Android / iOS に持ち込む話は後ほど
PC, Mac & Linux Standalone で描画 PC, Mac & Linux Standalone でそのまま描画するなら簡単 UV値は TEXCOORD1 に入ってくる スケールオフセットは unity_LightmapST に入ってくる st ruct VS input { flo at 4 po sit io n : PO S IT IO N; flo at 2 uv : T EXCO O RD0; flo at 2 lmuv : T EXCO O RD1; }; PS input VS main(VS input i) { PS input o ; o .po sit io n = Unit yO bject T o ClipPo s(i. po sit io n) ; o .uv.xy = i.uv; o .uv.zw = i.lmuv * unit y_Light mapS T .xy + unit y_Light mapS T .zw ; ret urn o ; }
PC, Mac & Linux Standalone で描画 PC, Mac & Linux Standalone でそのまま描画するなら簡単 テクスチャーは unity_Lightmap に入っている フェッチした値を DecodeLightmap() 関数を通すことで値を得る sampler2D _MainT ex; st ruct PS input { half4 uv : T EXCO O RD0; flo at 4 po sit io n : S V_PO S IT IO N; }; fixed4 PS main(PS input i) : S V_T arget { half4 result = t ex2D(_MainT ex, i.uv.xy); result .rgb *= Deco deLight map(t ex 2D(u nit y_L ight map , i.uv.zw ); ret urn fixed4(result ); }
Android / iOS に HDR テクスチャーを持ち込む トーンマップで使われる Reinhard 変換を利用 [Reinhard et al . 2002] Reinhard 変換をかけ LDR で保存 𝑥 𝑓 𝑥 = 1+𝑥 シェーダー内で Reinhard 逆変換をかけて HDR に戻す 𝑥 ,𝑥≠1 𝑓 𝑥 = 1−𝑥
Android / iOS に HDR テクスチャーを持ち込む ライトマップに Reinhard 変換をかけるシェーダーを書く PS input VS main(VS input i) { PS input o ; o .po sit io n = mul(UNIT Y_MAT RI X_P , i.po sit io n); o .uv = i.uv; ret urn o ; } fixed4 PS main(PS input i) : S V_T arget { fixed4 light map = t ex2D(_MainT ex, i.uv); half3 result = Deco deLight map(li ght ma p) ; result = result / (1.0h + result ); ret urn fixed4(result , 1); }
Android / iOS に HDR テクスチャーを持ち込む 先ほどのシェーダーでライトマップを保存 Mat erial mat erial = new Mat erial(m_shader); T ext ure2D inT ex = Light mapS et t ings.li ght m aps[ ind ex ]. li ght m apCo lo r RenderT ext ure t mpRT = RenderT ext ure.Get T empo r ary(i nT e x.w idt h, inT ex.height , 0, RenderT ext ureFo rmat .ARG B3 2) ; Graphics.Blit (inT ex , t mpRT , mat erial); o ut T ex.ReadPixel s(new Rect (0, 0, inT ex.w idt h, inT ex.height ), 0, 0, false); o ut T ex.Apply(); RenderT ext ure.Rele aseT e mpo r ary(t mpRT );
Android / iOS に HDR テクスチャーを持ち込む
描画シェーダー
テクスチャーは自分で変数を用意する
フェッチした値を Reinhard 逆変換をかける
sampler2D _MainT ex;
sampler2D _Light map;
fixed4 PS main(PS input i) : S V_T arget
{
half4 result = t ex2D(_MainT ex, i.uv.xy);
half3 light map = t ex2D(_Light t map, i.uv.zw );
light map = light map / (1.0h - min(0.9h, light map));
result .rgb *= light map;
ret urn fixed4(result );
}
自分で影をレンダリング
影のレンダリング • まずはシャドウマップ • 基本的には光源位置からライティング方向へデプスを レンダリングする(シャドウカメラ≠視点カメラ) • 近年ではきれいな影をレンダリングするためにデプス以外にも いくつかのパラメーターを記録するようになってきた • モバイルではまだしばらくデプスシャドウマップか
シャドウマップ シャドウマップ技法の種類 [Wimmer et al . 2004] USM PSM LSPSM [Wimmer et al. 2004]より引用
USM Uniform Shadow Maps 平行投影でシャドウマップをレンダリングする メリット 簡単 デメリット 視点近くの影の解像度が粗くなる
PSM Perspective Shadow Maps 視点カメラの Perspective を適用し視点近くの解像度を上げる メリット 視点近くの解像度が高い デメリット 場合分けする必要がある 視点より後ろの影を落とすオブジェクトの処理ができない
LSPSM Light Space Perspective Shadow Maps 視点近くほど解像度が高く 必要な全てのオブジェクトをレンダリングする メリット 視点近くの解像度が高い 全てのオブジェクトに対応する デメリット 視線ベクトルと光線ベクトルが平行に近いと使用できない
影をレンダリングする手順 • • • • • • シャドウマップ用カメラ(シャドウカメラ)を作る シャドウマップ用 RenderTexture を作る シャドウマップレンダリング用シェーダーを作る シャドウマップレンダリングするシェーダーにキーワードをつける メインカメラ(視点カメラ)に C# コードをコンポーネントとして追加 影を受けるシェーダーに影をレンダリングするコードを追加
シャドウカメラを作る GameObject の Camera を追加 disable にしておく orthographic に設定 範囲を調整
シャドウマップ用 RenderTexture を作る Assets で RenderTexture を作る フォーマットによる違い RenderTextureFormat.Depth 自分でデプスを比較して処理する必要がある RenderTextureFormat.Shadowmap ハードウェア比較機能を使うことができる 後者が圧倒的にラク
シャドウマップレンダリング用シェーダーを作る st ruct VS input { flo at 4 po sit io n : PO S IT IO N; }; st ruct PS input { flo at 4 po sit io n : S V_PO S IT IO N; }; PS input VS main(VS input i) { PS input o ; o .po sit io n = Unit yO bject T o ClipPo s(i. po sit io n) ; ret urn o ; } fixed4 PS main(PS input i) : S V_T arget { ret urn fixed4(0,0,0 ,0 ); }
シャドウマップレンダリングするシェーダーにキーワードをつける シャドウマップ用シェーダーと 影を落とすモデルのシェーダーに “RenderType” のキーワードを同じものを指定する S ubS hader { T ags { "RenderT ype" = “Cast S hado w ” } } Pass { … }
視点カメラに C# コードをコンポーネントとして追加 CastShadow.cs を追加 OnPreRender メッセージで Camera.RenderWithShader() を呼ぶ public class Cast S hado w : Mo no Behavio ur { public Camera m_shado w Camera; public S hader m_shader; public RenderT ext ure m_renderT ext ure; public Renderer[] m_receivers; vo id O nPreRender() { m_shado w Camera.t ar get T e xt ure = m_renderT ext ure; m_shado w Camera.Re nderW it hS hade r( m_sha der , “RenderT ype”); } // 続く
視点カメラに C# コードをコンポーネントとして追加 View マトリクスと Projection マトリクスを得て乗算する シャドウマップとマトリクスを影を受けるレンダラーに渡す vo id Updat e() { Mat rix4x4 shado w View = m_shado w Camera.w o rldT o Ca meraM at ri x ; Mat rix4x4 shado w Pro ject io n = m_shado w Camera.pro je ct io nM at ri x ; Mat rix4x4 receiveM at rix = shado w Pro ject io n * shado w View ; } } Mat erialPro pert yBlo ck pro p = new Mat erialPro pert yBlo ck () ; pro p.S et T ext ure("_S hado w T e x ", m_renderT ext ure); pro p.S et Mat rix("_S hado w M at ri x ", receiveM at rix); fo reach (Renderer r in m_receivers) { r.S et Pro pert yBlo ck(p ro p) ; }
影を受けるシェーダーに影をレンダリングするコードを追加 頂点シェーダー 受け取ったマトリクスで position を変換 mat rix4x4 _ReceiveMat rix; … half4 shado w po s : T EXCO O RDn; … PS input VS main(VS input i) { PS input o ; … flo at 4 w o rldPo s = mul(unit y_O bject T o W o rld, i.po sit io n); o .shado w po s = mul(_ReceiveMat rix , w o rldPo s); … ret urn o ; }
影を受けるシェーダーに影をレンダリングするコードを追加
ピクセルシェーダー
UNITY_SAMPLE_SHADOW() マクロで比較結果が返ってくる
影の中 == 0, 影の外 == 1
UNIT Y_DECLARE _S H ADO W MAP( _S hado w T ex );
fixed4 PS main(PS input i) : S V_T arget
{
half4 shado w po s;
shado w po s.xyz = i.shado w po s.xyz * 0.5h + 0.5h;
shado w po s.w = i.shado w po s.w ;
#if defined(UNIT Y_R EV E RS E D_ Z)
shado w po s.z = 1.0h - shado w po s.z;
#endif
}
half shado w = UNIT Y_S AMPLE_S HADO W (_S hado w T ex , shado w po s);
//half shado w = UNIT Y_S AMPLE_S HADO W _ PRO J (_S had o w T ex , shado w po s);
ret urn fixed4(shado w .x x x, 1);
自分でマトリクスを作る 自分でマトリクスを作れば各技法を実装可能 ただし GL.GetGPUProjectionMatrix() 関数を呼ぶ必要あり vo id Updat e() { Mat rix4x4 shado w View = Creat eLo o kAt (); Mat rix4x4 shado w Pro ject io n = Creat ePro ject io n(); Mat rix4x4 receiveM at rix = shado w Pro ject io n * shado w View ; Mat rix4x4 cast Mat rix = GL.Get GPUPro ject io nMat r i x(sh ado w Pr o je ct io n , t rue) * shado w View ; Mat erialPro pert yBlo ck pro p = new Mat erialPro pert yBlo ck () ; } } …
自分でマトリクスを作る シャドウマップ用シェーダーも 受け取ったマトリクスで position を変換するようにする mat rix4x4 _Cast Mat rix; PS input VS main(VS input i) { PS input o ; flo at 4 w o rldPo s = mul(unit y_O bject T o W o rld, i.po sit io n); o .po sit io n = mul(_Cast Mat rix, w o rldPo s); } ret urn o ;
おまけ さらに最適化した結果こうなりました OnPreRender メッセージで Graphics.ExecuteCommandBuffer() を呼ぶ vo id O nPreRender() { m_co mmandBuffer .C lea r() ; m_co mmandBuffer .S et R ende rT ar get ( m_re nderT arget Id ); m_co mmandBuffer .C lea rRe nderT arg et (t rue , false, Co lo r.clear, 1.0f); fo reach (MeshRenderer r in m_renderers) { m_co mmandBuffer .Dr aw Re nder er(r , m_mat erial); } } Graphics.Execut eCo mma nd Buff er( m_ co m mand Bu ffer );
モバイル用物理ベースレンダリング
物理ベースレンダリング? 本アプリはフォトリアルを目指していない 光学物理シミュレーションを目指していない 物理ベースレンダリングで使われる関数 BRDF を利用 BRDF 法レンダリング BRDF : Bidirectional Reflectance Distribution Function
BRDF 法レンダリングの導入 リニア空間で HDR で行う ハイスペックハードウェアでは ハードウェアガンマ補正あり HDR レンダーターゲットあり マルチプルレンダーターゲットあり MRT 使えますが Forward Rendering で考えてみます
ハイスペックの BRDF 法レンダリング HDR で計算して HDR レンダーターゲットに 書き込む sRG B テクスチャー ハ ード ウェア リ ニア 変換 BRDF 計算 H D R レンダー ター ゲット
ハイスペックの BRDF 法レンダリング 全て描き終わったら HDR レンダーターゲットの 輝度から目標の輝度を求め トーンマッピングを行う 輝度判定 トーンマッピ ング ハ ード ウェア ガ ンマ補 正 LDR フ レーム バ ッ フ ァ
モバイルのスペック どれくらいのスペックに合わせるか? OpenGL ES バージョン? Vulkan / Metal に対応するか? GPU メーカー固有フォーマットに対応するか?
想定スペック ハードウェアガンマ補正なし テクスチャーは ETC / ETC2 / PVRTC メーカー固有フォーマットや新しい ASTC は使わない アルファチャンネルは本来の透過度で使用 ETC はアルファチャンネルなし PVRTC はアルファチャンネル含めると画質が落ちる マルチプルレンダーターゲットなし レンダーターゲットは RGBA8 GPU のスペックを考慮
モバイル版 BRDF 法レンダリング モバイル(想定スペック)では 入力(テクスチャー)はハードウェアガンマ補正なしで LDR 出力(レンダーターゲット)もハードウェアガンマ補正なしで LDR 入力出力ともアルファチャンネルは本来の透過度で使用 ならば一つのシェーダー内で先ほどの全てを賄おう
モバイル版 BRDF 法レンダリング テクスチャーはリニア空間で HDR に変換し HDR で計算 通常テクスチャー H D R 向けテ クス チャ ー リニア空間へ変換 Rei nh a rd 逆変換 BRDF 計算
モバイル版 BRDF 法レンダリング あらかじめ設定しておいた EV 補正値で露出補正 同時にトーンマッピング 同時にガンマ補正 露出補正 トーンマッピ ング sRG B へ変換 LDR フ レーム バ ッ フ ァ
ガンマ補正 “UnityCG.cginc” のインライン関数を利用 sRGB からリニア空間への変換 half4 srgbColor = tex2D(_MainTex, i.uv); half3 linearColor = GammaToLi nearSp ace(s rgbC ol or.rgb ); リニア空間から sRGB への変換 half3 srgbColor = LinearToGamm aSp ace(li nearC ol or);
露出補正 & トーンマッピング 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);
モバイル版 BRDF 法レンダリング これでひとつのシェーダー内で BRDF 法レンダリングができた 他のリニア空間ではないオブジェクトとも調和 アルファブレンディングとの相性も良し
まとめ • Unity はかなりカスタマイズができる • CPU が重ければ自分で書くこともできる • 自分でシェーダーを書くと GPU 負荷コントロールにもなる
参考文献 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/lisp sm/ 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-renderin gsiggraph-2010-color-course
ご清聴ありがとうございました