LoginSignup
69
55

More than 5 years have passed since last update.

WebGLにおけるジオメトリインスタンシング(ANGLE_instanced_arrays)を丁寧に説明してみる

Last updated at Posted at 2016-06-10

はじめに

みなさんこんにちは。エマ・デュランダルさんです。
最近、WebGL関連のQiita記事を良く書くようになってきました。

WebGLはただなんとなく使う分には楽ですが、複雑な3Dの世界を作ろうとすると、思いの外パフォーマンスが出ないことがあります。

「Three.jsでやるとこんなに速いのに、どうしてWebGLを直接使うとこんなに遅くなるんだろう」という経験をされた方も多いかもしれません。

こうした世のWebGLライブラリには、たくさんの高速化のノウハウが詰まっています。
それらのうちの幾つかの技法を知ることで、あなたのWebGLアプリケーションも確実に高速化することができます。

本記事では、その技法の一つ、WebGLの拡張機能である「ジオメトリインスタンシング(ANGLE_instanced_arrays)」について、ご紹介したいと思います。

基本的な考え方

さて、「インスタンシング」とは何か、なぜ必要なのか。そうしたことを説明する前に、まず一般的な3Dモデルの描画の概要からおさらいしましょう。

3Dモデルをモニタに表示するためには、当然ながら3Dの処理を行うGPUに対して、CPU側から3Dモデルについての各種データ(頂点位置、色、テクスチャ座標、法線など)を送らなければなりません。

WebGLでは、そのためにVBO(頂点バッファオブジェクト)というものを作成し(大抵はその実体はGPU上のビデオメモリ上に作られます)、そのVBO領域に各種データ(頂点位置、色、テクスチャ座標、法線など)を送り込むわけです。
(話を単純化するため、今回はモデルのテクスチャについては忘れてください)

で、VBOが完成したら、GPUに「そのVBOデータを使って3Dモデルを描いて!」と描画命令を発行します。

ただし、その際、VBOとは別の付随情報も大抵の場合、必要になります。「どこに描画するのか」を頂点シェーダーで計算するのに使う変換行列などです。これらは通常、Uniform変数という扱いでGPUに送信・設定します。

で、例えばですよ。あなたは画面に大量の岩を描画したいとしましょう。シーン上にある大量の岩を、あらかじめ全部3Dソフトなどで作るのは大変な手間です。
そこで、あなたは考えました。ベースとなる岩を1つだけ3Dソフトで作って、あとはそれを少し拡大・縮小したり色を変えたりして変化をつけながら、何万回も描画していけば、それっぽい岩場が表現できるんじゃないか、と。

実に合理的な考えですし、実際昔から3Dゲームなどではそのようなことが行われてきました(実際はさすがに1種類ではなく、数種類のベースパターンを作って組み合わせると思いますが)。

しかし、ここで問題があります。描画パフォーマンスです。

岩場にある岩は、当然置かれている位置が違います。大きさや色も微妙にそれぞれ違います。こうしたバリエーションを実現するために、描画の都度、いちいちVBOの頂点位置やカラーなどを書き換えていたら処理時間がかかりすぎます。

そこで、表示位置を移動させる変換行列やカラーベクトル、といった「岩ごとに異なる情報」を、シェーダーからアクセスできるUniform変数として、各岩の描画命令を出す前に都度設定する。という方法が現実的なアプローチとなるわけです。シェーダーでは、それらのUniform変数にアクセスして、岩にバリエーションをつけていけばいいわけですね。

ただ、これもまだパフォーマンス的には速いとは言えないんです(岩のVBOデータをその都度、直接書き換えるよりははるかにマシですが)。

「Uniform変数の設定」と「描画命令の発行」、これを、描画したい岩が1万個あったら、1万回やる必要があります。これはCPUにとって非常にヘビーな仕事ですし、GPUはCPUがそれらの仕事でひぃひぃ言っている間、「仕事を(与えられていないので)していない」時間が発生してしまいます。

これではとても最大のパフォーマンスを発揮することはできません。

