WebGL風とGPUプログラミング。 FrontTalks 2018での講演

Webページで複雑なグラフィックをレンダリングするために、WebGLと略されるWebグラフィックライブラリがあります。 インターフェイスデザイナーのDmitry Vasilievは、レイアウトデザイナーの観点からGPUプログラミングについて、WebGLとは何か、この技術を使用して大規模な気象データを視覚化する問題をどのように解決したかについて話しました。





-私はYandexのエカテリンブルク事務所でインターフェースを開発しています。 私はスポーツグループで始めました。 ホッケー、サッカー、オリンピック、パラリンピック、その他のクールなイベントで世界選手権が開催されたとき、私たちはスポーツ特別プロジェクトを開発していました。 また、新しいソチトラック専用の特別な検索結果の開発にも取り組みました。

















スライドからのリンク


さらに、1.5ヘルメットでは、エラー処理サービスを再起動しました。 その後、Pogodaで作業を開始しました。そこでは、APIの操作性、その開発、このAPIを中心としたインフラストラクチャの作成、訓練された機械学習式のノードバインダーの作成をサポートしました。







それから仕事はもっと面白くなりました。 気象サービスの再設計に参加しました。 デスクトップ、手押し車。











標準の予測を整理した後、誰も持っていない予測をすることにしました。 この予測は、領土全体の降水量の動きの予測でした。







半径2000 km以内の降水量を検出する特別な気象レーダーがあり、その密度と距離を知っています。







このデータを使用して、さらに動きを機械学習することで予測し、マップ上でこのような視覚化を行いました。 前後に移動できます。





スライドからのリンク


私たちは人々のレビューを見ました。 人々はそれが好きだった。 あらゆる種類のミームが現れ始め、モスクワが地獄にto濫しているときにクールな写真がありました。



誰もがこのフォーマットを気に入っていたため、次の予測を風に当てて進めることにしました。







風予報を表示するサービスはすでに存在しています。 これは目立ついくつかの高級なものです。







それらを見て、私たちは同じことをしたい-少なくとも悪くはしないことに気付きました。



そのため、風速に応じて地図上をスムーズに移動する粒子を視覚化し、背後に何らかのループを残して、風の軌跡が見えるようにすることにしました。



私たちはすでに素晴らしく、2Dキャンバスを使用して降水量のあるクールなマップを作成したので、粒子についても同じことをすることにしました。







デザイナーと相談した結果、クールな効果を得るには、画面の約6%を粒子で埋める必要があることに気付きました。



標準的なアプローチを使用してこのような数のパーティクルを描画するために、最小タイミングは5ミリ秒でした。







パーティクルを移動し、パーティクルの尾を描くような美しさをもたらす必要があると思われる場合は、少なくとも25フレーム/秒を生成するために、スムーズなアニメーションを表示するために最低40ミリのタイミングで抜けると想定できます。



問題は、ここではすべての粒子が順番に処理されることです。 しかし、それらを並行して処理するとどうなりますか?



カンファレンスの1つで、「Legend Breakers」によって、中央処理装置とグラフィック処理装置の動作の明確な区別が示されました。 ペイントボールマーカーが取り付けられたマシンを展開しました。そのタスクは、スマイルを1色で描くことでした。 約10秒で、彼はそのような絵を描きました。 ( ビデオへのリンク -概算)















その後、GPUであるカヌーを展開し、モナリザにいくつかの唾を塗りました。 これにより、CPUとGPUの計算速度が異なります。























ブラウザでこのような機会を活用するために、WebGLテクノロジーが発明されました。



これは何ですか この質問で、私はインターネットに登りました。 パーティクルアニメーションと風の言葉をいくつか追加すると、いくつかの記事が見つかりました。





スライドからのリンク: firstsecond


それらの1つは、WebGLで風を作り、GPUでパーティクルの状態を移動および保存する方法について話したChris Wellonsのブログを参照した、MapboxのエンジニアVladimir Agafonkinのデモです。



取ってコピーします。 そのような結果を期待しています。 ここで、粒子はスムーズに動きます。







何がわからないのかわかりません。







コードを理解しようとしています。 改善、再び不満足な結果を得る。 さらに深く登ります-風の代わりに雨が降ります。







さて、私たちは自分でそれをすることにしました。







WebGLを使用するには、フレームワークが存在します。 それらのほとんどすべては、3Dオブジェクトの操作を目的としています。 これらの3D機能は必要ありません。 パーティクルを描画して移動するだけです。 したがって、私たちはすべてを手で行うことにします。







現在、WebGLテクノロジーには2つのバージョンがあります。 クールな2番目のバージョンは、グラフィックアダプターでプログラムが実行されるプログラミング言語の最新のバージョンを備えており、描画だけでなく直接計算を実行できます。 ただし、互換性は不十分です。







