2013年にリリースされたゲームTiny Thiefがリリースされました。これは、アニメーションアトラスなどを含むビルドでのビットマップグラフィックスの拒否により、モバイルFlash(AIR)開発環境に大きなノイズをもたらしました。 。
これにより、独自のコンテンツを大量に使用し、インストールファイルのサイズを最大70メガバイト(* Google Playの.apkファイル)まで保存できました。 最近、モバイルデバイスでのベクターグラフィックスのレンダリングのトピック(および一般にハードウェアサポートを使用したベクターのレンダリングのトピック)に対する関心が再び高まり、このトピックに関する「エントリレベル」レベルの情報の欠如に驚きました。 これは、ベクトルと既存のソリューションを描画する方法、およびこれらのことを自分で行う方法に関するリファレンス記事です。
メインウェイ
ベクトルを描画する最も一般的な方法は、すべての形状、曲線などを取得し、三角形化アルゴリズム(閉じた輪郭を三角形に分割)でそれらを通過し、同じ塗りつぶされたオブジェクトでさまざまなストロークと線を取得し、記述された数学的な近似表現を取得することです図の式。
つまり、この方法で描かれたベクトル円は、実際にはポリゴンになります。 この場合の品質基準は、結果として得られる三角形の数とサイズになります。
左から右へ-
- Inkscapeのソースコード、
- 少数の三角形で三角測量した結果、
- 結果の三角形
このような回避策の理由は簡単です。グラフィックカードは、頂点、三角形、ピクセルでのみ効率的に機能します(GPGPUは少し異なる話ですが、この文脈では、言及する価値があるだけです)。 CPUを使用して数学的に正しいモデルの表現を描画する場合、これにはさらに時間がかかります。 したがって、そのまま生でレンダリングするグラフィックカードを三角測量して送信します。
落とし穴
三角形のこのような粗いレンダリングは、エイリアシング効果の出現につながります-画像のエッジのグラデーション(これは上のスクリーンショットではっきりと見えます)。 この問題は、三角形として表される不透明なジオメトリに固有のものです。
Tiny Thiefのスクリーンショットを見ると、ゲームにこの欠点がないことがすぐにわかります。オブジェクトの端は美しく滑らかです。
現地調査
Adreno Profiler、NVIDIA PerfHUD ES、およびUnityを使用して、以下で説明するすべてのことをテストしました(提案されたソリューションの機能を確認します)。
カラーグリッドモードを有効にすると、Adreno Profilerに表示される内容は次のとおりです。
つまり、三角形分割の同じ方法でレンダリングします。 頂点は、テクスチャ(頂点のカラーパラメータ)なしで直接ペイントされます。
アルファバッファの内容は次のとおりです(明らかに、Adreno GPUには「アルファバッファ」などがあります)。
オブジェクトのエッジに沿って、薄い単一ピクセルのストリップ。 興味深いことに、アルファチャネルは隣接するオブジェクト間のエッジで「白」です(白い背景は色付きの文字です)。つまり、ロゴ全体が1つのパスで描画され、そのようなオブジェクト内のスムージングはわずかに異なる方法で実装されます。
シェーダー:
{ lowp vec4 fcolor; // color fcolor = color; // , factor fcolor.a *= factor.a; fcolor = fcolor; gl_FragColor = fcolor; }
スムージングの本質は多かれ少なかれ明確です-三角形化するとき、オブジェクトのエッジに沿って細い三角形のセットを追加します。
どんなに写真を近づけようとしても、これらのずるい細い三角形を見つけることはできませんでした。 しかし、幸いなことに、Adfno ProfilerはPerfHUDとは異なり、ジオメトリをテキスト形式でエクスポートできます。
分割組立
単純なパーサーを作成した後、Unityで元のメッシュを復元することが判明しました。 しかし、奇妙な写真が待っていました。
スムージングのないフレーム。 グリッドビューモードでは、塗りつぶしの三角形も表示されません。
長い間、何が問題なのか理解できませんでした。 塗りつぶし三角形が反対方向に回転していることがわかりました。 これは、反対側からロゴを見ると明らかになります。
また、ロゴの要素の間には、グラデーションで塗りつぶされた空の線があり(グラデーションは頂点を対応する色でペイントすることによって作成されます)、スムージングがありません。
シェーダーでバックフェースカリングを削除すると、結果として得たいものが得られます。
しかし、興味深い機能が発生します-このオブジェクトをカメラに近づけると、スムージングが目立ちやすくなり、ブラーのように見えます。
つまり、画面のサイズに応じて、所定の場所で毎回三角測量が行われ、このオブジェクトのサイズの変更を意味しません。 三角形のサイズは、合計幅が1画面ピクセル以下になるように計算されます。
画面上のほとんどすべてのオブジェクトは同じ方法で描画されます。 例外は背景であり、テクスチャで一度レンダリングされます。
統計
キャラクターのレンダリングに関する要約情報を見るのは十分に興味深いものです。平均して、三角形の形の各ヒーロー(ガード、料理人)は約3〜4000個の三角形です。 良質の低ポリ3Dモデルのようなものです。 グリッドは非常に密集しているため、オブジェクトはテクスチャで描画されているようです。
ロゴはほぼ9000の三角形を占めています。 描画呼び出しの合計数は平均で約100です(背景がテクスチャとして描画されない場合ははるかに大きくなります)が、FPSは古いZTE V811(Beeline smart 2)でも安定して最大です。
一般に、ベクターグラフィックスを貯金箱に描画する最初の(そして主要な)方法を取ります。
ベクトル画像を三角形分割し、ジョイントに沿って中間色で細い境界線を作成し、エッジに沿って薄い半透明のストリップを作成します。
逆さま
SDF
ベクトル画像の色の数に制限を設定すると、まったく異なる方法を使用できます。 単純なベクトルモノクロアイコンがあるとします。
符号付き距離フィールドを使用すると、品質をほとんど損なうことなく「圧縮」できます。 一番下の行は、テクスチャ自体を保存するのではなく、アイコンの境界までのピクセルの距離に関する情報を保存するということです。 通常、境界の値は0.5に等しいと見なされます。 アイコンの「内側」にあると考えられるものすべて。 少ないのは「外側」です。 実際、どちらの方法で境界線を上回るかは問題ではありません。内側を0.5未満、外側を0.5以上にすることができる場合があります。 わかりやすくするため(白い背景に黒いアイコン)、そのようなオプションのみを示します。
このように塗りつぶされたサイコロは次のようになります。
通常のぼかしとの違いは、現在のピクセルと境界線の間の最小距離を見つけることです。いずれの場合も、法線に沿った距離を計算します(ポイントからラインまでの最小距離は常に垂線によって決まります)。 つまり、テクスチャのグラデーションは、最も近い境界線の法線の方向を表します。
インターネット、特にハブには、SDFに関する多くの記事があります。記事の最後にそれらを掲載します。
この写真は、通常のテクスチャとSDFの2つのオプションの品質の違いを明確に示しています。 通常の画像を拡大すると、ぼかしがはっきりと見えるようになります。 いずれにせよ、SDFテクスチャを増やすことにより、シャープな境界が得られます。 さらに、テクスチャサイズを半分に縮小しても、アーティファクトの存在はほとんど見えないままです(生のSDFテクスチャの品質を向上させることについて別の記事を書くことができます)。 アーティファクトは、通常のテクスチャとは対照的に、アイコンの斜めの端に滑らかなはしごとして表示されます。 これは、ピクセルが正確に水平および垂直に移動するという事実によるものであり、画像サイズが縮小されると、2つの垂直な直線の助けを借りて斜めの直線を近似する精度も低下します(法線ベクトルを境界に近似することを思い出させてください)。
レンダリング用のシェーダーは、テクスチャを読み取るよりも少し複雑になります。 実験では、次のような多くの異なるオプションを試しました。 また、記事[2]の変形で、一般的には次のようになります。
float4 frag (v2fSDF f) : SV_Target { float2 uv = f.uv; half4 texColor = tex2D(_MainTex, uv); // - half distance = texColor.a; // half smoothing = _Smoothing; // - _Dilate = 0.5 half2 range = half2(_Dilate - smoothing, _Dilate + smoothing); // ( - ) half totalSmoothing = smoothstep(range.x, range.y, distance); half3 rgb = f.color.rgb; return float4(rgb, totalSmoothing); }
この場合、テクスチャのRBGチャネルが排出され、計算に関与しないことに注意してください(これについては後で説明します)。 _ スムージングを画面上の現在のテクスチャサイズに手動で設定できます(ただし、メッシュを介したレンダリングの場合と同じように、増加には同じ問題があります)、またはcg関数fwidthを使用します。 「画面上のアイコンの相対的なサイズに合わせます。
SDFメソッドの主な制限はソースシンボルの「バイナリ」(1色)性の必要性であるため、テキストをレンダリングするときに最もよく使用されます。同じSDFテクスチャの処理オプションを調整および変更することにより、ストローク、シャドウ、ブラーなどを作成できます [1]。 SDFを使用するあまり一般的ではない方法は、モノクロのアイコンを描くことです(サイコロの場合のように)が、ほとんどの場合、これはテキストシンボルの特別な場合です。
このアプローチの別の欠点は、鋭いエッジと角度が失われることです。
左側は元のテクスチャです。 右-縮小サイズのSDF
また、記事[3]のテキストを使用した別の例:
利用可能になった問題を解決する
鋭角を保持する同様のアルゴリズムの実装例があります[4] [5]:
アルゴリズムの概要は次のとおりです。
生のSDFは角が丸くなります。ピクセルが遠くなるほど丸くなるからです。 これは、角に垂直を描くことができないために発生します(関数の導関数はこの点には存在しません)-多くのピクセルは、中心が角度である円の半径に沿った距離を考慮します。 これは、滑らかに走る曲線の切れ目をチェックして、シンボルのすべてのコーナーを追跡することで回避できます。 次に、真理値表を使用して、角度の象限を塗りつぶすかどうかを決定します。 つまり、通常、コーナーは異なるチャネルに記録された交差したSDFカードによって塗りつぶされ、最終的なピクセル値は3つのチャネルからのベクトルの中央値によって計算されます。
もちろん、記事全体を1つの段落の90ページに収めることはできません。そのため、完全に参照することをお勧めします[5]。
チャネルに散在するさまざまなフィールドの交差点で同様のことをしようとする以前の試みがありましたが、一部のオプションは、説明した例とは異なり、実際にそこに残っていないため、影を追加したり、ストロークを太くしたり、シンボルの太さを増やしたりすることを意味しません距離フィールド自体)。
少ないチャンネル-より高い精度
Twitterには、フックまたは詐欺師によって似たようなことをしているが、1つのチャネルを持つ友人がいます。
彼のtwitterのさまざまなリンクを見ると、いくつかの実装オプションを見つけることができます。 私が理解しているように、アプローチは標準のSDFとは異なり、実際の境界までの距離を使用せず(コーナー中心の周りの丸めを回避するため)、わずかに再解釈された図を使用し、そのコーナーはさらに続きます。 これにより、角の丸みやいくつかのチャンネルを取り除き、シェーダーを簡素化し、そのような図を表示するために必要な情報の総量を減らすことができます。
このコンパニオンには、GPUのベジェ曲線の距離フィールドをリアルタイムで考慮するシェーダーもありますが、1つの曲線(パラメーターで設定され、その式は「シェーダーに直接」ある)でもデスクトップコンピューティングパワーが必要です。 設定とコードをひねると、ペイントや変調なしで距離フィールド自体を見ることができます。
これらの方法の共通の本質は、キャラクターの境界を定義する曲線を分析することです。
基本に戻る
3番目の方法-シンボルに関するラスタ情報を保存するのではなく、いわば「ストーブから」描画する-曲線のベクトル表現から直接描画することもできます。 問題は、パフォーマンスを損なうことなく曲線情報をグラフィックカードに転送することが比較的難しいことです。 同様の方法を説明する記事がいくつかあります。
ベクターテクスチャを使用したGPUテキストレンダリング [3]。Microsoftにも特許があります。
要するに、一番下の行は次のとおりです。
シンボルをセルに分割し、セルごとに、異なる角度で発射され、このセルと交差する光線と曲線の交差点のマップを作成します。 交差点の数と、これらの交差点が発生した距離を調べます。 曲線データは、ベジエ曲線の座標が設定されたしわくちゃのテクスチャとして保存されます。 1つのベジェ曲線は、この曲線の次数に応じて3または4パラメーターです。 4番目のパラメーターを超えると、通常、曲線は取得されません。 シェーダーは、現在のレンダリングされたセルとこのセルに存在するテクスチャパラメーターに応じて、参照テクスチャから必要なピクセルを読み取り、それらを使用してGPUの曲線の分析形式を復元します。
すべてがバラ色ではありません。
このようなアプローチの欠点は、比較的多数のテクスチャ読み取り操作を使用することです。 モバイルデバイスでタップぼかしブラーを使用したシャドウのリアルタイムレンダリングを何らかの方法で処理しましたが、Dependent Texture Reads(DTS-ロシア語で一般に受け入れられているアナログは見つかりませんでした)はパフォーマンスを大幅に低下させました。 非常に粗雑な場合-DTSは、テクスチャの読み取り座標がフラグメントシェーダーでのみ認識されるようになると、つまりピクセルを直接レンダリングするときに発生します。 通常、フラグメントシェーダーでのテクスチャの高速読み取りは、頂点シェーダーの実行直後にピクセルの特定の補間されたテクスチャ座標が判明するという事実によって決まります。つまり、ビデオカードは事前に目的のテクスチャピクセルを読み取り、ピクセル値を「無料」で提供します。 アルゴリズム、動作、および作業の程度は、主にこれらのシェーダーが実行されるアイロンによって決まります。 OpenGL ES 3.0+では、DTSのパフォーマンスの問題の大部分は解決されているようですが、現時点ではモバイルデバイスの約半分がOpenGL ES 2.0で動作するため、現時点では優れたハードウェアを期待する価値はありません。
( ソース2017年2月6日 )
特許を取得したマイクロソフトのアプローチでは、4つのチャネルを使用してセル内のピクセルの色をエンコードできることは注目に値します。 そして、最初に興味を持ったのは、まさにカラーベクトル画像のレンダリングでした。
さらに生きる方法
上記の方法には、次の欠点があります。
- メッシュ三角形分割法を使用した画像の品質は、三角形の数に大きく依存します。これにより、割り当てられたメモリとビデオカードの負荷が増加します(小さいながらも高密度のグリッドは、クワッドに描かれた同じサイズのテクスチャよりもGPUに負荷がかかります)。
- SDFそのものは、マルチカラー要素をレンダリングする能力を意味するものではありません。 条件の1つは、「バイナリ」ソースイメージです。
- さまざまなベクトルパラメータを定義するピクセルへの「ランダムアクセス」の方法は、GPU側で多くの処理能力を必要とします。特に、追加のテクスチャ読み取りに多くの時間が費やされます。
したがって、私は同じSDFの原理に基づいて、マルチカラーのベクトルグラフィックスをレンダリングするためのわずかに異なる方法を提供したいと考えていました。
SDFのSecond Life
SDFは、テキスト文字の単色レンダリングと同義語になりました。 ただし、ベクター画像をモノクロレイヤーのセットとして表現し、同じSDFテクスチャを使用すると、任意の複雑さと色のベクター画像を描画できます。 つまり、初期イメージを一連のモノクロレイヤーに単純に分割します。
例-レイヤーにカットされたケニーの人気セットのボックスは次のようになります。
これは、SVGファイルの外観です。 レイヤーは互いに重なり合っていないが、互いに「結合」していることに気付くかもしれません。 Inkscapeを使用してこのようなベクター画像を表示すると、これらのレイヤーの同様のドッキングに固有のアーティファクトが明確に表示されます。
アーティファクトの存在は、ベクターグラフィックスの作成方法によって異なりますが、ここでは、このオプションを使用してみましょう。
行動する
レイヤーごとに、独自のSDFテクスチャを作成し、SVGファイルでの順序と同じ順序でレイヤーを重ねてみましょう。
左から右-アンチエイリアシングを有効にしたSVGインポーター、「レイヤード」SDF、初期テクスチャーの増加。 SVG ImporterはInkscapeからSVGを適切に解析できませんでしたが、それはポイントではありません。
両方のオブジェクトが非常に近い場合、違いは次のようになります。
三角測量:
- 三角形の数(-)によって制限されるフィレットの目立つグラデーション
- 同じ理由でジョイントに亀裂が発生する可能性があります(-)
- シャープシャープアングル(+)
- 最小限のオーバードロー(互いに重なり合う半透明のジオメトリ)(+)
- 三角形の総体積-568
- 割り当てられたメモリ-44 Kb
パフSDF:
- 古典的なアプローチの直接的な欠点は、鋭い角の損失です(-)
- 大幅なオーバードロー(-)
- 三角形の総数は26です(テクスチャは四角形には描かれませんが、アルファでテクスチャを囲む自動生成されたメッシュ上にあります。より単純なオプションとして、単純にレイヤーの数に2、つまり10の三角形を掛けることができます)(+)
- ETC4を使用して圧縮された256x128の3チャネルテクスチャ。 レイヤーはチャネルに沿って散らばっています。レイヤーのサイズは128x128です。つまり、別のチャネルのアトラスの左側に3つのレイヤー、アトラスの右側に2つのレイヤーがあります。 割り当てられたメモリ-16 Kb(+)
他のすべての方法と比較した場合のこの方法の主な欠点は、大幅なオーバードローです。 このボックスを描画するには、4つのフルサイズレイヤーを重ねて配置し、さらに5番目のレイヤーに小さな四角形(小さなダッシュ)を配置する必要があります。 最悪の場合、オーバードローはベクトル画像のレイヤー数に正比例します。 デバイスの解像度が高いほど、レンダリングが遅くなります。
ただし、SVGファイルをメッシュに解析するためのほとんどのパッケージとは異なり、事前に準備されたテクスチャはかなり少ないスペースを占有します。 この点で、Scaleformはさらに進化しました。シーンをロードするときに、事前に作成されたファイルでアプリケーションアーカイブを詰まらせることなく、すべてのメッシュをその場で生成しました。 比較のために、初期ボックスサイズは4 Kbのテキストです。つまり、ベクトル画像のスムージングで事前に組み立てられたメッシュは、このベクトル形状を記述する生のテキストの11倍のスペースを占有します。
オプションは何千もあります
私はまた、カラー画像をSDFビューに変換する別の方法を見つけました。 [6]一番下の行は、色にビットプレーンを使用することです。 ビットカードは、色の明るさをビット単位でレイアウトします。
つまり、輝度ビットが順番に取得され、個別のバイナリテクスチャに入れられます。 8つのテクスチャが必要な画像チャンネルは1つだけです。 つまり、透明度のないカラー画像ごとに24のテクスチャです。
さらに進んで、それぞれのバイナリテクスチャを8ビットSDFテクスチャとして提示すると、初期画像を完全に表現するには、24個の8ビットテクスチャが必要であることがわかります(24個のシングルビットテクスチャではなく、ビットカードに分解した直後に取得されます)。
SDF処理されたビットカードから初期カラー画像を復元するプロセスは次のとおりです。
- 各チャネルの各ビットマップについて、現在のSDFピクセル値がチェックされます。
- 0.5未満の場合、元のビットは1であり、0より大きい場合(この場合の0.5は抽象値であり、8ビット整数が127の値と比較されます)
- すべてのビットのすべての値が順番に収集され、各チャネルが個別に復元されます。 たとえば、赤チャネルの現在のピクセルのビット値が01110011である場合、赤チャネルの輝度は115です。
- 同じ方法で現在のピクセルのすべてのチャネルを通過します。
- 3つのチャネルで色の値を復元します。
このアルゴリズムは非常に注意が必要ですが、実際、品質は良くありません。
アーティファクトは、カラーチャンネルをビット単位のコンポーネントにカットすることで、SDFテクスチャの縮小コピーの保存中の精度低下の問題が悪化するという事実により表示されます。 私の意見では、この方法はこの理由のために特に適用されません。 しかし、もう1つの欠点は、1つのオリジナルカラー画像に24個の8ビットSDFテクスチャを保存する必要があることです。
まとめ
すぐに使用できる新しいソリューションを提供することはできませんが、パスマーキングのパレットを使用してSDFエンコーディングを作成するためのアイデアと試みがあります。これにより、さまざまなチャネルの多数の異なるテクスチャを保存し、オーバードローを減らすことができます
記事はすでに非常に大規模であることが判明したため、コンテンツを大幅に削減する必要がありました。 私が言わなかったことから:
- 頭痛や眠れない夜なしでベクトルグラフィックをラスタライズする最も簡単な方法
上記の記事を読んだ友人TheRabbitFlashは 、ベクターグラフィックス全般、特にFlashのラスタライズに関する膨大な情報を共有しました。 "" Adobe Flash ( Adobe Animate), . - Scaleform Mobile SDK 5 Ants Starling, Tiny Thief, Flash + Starling.
- .
- ( Tiny Thief 2561).