限られたシチュエーションでの別の解決策:「メッシュ情報のマージ」

「メッシュ情報のマージ」という別の解決策もあります。これは、ベースとなる岩のメッシュデータと、それぞれ描画したい岩のバリエーション情報をもとに、1万個描画したいなら、岩1万個分の(それぞれの岩がバリエーション付けされた)岩場のVBOをCPU側でえいやっと作ってしまう、というものです。

 これなら、その巨大なVBOを一回描画するだけで、岩場を表示させることができます。Uniform変数の設定と描画命令を1万回やる必要は無くなるわけです。これで相当速くなります。
 
 ただし、デメリットとして、「岩」ではなく「岩場全体」を描画するわけですから、Uniform変数などで変化をつけられるのは、当然ながら「岩」単位ではなく「岩場全体」の単位になってしまいます。岩場の各「岩」を動かしたり、色を変えたり、といったことはできなくなります。
 つまり、一度作ったそれぞれの「岩」のバリエーションを途中で変更するのは困難(もしやるとすれば、巨大な「岩場」VBOの作り直し)ということになります。
 
 せいぜい、できる移動系の演出としては、岩場全体を動かすとか、カメラを動かす、くらいでしょうか(表示結果的には同じことですが)
 
なので、この「メッシュ情報のマージ」手法は、使えるシチュエーションが結構限定されてしまいます。

では、各「岩」にバリエーションをつけ、しかもそのそれぞれのバリエーションを随時変更・更新することができ、それでいて描画が高速にできる、他に良い方法はないのか!?

あるんです。それが、「ジオメトリインスタンシング(ANGLE_instanced_arrays拡張)」です。

柔軟性と高速性を両立する「ジオメトリインスタンシング」

ジオメトリインスタンシングでは、「各岩のバリエーション情報」を、前もってGPUに送っておきます。
VBOの方は、ベースとなる岩1つ分のデータを準備するだけです。

そして、ジオメトリインスタンシング専用のWebGL関数で設定・描画を行うと、
その描画関数で「描画したい岩の数」を1万個、と指定した場合は、その一回の描画関数の呼び出しだけで、1万個の岩を一気に描画することができます。

しかも、前もってGPUに送っておいた「各岩のバリエーション情報」にもアクセスできるので、各岩にバリエーションもついた状態で描画されるのです! 素晴らしいですね!

図にするとこんな感じ。

インスタンシング図解.jpg

Uniform変数の岩毎の設定。描画関数の岩毎の呼び出し。そうしたコストがなくなりますから、もう超速いです。描画したい岩(これを一般化して言うと「インスタンス」と呼びます)の数が多ければ多いほど、通常のやり方で描画した時とのパフォーマンスの差はどんどん大きくなっていきます。

もちろん、無限大のパフォーマンスを持っているわけではありません。「ジオメトリインスタンシング」はCPUの処理コストやGPUヘのデータ渡しのコストを激減させることで、GPUのスループットを向上させる技法です。

描画するインスタンスの数をあまりに増やすと、当然GPUの頂点パイプライン、頂点シェーダやフラグメントシェーダ、メモリ帯域の負荷が上がりますので、最近のGPUがいかに速いと言えど、どこかで必ず限界はきます。そのことは覚えておいてくださいね。

「インスタンス」って用語よく見るけどぶっちゃけて言うとなんなん?

なお、以降の説明で「インスタンス」という言葉をしょっちゅう使いますが、別にインスタンス描画をする際に、WebGL(OpenGL)のリソースとして「インスタンス」なるモノが生成されるわけではありません。

念のため、「インスタンス」とは何か。もう一度、一言で説明します。
インスタンシング描画では、描画する際のベースとなるメッシュがまずあるわけですが、これを複数描画した際のそれぞれの描画物のことです。あくまで概念的な呼称です。
オブジェクト指向言語でいう、「クラス」とそれをベースに実体が(複数)生成される「インスタンス」の関係と似ているかもしれませんね。
つまり、