さて、Opera Mini以外の優れたサポートを備えた古い実績のあるWebGL 1を使用することにしました。







WebGLは2つのコンポーネントから構成されています。 これは、グラフィックカードで実行されるプログラムの状態を実行するJSです。 また、グラフィックカードで直接実行されるコンポーネントがあります。



JSから始めましょう。 WebGLは、canvas要素に適切なコンテキストです。 さらに、このコンテキストを受け取ると、割り当てられるのは特定のオブジェクトだけではなく、鉄のリソースが割り当てられます。 そして、ブラウザのWebGLで何か美しいものを実行し、Quakeをプレイすることに決めた場合、これらのリソースが失われ、コンテキストが失われ、プログラム全体が壊れる可能性が非常に高くなります。







したがって、WebGLを使用する場合は、コンテキストの喪失をリッスンして、回復できるようにする必要もあります。 したがって、initがそうであることを強調しました。







さらに、JSのすべての作業は、GPU上で実行されるプログラムを収集し、それらにグラフィックカードを送信し、いくつかのパラメーターを設定し、「描画」と言うだけです。







WebGLでは、コンテキスト要素自体を見ると、たくさんの定数を見ることができます。 これらの定数は、メモリ内のアドレスを参照します。 実際、プログラムのプロセスでは一定ではありません。 コンテキストが失われ、再び復元されると、別のアドレスプールが割り当てられる可能性があり、これらの定数は現在のコンテキストでは異なるためです。 したがって、JS側のWebGLのほとんどすべての操作は、ユーティリティを介して実行されます。 誰も住所やその他のゴミを見つけるという日常的な仕事をしたくない。







ビデオカード自体で実行されるものに注目します。これは、Cライクな言語GLSLで記述された2組の命令で構成されるプログラムです。 これらの命令は、頂点シェーダーおよびフラグメントシェーダーと呼ばれます。 プログラムはそのペアから作成されます。







これらのシェーダーの違いは何ですか? 頂点シェーダーは、何かを描画する表面を設定します。 塗りつぶす必要があるプリミティブを設定した後、この範囲に入るフラグメントシェーダーが呼び出されます。











コードでは、このように見えます。 シェーダーには、JSの外部で設定される変数を宣言するためのセクションがあり、そのタイプと名前が決定されます。 この繰り返しに必要なコードを実行するメインセクションと同様に。



ほとんどの場合、頂点シェーダーはgl_Position変数を4次元空間の座標に設定することが期待されています。 これはx、y、zおよびスペースの幅であり、現時点で知る必要はありません。



フラグメントシェーダーは、特定のピクセルの色を設定することを想定しています。



この例では、接続されたテクスチャから選択されたピクセルカラーがあります。







これをJSに転送するには、シェーダーのソースコードを変数でラップするだけです。







さらに、これらの変数はシェーダーに変換されます。 これはWebGLコンテキストです。ソースコードからシェーダーを作成し、並行してプログラムを作成し、いくつかのシェーダーをプログラムにアタッチします。 実行可能なプログラムを取得します。



途中で、シェーダーのコンパイルが成功したこと、プログラムが正常にビルドされたことを確認します。 異なるレンダリング値に対して複数のプログラムが存在する可能性があるため、このプログラムを使用する必要があると言います。



それを設定して、ドローと言います。 ある種の写真が判明します。







より深く登りました。 頂点シェーダーでは、出力ポイントのサイズに関係なく、すべての計算は-1〜1のスペースで実行されます。 たとえば、-1〜1のスペースは、画面全体を1920x1080で占有できます。画面の中央に三角形を描くには、座標0、0をカバーするサーフェスを描く必要があります。







フラグメントシェーダーは0〜1の空間で機能し、色はR、G、B、アルファの4つのコンポーネントで表示されます。



例としてCSSを使用すると、パーセンテージを使用するときに似たような色の表記に遭遇する場合があります。







何かを描くには、どのデータを描く必要があるかを言う必要があります。 特に三角形については、x、y、および十分な3つのコンポーネントで構成される3つの頂点の型付き配列を定義します。



この場合、頂点シェーダーは現在のポイントのペア、座標を取得し、画面上でこの座標を設定するように見えます。 ここでは、変換なしで、画面にポイントを配置します。







フラグメントシェーダーは、JSから転送された定数を追加の計算なしで色で色付けできます。 さらに、フラグメントシェーダーの一部の変数が外部または以前のシェーダーから転送される場合、精度を指定する必要があります。 この場合、中程度の精度で十分であり、ほとんどの場合それで十分です。







