マスクの前処理方式

Live2D Cubism 3 SDK for Nativeでは、スマートフォンなどで描画速度を維持するために、
モデル描画処理の最初に一枚のマスクバッファに対してすべてのマスク形状を描画する『前処理方式』を採用しています。

原則的な描画方法では、マスクを必要とするDrawableを描画するタイミングで、その都度マスク形状を描画します(図参照)。
この方法では、Drawableがマスクを必要とするたびにレンダーターゲットの切り替え・バッファのクリアなど比較的高コストな処理が発生することになります。
そのため、スマートフォンなどで描画速度が低下する原因になる場合があります。

しかし、前もってマスクを用意するだけではマスクバッファが複数枚必要になり、メモリーを圧迫することになります。
この点を解決するため、一枚のマスクバッファに対して以下の処理を行うことで、まるで複数枚のマスクバッファを利用しているかのように扱いつつ、メモリーの圧迫を抑えることができます。

 

マスクの統合

前もってすべてのマスクを生成するため、同じマスク指定を受けているDrawableは同一のマスク画像を使うことで生成する枚数を抑えられます。

この処理はCubismRenderer_OpenGLES2::Initialize関数呼び出しの中で
CubismClippingManager_OpenGLES2::Initialize関数によって行われます。

 

 

色情報での分離

マスクバッファは実体として通常のテクスチャバッファなどと同じようにRGBAの映像用配列です。
通常のマスク処理ではこのAチャンネルのみを使用してマスクを適用しますがRGBのチャンネルは使用しません。
そこでRGBAで別々のマスクデータを持つことによって一枚のマスクバッファを4枚のマスクバッファとして取り扱えるようになります。

 

 

分割分離

マスク画像が4枚では足りなくなった時、マスクバッファを2分割、4分割、9分割で取り扱うことによってマスクの枚数を増やします。
色情報での分割もあるので4x9の36枚まで違うマスクを保持できるようになっています。

またマスク画像がつぶれるのを防ぐため、マスクの適用を受けるすべてのDrawableの矩形でマスクを描画します。
このため範囲の生成とマスク生成、マスク使用でのマトリックスの生成が必要になります。

 

 

 

矩形の確認

マスク生成の初めのステップで、マスクごとにマスク適用先すべてが収まる矩形を確認します。

 

 

色分離、分割分離を受けたレイアウト決定

マスクごとに所属するマスクバッファの色チャンネル、分割位置を定めます。

 

 

マスク描画、マスク使用のマトリックス生成

描画前に調べた矩形範囲と所属場所に基づいてマスク生成用、マスク使用用の変換マトリックスを用意します。

 

 

マスクバッファの動的なサイズ変更

GLES2レンダラには、実行時にマスクバッファのサイズを変更するAPIを用意してあります。
現状、マスクバッファのサイズは初期値として256*256(ピクセル)を設定していますが、マスク生成領域を9枚に切るような場合、
85*85(ピクセル)の矩形領域に描画したマスク形状を、更に拡大してクリッピング領域として使用します。
その結果、クリッピング結果のエッジがぼやけたり、滲みのような現象が見られます。
それを解決する方法として、マスクバッファのサイズをプログラム実行時に変更するAPIを用意しています。

例えば、マスクバッファのサイズを256*256 ⇒ 1024*1024とすることで、マスク生成領域を9枚に切るような場合、341*341の矩形領域にマスク形状を描画することができるので、
拡大してクリッピング領域として使用しても、クリッピング結果のエッジのぼやけやにじみを解消することができます。

※マスクバッファのサイズを大きくする ⇒ 処理するピクセルが増えると速度は遅くなるが、描画結果は綺麗になる。
※マスクバッファのサイズを小さくする ⇒ 処理するピクセルが減るので速度は速くなるが、描画結果は汚くなる。

 

 

前処理方式によってパフォーマンスの向上が見込める理由

携帯端末特有の事情として、GPUに対するClear命令やレンダリングターゲット切り替え命令の処理コストが、他の命令よりも高い場合があります。
原則的方式で描画するときには、これら処理コストが高い命令を、マスクが必要になるDrawableの数だけ実行することになります。
しかし、前処理方式の場合では、これらの命令を実行する回数を削減することができるので、スマートフォンなどでのパフォーマンスの向上が見込めます。

実際にその効果を把握するため、レンダリングにおける各処理単位での時間コストを測定してみます。
測定方法として、以下に示すソースコードで確認します。 レイヤーはビルドごと分離して計測します。
また、テスト対象のモデルはHaru一体です。
測定する端末としてはAndroid機を2種類、Windows機を1種類用意しました。
測定はAndroid側でclock_gettime関数、Windows側でQueryPerformanceCounter関数を使用して バッファに結果をキャッシュして平均値を計算するという方針で測定します。

クリッピングマスク生成部(レイヤー1)

CubismClippingManager_OpenGLES2::SetupClippingContextでは描画ターゲットの切り替えや塗りつぶしの時間を測定します。

 

モデル描画全体(レイヤー1、2混在)

描画前、ソート、描画後処理の測定をします。

レイヤーを分けてマスク生成とそれ以外の描画という大枠でも測定します。

 

メッシュの描画(レイヤー1)

CubismRenderer_OpenGLES2::DrawMeshではシェーダーへの設定時間と描画命令単一時間を測定します。

 

レイヤー3

大枠としてUpdateの流れを
パラメーター計算、モデルのアップデート、レンダリングの3つに分けてみていきます。

 

 

結果

Android1 Android2 Winpc1
L1clear  1781.20  218.80  26.80
L1gldraw  45.47  51.63  10.58
L1sharder  12.31  9.34  5.37
L1post 1.50 1.00 0.10
L1switch 10.70 56.30  7.80
L1predraw 15.90 8.20 2.20
L1sort 7.60 7.00 0.60
L2MaskMake 2686.80 1357.60 318.50
L2draw 4004.10 4013.20 1217.00
L3paramupdate 392.00 375.40 89.70
L3modelupdate 1357.50 1410.90 1070.40
L3rendering 6715.70 5233.70 1892.00

上の表は先に示した部分の実行時間です。
携帯端末はClearのコストが高かったり、レンダリングターゲットの切り替えが他の命令に比べて重たいことがわかります。
この重い命令が原則的な方法で描画するときにはマスクが必要になるDrawableの数だけ走ります。
この計算が一回で済むためスマートフォンなどでのパフォーマンスの向上が見込めます。

 

Copyright © 2018 Live2D Inc.