「インスタンス描画のベースとなるメッシュ」=オブジェクト指向でいう「クラス」
「インスタンス描画で複数描画した際のそれぞれの描画物」=「インスタンス」

という対比イメージで覚えれば良いかと。

WebGLでの「ジオメトリインスタンシング」の具体的な使い方

さて、いよいよ実践です。
主に3つ方法があります。

観点は、「インスタンス毎のデータをどのような形で用意して」「どのような方法でそれにアクセスするか」です。

以下のアプローチ2と3はWebGL2からでないと使えないので、広い環境で使えるようにという意味では、基本はアプローチ1だと思ってください。

(注:WebGL1.0でも 工夫すれば アプローチ2と3実装できそうです。1 をご覧ください。)

アプローチ1:インスタンス毎データをVBOで用意して、vertexAttribDivisorANGLEを使ってアクセス

インスタンス毎に異なるデータを用意する先として、VBOを使う方法です。現行最も普及しているWebGLであるWebGL1では、ほぼこれになると思います。

まず、インスタンスの元となるベースメッシュのVBOを用意します。まぁこれは頂点位置やカラー、テクスチャ座標ごとに個別のVBOを作ってもいいですし、この記事で説明しているように、単一のインターリーブVBOとして作っても構いません。好きにやってください。

次に、「インスタンス毎に異なるデータ」(以後、「バリエーションデータ」と呼ぶことにします)のためのVBOを作成します。これもデータの種類毎にVBOをそれぞれ作ってもいいですし、単一のインターリーブVBOにしても構いません。(それどころか、ベースメッシュVBOにバリエーションデータを含めることすらできるはずです。まぁ、ややこしくなるので今回はやりませんが…)

さて、VBOを作ったら、そのVBOをバインドして、gl.vertexAttribPointerを呼んでGPUの頂点アトリビュート(レジスタ)とVBOを関連付けるんでしたね。
この時にインスタンシングで必要になってくるのが、vertexAttribDivisorANGLE関数です。

あ、事前に以下のようなコードで、ANGLE_instanced_arrays拡張を有効化してくださいね。

var ext;
ext = gl.getExtension('ANGLE_instanced_arrays');
if(ext === null){
    alert('ANGLE_instanced_arrays拡張がサポートされていません。');
    ...
}

上記だと、vertexAttribDivisorANGLEの呼び出しは実際にはext.vertexAttribDivisorANGLE(..., ...)のようになります。

で、なぜvertexAttribDivisorANGLE関数なるものが必要なんでしょう。ちょっとここで考えてみてください。

「バリエーションデータ」をVBOで用意しました。で、ベースとなるメッシュのデータもVBOです。
で、それぞれ頂点アトリビュートにアタッチして、通常のglDrawArrays/glDrawElementsとかで描画するとどうなるか…。

(以下、説明がかなりくどいですが、vertexAttribDivisorANGLEの役割がイマイチよく分からない、という方が結構いらっしゃるようなので、くどいくらい前提を説明しています)

ベースとなるメッシュのVBOが1万頂点あるとしましょう。つまり、ベースメッシュVBOのデータ個数は1万個です。
一方、「バリエーションデータ」の方は、描画したいインスタンスの数が100個だったら、「バリエーションデータのVBO」(以後、「バリエーションVBO」と呼ぶことにします)のデータ個数は100個用意することになります。(逆に100万個表示したい場合だったら、データ個数は100万個用意する必要があります。当然ですけど)。つまり、ベースメッシュVBOと「バリエーションVBO」のデータ個数は揃わないわけです。

この状態で、例えば表示したいインスタンス個数が100個(つまり「バリエーションVBO」のデータ個数が100個しか用意されていない)の場合に、
gl.drawArrays/gl.drawElementsを実行した場合…。これらの描画関数は、すべての頂点アトリビュートについて、gl.drawArraysなら引数で指定した頂点数、gl.drawElementsなら引数で指定したインデックス数分、各VBOにアクセスしに行きます。

