読者です 読者をやめる 読者になる 読者になる

モダンな OpenGL で頂点モーフ

OpenGL Shader SEGA

セガゲームス、開発技術部の山田@プログラマです。
普段はゲームのランタイムライブラリ作成やらツール作成やら、深追いデバッグを担当しています。
今回より本ブログにデビューとなりましたのでよろしくお願いします。

さてさて今回のお題に入りましょう。
最近は DirectX12 や Vulkan といった新しい API がありますが、今回は OpenGL の比較的新しい機能の紹介をしたいと思います。

お題: Shader Storage Buffer Object を使う

OpenGL 4.3 で入った Shader Storage Buffer Object (SSBO) というものを使ったことはありますか?

この SSBO は知名度が低いのですが、シェーダーから自由に読み書きができるという点ですごく便利です。
シェーダーにバッファをセットするという点では Uniform Buffer Object (UBO, 定数バッファ) がありますが、
これは読み込みしかできません。書込みが可能な SSBO を使って何ができるでしょうか。

1つの例として今回は SSBO を用いての頂点モーフを考えてみましょう。
近年の描画処理においてはモデルデータを1回描画して終わりではありません。
複数回同じモデルを色々なシェーダーで描画して、最終的な画面結果を作り上げていきます。
そのため、頂点モーフの処理もまた複数回必要になってきます。
しかしながら形状自体は同じであるため、頂点モーフの結果を使いまわせれば処理コストを節約することが出来そうです。
以降はその実装についてのご紹介です。

SSBO のチュートリアル

SSBO は以下のようにして生成します。 OpenGL におけるバッファオブジェクトの生成方法と同じです。

GLuint ssbo;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo );
glBufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, buffer, GL_STATIC_DRAW );

使う際には、この作成した SSBO のオブジェクトを glBindBufferBase で任意のバインディングポイントにセットしていきます。
バインディングポイントの最大数は GPU のドライバに依存するようなので若干注意が必要です。

glBindBufferBase( GL_SHADER_STORAGE_BUFFER, bindingPoint, ssbo );

こうやってセットしたバッファを シェーダーから使うには以下のように記述します。
不定の長さのものを扱えるところも SSBO の扱いやすいポイントです。

#version 430
layout(location=0) in vec4 position;

layout(std430,binding=0) readonly buffer SSBO {
  vec4 data[];
} gSSBO;

void main() {
   vec4 data0 = gSSBO.data[0];
   vec4 data1 = gSSBO.data[2];
}

頂点モーフの実装

SSBO の使い方を分かったところで、頂点モーフの実装を考えてみます。
複数のモーフターゲットがあることが普通で、処理できる個数を限定してしまうことはしたくありません。
そこで各パラメータによって複数のモーフターゲットの成分を合成し、
描画する際にはこの成分と加算することで形状が確定するということにしてしまいます。
この成分の合成する先として SSBO を使います。

f:id:sgtech:20161027134536p:plain:w300

描画のための OpenGL の呼び出しコードは以下のようなものとなります。
各頂点分だけのモーフ成分を計算したいので、 GL_POINTS で頂点分を処理しているところがポイントでしょうか。
ピクセルとしての書き込みを行う必要もないので GL_RASTERIZER_DISCARD を有効化していたりするのもポイントですね。

glUseProgram( ssboMorphShader );
glBindVertexArray( ssboMorphVAO );
glEnable(GL_RASTERIZER_DISCARD);
glDrawArrays(GL_POINTS, 0, vertexCount );

glDisable(GL_RASTERIZER_DISCARD);
glUseProgram( drawMorphShader )
glBindVertexArray( drawVAO );
glDrawElements( GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, nullptr );

実行してみると以下のようになりました。

素直に実装したマルチストリームの頂点シェーダー版と比べて、
数回描画する場合には SSBO を使って処理したほうが高速に処理できることが確認できます。

f:id:sgtech:20161027134340g:plain

One more thing

さて、頂点モーフターゲットの成分を合成している部分について注目してみると、
テクスチャ未使用、ピクセルシェーダーも不要と、単なる計算処理しかしていません。
この部分について OpenGL にも搭載されている ComputeShader (以降 CS) を使ってみることにしましょう。

モーフターゲットについては SSBO で作成しているので、そのまま CS にセットできます。
また、今回も SSBO に結果を書き込むため、描画処理そのものは先ほどのものと共通で使えます。

計算処理から描画までの OpenGL の呼び出しコードは以下のようなものになります。
モーフの計算部分については Vertex Array Object (以降 VAO) のセットが不要です。

glUseProgram( computeMorphShader );
glDispatchCompute( vertexCount/32+1, 1,1);

glUseProgram( drawMorphShader )
glBindVertexArray( drawVAO );
glDrawElements( GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, nullptr );

同じ SSBO を使ったコードで、頂点シェーダー版と ComputeShader 版とで、
実行について速度差が生まれるかどうかについてですが、今回の例においては劇的な差はありませんでした。
若干 頂点シェーダーSSBO出力版のほうが軽いでしょうか。

f:id:sgtech:20161027134503g:plain:w400

最後に

SSBO を用いた頂点モーフの実装例を紹介しました。
勢い余って OpenGL の Compute Shader による実装までやってしまいましたが、いかがでしたでしょうか。
OpenGL 4.x もまだまだ面白いと感じてもらえたら幸いです。




モデル協力:セガ・ハード・ガールズ


セガ・ハード・ガールズ公式 HP:http://shg.sega.jp/ 公式twitter:アソビン教授(セガ・ハード・ガールズ) @SHG_Official TVアニメ『Hi☆sCoool! セハガール』公式HP: http://shg.sega.jp/anime.html

(C)SEGA /セハガガ学園理事会