JSに渡します。 同じシェーダーを変数に割り当て、これらのシェーダーを作成する関数を宣言します。 つまり、シェーダーが作成され、ソースがシェーダーに注がれ、コンパイルされます。







頂点とフラグメントの2つのシェーダーを作成します。







その後、プログラムを作成し、既にコンパイルされたシェーダーをアップロードします。 シェーダーは互いに変数を交換できるため、プログラムをバインドします。 そしてこの段階で、これらのシェーダーが交換する変数のタイプの対応がチェックされます。



このプログラムを使用すると言います。







次に、視覚化する頂点のリストを作成します。 WebGLには、いくつかの変数に対して興味深い機能があります。 特定のデータ型を変更するには、array_bufferを編集するためのグローバルコンテキストを設定し、このアドレスに何かをアップロードする必要があります。 変数へのデータの明示的な割り当てはありません。 すべては、何らかのコンテキストを含めることで実行されます。



このバッファから読み取るためのルールも設定する必要があります。 6つの要素の配列を指定したことがわかりますが、プログラムは各頂点が2つのコンポーネントで構成されていることを説明する必要があります。そのタイプはフロートで、これは最後の行で行われます。







色を設定するために、プログラムはu_color変数のアドレスを検索し、この変数の値を設定します。 色を設定します。赤255、0.8は緑、0は青、完全に不透明なピクセル-黄色に変わります。 そして、三角形プリミティブを使用してこのプログラムを実行すると言います。WebGLでは、点、線、三角形、複雑な形状の三角形などを描画できます。 そして、3つのピークを作成します。







また、レンダリング対象の配列を最初からカウントするように指定することもできます。





スライドからのリンク


この例を少し複雑にすると、カーソル位置に色の依存関係を追加できます。 同時に、fpsは屋根を通過します。







世界中に粒子を描くには、この世界のあらゆる地点の風速を知る必要があります。



マップを拡大して何らかの方法で移動するには、マップの現在の位置に一致するコンテナーを作成する必要があります。



パーティクル自体を移動するには、GPUを使用して更新できるデータ形式を作成する必要があります。 描画自体を作成し、ループを描画します。







テクスチャを介してすべてのデータを処理します。 22チャンネルを使用して、水平および垂直速度を決定します。ここで、風速ゼロは色範囲の中央に対応します。 約128です。 速度は負と正のどちらにもできるため、範囲の中央を基準にして色を設定します。



そのような写真が判明します。







カードに読み込むには、カットする必要があります。 画像をマップに接続するには、標準のYandex.Mapレイヤーツールを使用します。このツールでは、タイルをカットするアドレスを決定し、このレイヤーをマップに追加します。





スライドからのリンク


不快な緑色が風速でコード化されている写真が得られます。







次に、アニメーション自体を描画する場所を取得する必要があります。この場所は、マップの座標、その動き、およびその他のアクションに対応する必要があります。



デフォルトでは、レイヤーを使用すると想定できますが、カードレイヤーはキャンバスを作成し、そこからキャプチャ可能な2Dコンテキストを即座にキャプチャします。 しかし、すでに異なるタイプのコンテキストを持っているキャンバスから取得して、そこからGLコンテキストを取得しようとすると、結果としてnullを取得します。 アクセスすると、プログラムがクラッシュします。





スライドからのリンク


そのため、ペインの利点を活用しました。これらはレイヤーのコンテナであり、そこにキャンバスを追加しました。そこから、必要なコンテキストを取得しました。







何らかの方法で粒子を画面上に配置して移動できるようにするために、テクスチャ内の粒子の位置の形式が使用されました。



どのように機能しますか? 最適化のために正方形のテクスチャが作成され、ここでその辺のサイズがわかります。







粒子を順番に描画し、粒子のシリアル番号と粒子が保存されているテクスチャのサイズを知ることにより、実際の画面上の位置がエンコードされる特定のピクセルを計算できます。











シェーダー自体では、レンダリングされたインデックス、パーティクルの現在の位置、および辺のサイズのテクスチャを読み取るように見えます。 次に、このパーティクルのx、y座標を決定し、この値を読み取ってデコードします。 これはどのような魔法ですか:rg / 255 + ba?



パーティクルの位置には、20のダブルチャネルを使用します。 カラーチャンネルの値は0〜255で、1080スクリーンでは、粒子を最大255ピクセルに配置できるため、少しでもスクリーンのどの位置にも粒子を配置できません。 したがって、1つのチャネルには、パーティクルが255ピクセルを通過した回数に関する知識が格納され、2番目のチャネルには、通過した正確な値が格納されます。



次に、頂点シェーダーはこれらの値をワークスペース、つまり-1から1に変換し、ディスプレイ上のこのポイントを設定する必要があります。