単純にgl.drawArraysで考えてみると、1万頂点ですから各VBOに1万個分データアクセスします。ベースメッシュVBOはもちろんその個数あるので大丈夫です。ですが、「バリエーションVBO」の方は、たかだか100個分しかありません。メモリ範囲をオーバーしてエラーになってしまいます。

逆に、インスタンス100万個の場合はメモリ範囲オーバーにならないですが、でもそれだって正しい描画結果にはなりません。

それもそのはず、よく考えてみてください。「ベースメッシュVBO」は頂点毎/インデックス毎にアクセスされるべきデータで、一方の「バリエーションVBO」はそうではなく、 インスタンス毎にアクセスされるべき データなのです。それと、「ベースメッシュVBO」と一緒くたに頂点毎/インデックス毎にアクセスされたら、正しい結果になるわけがありません。

「ベースメッシュVBO」と「バリエーションVBO」は、アクセス単位が違う! この違いをGPUに伝える手段はないものか!
それが、vertexAttribDivisorANGLEなのです。

vertexAttribDivisorANGLEは引数が2つありまして、一つ目は対象となる頂点アトリビュート(レジスタ)インデックス、2つ目は「除数」と呼ばれるものです。ここではとりあえず、除数の説明は後回しにします。

WebGLのデフォルトでは、描画命令がgl.drawArraysの場合は、VBOのデータ読み取り位置のインクリメントは頂点数単位、描画命令がgl.drawElementsの場合は、インデックス単位(インクリメントというより、インデックス参照だから飛び飛び移動ですね)です。

ところが、vertexAttribDivisorANGLEを呼ぶと、引数で指定した頂点アトリビュートにバインドしたVBOについては、データ読み取り位置のインクリメント単位が頂点/インデックス単位ではなく、インスタンス単位になるのです。

そう、『「ベースメッシュVBO」と「バリエーションVBO」のアクセス単位が違う』問題が、これで解決できるわけですね。

図にするとこんな感じ。

インスタンシング図解2.jpg

あ、ちなみにインスタンシング描画するには、先ほどまで説明としてgl.drawArraysgl.drawElementsを引き合いに出しましたが、実際の描画命令はこれらではなく、それらと対応するインスタンシング版であるext.drawArraysInstancedANGLE/ext.drawElementsInstancedANGLEを使ってください。

インデックスバッファのインデックスによる飛び飛びアクセスになっちゃわない?

ここで疑問に持たれた人がいるかもしれません。「gl.drawElementsはVBO上を指定したインデックスバッファのインデックスが示す位置に飛び飛びアクセスするけど、ext.drawElementsInstancedANGLEはどうなの?「バリエーションVBO」上もインデックスによる飛び飛びアクセスになってしまわない?」と。

結論から言いますと、そうはなりません。「バリエーションVBO」(より厳密に言うと、vertexAttribDivisorANGLEで指定された頂点アトリビュートにバインドされたVBO)には、インデックスバッファのインデックスによる飛び飛びアクセスではなく、処理するインスタンスの順番によるシーケンシャルアクセスになります。安心だね!

ちなみに、一度アクセス単位をインスタンス単位にした頂点アトリビュートを、元の頂点/インデックス単位アクセスに戻したい場合は、もう一度ext.drawArraysInstancedANGLEを読んで、その時に第2引数である除数に0を設定します。

除数について

そうそう、その除数についてですが、これは通常は1を指定するんですが、それ以外の数字(2以上の数字)を指定した時、どうなるかというと、

「新しいインデックス値」=「その時描画しようとしているインスタンスの処理番号(インデックス)」/「除数」 (整数演算なので結果は小数切捨てです)

としたとき、この「新しいインデックス値」で「バリエーションVBO」にデータアクセスします。

つまり、除数を2にすると、「バリエーションデータVBO」上のデータ読み取り位置のインクリメントの頻度が半分に、除数を4にすると、4分の1になります。

具体的な例を出すと例えば、「バリエーションデータVBO」のデータの一つとしてカラー情報があり、

そのデータが「赤、黄色、緑、青、黒」だった場合、

除数が1で5個インスタンスを描画すると、「赤、黄色、緑、青、黒」の色のインスタンスが描画されます。

除数が2で5個インスタンスを描画すると、「赤、赤、黄色、黄色、緑」という色の取り合わせでインスタンスが描画されます。

除数が3で5個インスタンスを描画すると、「赤、赤、赤、黄色、黄色」という色の取り合わせでインスタンスが描画されます。

図にするとこんな感じ。

インスタンシング図解4.jpg

WebGL2でアプローチ1をやる場合

WebGL2では、ジオメトリインスタンシング機能は標準でサポートされていますので、gl.getExtensionメソッドでいちいち拡張オブジェクトを取ってくる必要はありません。

vertexAttribDivisorANGLEdrawArraysInstancedANGLEdrawElementsInstancedANGLEを呼ぶときも、

ext.vertexAttribDivisorANGLE ではなく gl.vertexAttribDivisor の形で呼ぶことができます。最後のANGLEという文字列部分はいりません。

サンプルコード

アプローチ1のサンプルコードを以下に示します。

Approach1 on Runtant

インスタンス毎の変換行列を3つの頂点アトリビュートに分けていますが、考え方はアプローチ3と同様です。
また、各Cubeが動かないのは味気ないので、インスタンス毎の変換行列データを毎フレーム更新しています(動かす必要がないなら、更新しなくても構いません)

スクリーンショット 2016-06-11 1.48.21.jpg

ちょっと宣伝:glTips

本記事のサンプルコードでは、私が作ったglTipsという、WebGLコードで良くある定石コードをまとめたスニペット集を使っています。独自の定数やクラス等は一切作っておらず、本当に素の意味でのWebGLコード(を関数にまとめたもの)になっています。ライセンスはな、なんとPublic Domain!
どんな用途に使ってもいいですし、コードの一部をコピペして使っても文句言いません。好きに使ってください ^o^/

って、まだ公開したばかりで、そんな偉そうに言えるほどコードスニペット貯まってないですけどね^^;
徐々にコードを増やしていきますので、生暖かく見守ってくださいw

アプローチ2:インスタンス毎データをUniform変数配列で用意して、GLSLのgl_InstanceID組み込み変数を使ってアクセス(要WebGL2)

WebGL2からになりますが、頂点シェーダーにおいて組み込み整数変数gl_InstanceIDが使えるようになります。1

ここには、ジオメトリインスタンシングをやっている時に、その時処理しているインスタンス番号が入ってきます。

これと、Uniform変数配列を使えば、gl_InstanceIDでその配列を添字アクセスすることで、インスタンス毎にバリエーションをつけることができます。
基本的な例としては

unform mat4 worldMatrices[100];

みたいな感じで、World行列のUniform行列変数配列を利用することですね。

ただし、このやり方は手軽なんですが、Uniform変数配列で確保できる容量は、実はそれほど大きくありません(GPUのVRAMでなく、シェーダーユニットのローカルメモリ上に確保されるため)。その点は注意ですね。

あともちろん、gl_InstanceIDを配列の添字として使うだけでなく、普通に数値として使って(floatにキャストする必要があります)何か動的に値を計算、それをバリエーションとして使うっていうのでもいいです。

類似アプローチ:Uniform Buffer Objectを使う。

UniformBufferObjectとは何かというと、まぁGL系でよくある~~~~BufferObjectの扱いで、Uniform変数データを用意できるものです。
通常のUniform変数との違いとして、複数のシェーダープログラム間で共有することができる、とか、確保できるサイズが比較的大きい(実装依存)とか、Uniform変数よりもデータの更新が速い、といったことがあります。

もういくつもサンプル作るのめんどい(←ぉぃ)し、基本的にはアプローチ2の仲間だと思うので、簡単な紹介に留めましたが、気になる人は是非トライしてみてください。

サンプルコード

アプローチ2のサンプルコードを以下に示します。
(WebGL2対応のブラウザ(Chrome CanaryまたはFirefox Nightly)で、WebGL2を有効化してみてください)

Approach2 on Runtant