粒子を見るには、白でペイントしてください。 GLSLには、変数の型を定義して定数に渡すと、たとえば、この定数が4つすべてのコンポーネントに分散されるような糖質があります。







このようなプログラムを描画すると、同一の正方形のセットが表示されます。 それらに美しさを追加してみましょう。







まず、これらの正方形の現在の風速への依存性を追加します。 各パーティクルの現在の速度と対応するテクスチャを単純に読み取ります。 ポイントの絶対速度に対応するベクトルの長さを取得し、この速度を粒子サイズに追加します。







さらに、正方形を描画しないために、フラグメントシェーダーで、半径の外側にあるすべてのピクセルを切り取ります。これらのピクセルは、内接円の半径には含まれません。 つまり、シェーダーはそのようなものに変わります。







中心からレンダリングされたピクセルまでの距離を計算します。 スペースの半分を超える場合、表示されません。







より多様な状況が得られます。



次に、何らかの方法でこれらのものを移動する必要があります。 WebGL 1は何かを計算し、データを直接操作する方法を知らないため、プログラムを特別なコンポーネント、フレームバッファーにレンダリングする機能を利用します。



フレームバッファは、たとえば、更新可能なテクスチャにマッピングできます。 フレームバッファが宣言されていない場合、デフォルトで画面上に描画が実行されます。



出力を1つの位置テクスチャから別のテクスチャに切り替え、それらを1つずつ更新してから、レンダリングに使用できます。











位置自体を更新する手順は次のようになります。現在の位置を読み取り、現在の速度ベクトルに追加して追加し、新しい色でエンコードします。







コードでは、現在の位置の読み取り、デコード、現在の速度の読み取り、速度の通常化、2つのコンポーネントの折り畳み、カラーコーディングのように見えます。







そのような写真が判明します。 パーティクルの状態は常に変化しており、ある種のアニメーションが表示されます。



このようなアニメーションを5〜10分間実行すると、すべてのパーティクルが最終目的地に到着することが明確になります。 それらはすべて漏斗に滑り込みます。 そのような写真を取得します。







これを避けるために、ランダムな場所に粒子順列の要素を導入します。



これは、現在の風速、現在の粒子の位置、およびJSから送信する乱数に依存します。これは、WebGLの最初のバージョンにはランダム化機能と何らかのノイズ機能がないためです。







この例では、パーティクルの予測位置、ランダムな位置を計算し、リセット係数に応じてどちらかを選択します。





スライドからのリンク: firstsecondthirdfourth


最後のスライドの内容を理解するには、これらの記事を読むことができます。 1つ目は、WebGLが提供するもの、それが何で構成され、どのように間違えないかを理解する上で大きな後押しとなります。 Khronosでは、これは標準を開発しているグループであり、すべての機能の説明があります。







タスクの最後のポイントは、粒子の痕跡を描くことです。 これを行うには、位置更新テクスチャと同様に、画面上の現在位置を2つのテクスチャで記録し、画面上の現在位置を表示し、透明度をわずかに上げ、新しいパーティクル位置を重ねて、この画像の透明度を何度も上げます新しい位置をオーバーレイします。







このようなループアニメーションを取得します。











WebGLのレンダリングの完全なサイクルと2Dキャンバスを使用した画面上のいくつかのポイントの表示を比較すると、速度に大きなギャップが見られます。 2Dキャンバスに64,000ポイントを描画するには、平均25ミリ秒かかりますが、WebGLはメインストリームを0.3ミリ秒ブロックします。 これは百倍の違いです。



したがって、WebGLを使用すると、ベースのフローのブロックが少なくなります。これは、カード自体の高い応答性が重要であるカードで作業する場合に特に重要です。



最初は、すべての開発者がブラウザーコンソールを使用していくつかのブレークポイント、コンソールログを設定し、内部で何が起こっているかを確認することに慣れているでしょう。 WebGLはブラックボックスです。







しかし、彼と一緒に作業できるツールがいくつかあります。 たとえば、Firefoxには組み込みの「シェーダー」タブがあり、その場でWebGLキャンバスを画面上で見つけ、そこからプログラムを抽出し、そこからシェーダーを抽出し、その場で値を変更できます。 たとえば、この場で、ドットの色が白から青に変わります。







作業を簡単にする2番目のツールは、Spector.jsブラウザー拡張機能です。 また、WebGLコンテキストからキャンバスをキャプチャし、このキャンバスで実行されたすべての操作、タイミング、および渡された変数を表示できます。







合計1週間の作業で、ゼロからターンキーソリューションを取得しました。 これがどんな種類のテクノロジーであるか、WebGL、それが何で構成されているかを伝え、製品でのその使用の実例を示すことができたと思います。 それだけです



All Articles