インスタンス毎の変換行列を3つのUniform変数配列に分けていますが、考え方はアプローチ3と同様です。
もちろん、3つのvec4のUniform変数配列ではなく、1つのmat4のUniforom変数配列で、丸ごと変換行列を設定するやり方でやってもいいと思います。

スクリーンショット 2016-06-11 1.50.09.jpg

アプローチ3:インスタンス毎データをテクスチャで用意して、GLSLのgl_InstanceID組み込み変数を使ってアクセス(要WebGL2)

このアプローチもgl_InstanceIDを使うのでWebGL2限定です。1

アプローチ2は、確保できるUniform変数配列の容量の制約により、バリエーションをつけられるインスタンス数に限りがありました。

なら、インスタンス毎のデータを、Uniform変数配列ではなくて、テクスチャに格納して、それをgl_InstanceIDを使ってフェッチすればいいじゃない! というのがこのアプローチです。

ただし、注意点として、よほど「荒い」精度のデータで十分というケースを除いて(つまりほとんどのケースで)、テクスチャフォーマットはFP16(half float浮動小数点)である必要があります。

ただ、テクスチャの画素(テクセル)には、4要素しか詰めることができません。テクスチャの1画素分を1個のインスタンスのデータに相当させるのがわかりやすいので、ではその条件でWorld行列を詰めるにはどうしたらいいでしょうか。

方法は幾つかあると思うのですが、一番シンプルな方法としては、テクスチャを3枚使う、というものです。で、各テクスチャにWorld行列の各行成分を格納します。
あれ? 4枚じゃないの? と思われたと人もいるかと思います。まぁ4枚でもいいんですけど、実質3枚で十分です。

というのは、World行列に与える変化というのは大抵、移動と回転と拡大縮小の3種類だと思います。このうち回転と拡大縮小は3行3列の行列で表すことが可能です。移動に関してのみ

スクリーンショット 2016-06-11 1.18.07.jpg

という行列になり、4列目に移動成分が来ます。しかしこれなら、3枚のテクスチャの第4成分に格納可能ですね。
で、移動・回転・拡大縮小の操作が組み合わさったWorld行列は、4行目成分が必ず「0 0 0 1」になります。
なので、テクスチャとして用意するのはWorld行列の1〜3行目成分に相当する、3枚でOKというわけです。

図にするとこんな感じ。

インスタンシング図解3.jpg

テクスチャアクセスの方法としては、 @YVT さん(Hyper3DというThree.jsベースのハイクオリティレンダラーの開発者でいらっしゃいます)のこの記事を参考にすると良いでしょう(実際にうまくいくかは皆さんで検証してみてください)。

本記事では、@YVTさんの記事の最後に書かれていたように、WebGL2 (ES 3.0相当)なのでシンプルにtexelFetchを使う方向でいきます。

サンプルコード

アプローチ3のサンプルコードを以下に示します。
(Firefox NightlyまたはChrome CanaryでWebGL2を有効化してみてください)

Approach3 on Runtant

スクリーンショット 2016-06-11 1.51.04.jpg

各アプローチの比較表

各アプローチを比較してみるとだいたいこんな感じ

アプローチ1(VBO) アプローチ2(Uniform配列) アプローチ3(テクスチャ)
扱えるインスタンス数 大量 それほど多くない 大量
実装難易度 普通 易しい やや難しい
インスタンス毎データの更新速度 CPU側からの書き換えは遅い。Transform Feedback(WebGL2のみ)を使えば速い。 まぁ速い? CPU側からの書き換えだと遅い。Render to Textureなら速い。
備考 WebGL1から主要ブラウザで使える(正確には拡張機能としての扱い) WebGL2が必要 WebGL2が必要

まぁ、諸々を考えると、別にアプローチ1で良さそうな感じがしますね^^;

「インスタンス毎データの更新速度」は何かというと、インスタンス毎につけるバリエーション情報も、アプリケーションの状況に応じて変更したいことだってあると思うんですが、その際のデータの書き換えがどれくらい速くできるか、という項目です。

インスタンシングやってみても非インスタンシング時とほとんどパフォーマンスが変わらない場合

これは実際私がハマりまして、 @YVT さんの助言により解決したのですが、( @YVTさん、ありがとうございます。 )
ベースメッシュの頂点数(インデックス数)が多い場合、GPUの頂点パイプラインに負荷がかかり、性能が頭打ちになるようです。

多分、

頂点パイプラインに流れ込む頂点データの量 = ベースメッシュの頂点数(インデックス数) × インスタンス数

と思われます。
なお、OpenGLのglDraw*系の命令は、(ドローコールは重いんだよ〜とよく言われる印象と比べると)意外と高速です。

したがって、インスタンシング版と非インスタンシング版を比較した時に、それぞれ

インスタンシング版: ベースメッシュの頂点数(インデックス数) × インスタンス数 が多い
非インスタンシング版: CPU側のドローコール処理に余裕がある。(そして、インスタンシング版と同量の頂点を描画している)

という場合は、上記理由から、インスタンシング版が非インスタンシング版と比べてほどんど性能が上がらない、ということが起こりえます。

インスタンシングの効果をはっきり実感したい方は、ベースメッシュの頂点数(インデックス数)を思い切って減らしてみてください。

まとめ

いかがでしょうか。ジオメトリインスタンシング、その概念から使い方まで、最初は結構とっつきにくいものだと思います。
なので、今回かなり丁寧な説明を、動くサンプル付きでQiita記事にしてみました。

@cx20 さんの記事によると、現在、ANGLE_instanced_array拡張は主要なブラウザ全てでサポートされているようです。実質、どんどん使ってしまって構わないと思います。

ジオメトリインスタンシングをさらに超えていけ…!(非WebGLだけど)

ジオメトリインスタンシングはかなり強力な描画機能ですが、
ネイティブ環境におけるDirectXやOpenGL(デスクトップ向け)の最新バージョンでは、さらに強力な描画機能が用意されるようになってきました。WebGLとは当面関係ありませんが、ちょっと紹介しましょう。

インダイレクト描画

これは何かというと、GPU側に頂点データ(VBO)だけでなく、glDrawElementsやglDrawElementsInstanced(OpenGLでのインスタンス描画命令)で指定するような引数情報までをもGPU上のバッファ(描画命令バッファ)に前もって転送(またはCompute Shaderなどで動的生成)しておき、それらのデータを使って描画を行ってしまおうというものです。

これ、Compute Shaderと組み合わせれば、描画に必要な全情報をGPU自身が計算して用意して描画する、というCPUの手をわずらわせなくても独立して描画をすることができます。いわばGPUが自身でGPUを駆動するとでもいいましょうか。

WebGLでも早くできるようになるといいですね!

glMultiDrawElementsIndirect()

長い名前ですねw これ、すごいですよ。通常のジオメトリインスタンシングは、「同じ形状データ」のインスタンスを少しパラメータを変えて複数描画する、というものでしたが、
このglMultiDrawElementsIndirect()は、「複数の形状の異なる」メッシュデータを、インスタンス&インダイレクト描画してしまうというものです。

もう、やけくそって感じですよね。私も使ったことありませんw
こういう最新機能を駆使すれば、Vulkanを使わずともかなり高速な3DCGアプリケーションが作れるかもしれません!

WebGLでも早く…(ry

最後に

今回はインスタンシングを使わない普通のバージョンとの速度比較までは行いませんでした。
が、それは次の機会で必ずやります。→書きました
というのもね。結構面白いネタをまた考えてるんすよw
その昔、本物のインスタンシングに憧れていた人たちがおってだね…え、何の話か? おっと、これ以上はまだ言えねぇ言えねぇww

というわけで、また次回のエマさんQiita記事でお会いしましょう〜〜〜♪


  1. YVTさんの記事では、WebGL1.0でgl_InstanceID相当のことを実現するTipsが紹介されています。これを使えばアプローチ2や3をWebGL1.0でも実装可能と思われます。 

69
55
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
55