「 OpenGL 」一覧

Ubuntuで EGL使えるか?


先日NVIDIAのドライバをインストールした Ubuntu ですが、なんとEGLやGLES2についてのライブラリをもっているようでした。(331.20ではダメだった・・・)

これは X 上で EGL & GLES2が使えるんじゃないかと思い試してみることにしました。

続きを読む


OpenGL の ComputeShader


今回はようやくOpenGLでのComputeShaderを扱ってみます。意外とOpenGLでのComputeShaderって話題になっていないようで、ワクワクしています。

手順

VertexShader, FragmentShader などを生成するように、ComputeShader を生成します。ComputeShaderもまたGLSLで記述するので、今までのシェーダーを生成するような感じでプログラムを記述します。ひとつ違うのは出力する箇所でしょうか、今まではピクセルに出力する感じで out 変数に入れていましたが、ComputeShader では imageXXX というサンプラーっぽい型のuniform変数に値を書き込んでいく感じになります。

C++側のコードは以下のような感じになります。通常のシェーダーと同じようにGLSLをコンパイルして ShaderProgram を作成します。

ComputeShaderからの結果を格納するためのテクスチャを用意しておきます。
このときいつも通りにテクスチャを用意した後、下記のAPIにて設定しておきます。これが Shader Image load store 拡張のAPIになります。これによりテクスチャをUAV化というか書き込み先として利用できるようになります。

通常のメインループにおいては、以下のような感じでコンピュートシェーダーを実行し、結果をテクスチャとして利用して貼り付けています。コンピュートシェーダーの実行は glDispatchCompute で行います。

GL_computeshader

今回は gl_GlobalInvocationID を使って処理しているだけですが、各処理ブロックを識別するのに使える変数がもっとたくさんあります。これらの意味を理解して適切に使っていかないと折角の演算力を生かし切ることができません。
CUDAやDirectComputeのほうでもこれらのパラメータに相当するものがあるようなので、またそれらを試してみたときにどういう対応なのか、どの部分を指し示すのかをまとめてみたいと思います。


OpenGLでComputeShaderを使う前に


OpenGLでComputeShader を使う前に実は知っておくべきことが意外とある、ということで今回はその内容についてです。

ComputeShader は演算のためのシェーダーです。その演算内容をどこかに書き出さなければなりません。OpenGLでは、2種類ほどあるようでそれが以下の物に該当するようです。

  • Shader Image load store
  • Shader Storage buffer object (ssbo)

前者はOpenGL 4.2で取り入れられたもので、後者は 4.3 ではいったもののようです。

Shader Image load storeは、フォーマットが決められた出力先へ記録することができるようです。たとえば float4つの値を2次元テクスチャに記録するとか、そんな感じです。DirectXのほうはよくわかっていませんが RWTexture2D とかそういう系列のものに近いんじゃないかと思います。

Shader Storage buffer object (SSBOと略するらしい) は、フォーマットは比較的自由でGLのシェーダーの中からは Read/WriteできるUniform Buffer Object(UBO)のようなものとしてアクセスができる物のようです。DirectX11のほうでいえば、RWStructuredBuffer に相当するといったところでしょうか。

一方DirectXのRWByteAddressBuffer に相当する物はちょっとわかりませんでした。あまり一般的に使われる物ではない、という位置づけでOpenGLでは入っていないのでしょうか。知っている人がいたら教えて下さい。

言い忘れていましたが、これらUAV相当ということでひとまとめにしてもいいのかもしれません。


GLSLのシェーダーリフレクション (2)


OpenGL (GLSL) のユニフォームブロックでのシェーダーリフレクションを試してみました。
ここで使った vertexShader, fragment shaderは以下のようなものです。

これらのシェーダーについて情報を出してみるとこのような感じになりました。
まずはシェーダーユニフォームの一覧.

ユニフォームブロックの中身のメンバには、ロケーションIDは振られていないことが確認できます。ユニフォームのインデックスは振られており、情報はこちら軽油で取得できるようです。
そして、ユニフォームブロックに関して情報を取得してみます。

ユニフォームブロック内でのシェーダー定数のオフセットが取得できました。
ユニフォームブロック内に構造体を配置しても、従来のシェーダー定数の場合と変わらないような名前でオフセットやデータ型を取得できることがわかります。

std140 とかレイアウト指定をしない場合、シェーダーによって(環境によって?)定数の位置が変わりそうです。
そのため、UBOを複数のシェーダーで使い回すのは難しそうです。この場合結局定数毎に書き込む位置を見てバッファ内のデータ更新という手続きをとるので、あまりUBOによる描画効率化に繋がらないように感じます。

ユニフォームブロック内の定数であっても GL_ACTIVE_UNIFORMS にて有効なユニフォーム変数としてカウントされていることに少々驚きました。ただしユニフォームブロック内の変数名からは有効なロケーションが取得できません。ユニフォームブロック内の変数についてはロケーション=-1となっているようです。


GLSLのシェーダーリフレクション


GLSLいじりの過程で、シェーダーリフレクションっぽいことをやってみたのでその調査結果です。

OpenGLでは別に他のライブラリに依存することなく、シェーダーリフレクションの機能を持っています。普段ではuniform変数のロケーション情報を名前から取得しているかと思いますが、その延長上でいろいろな情報をとれるようになっています。

そこで次のようなシェーダープログラム(抜粋)で、どのように情報がとれてくるか試してみました。

結果はこんな感じでした。ドライバの状態によっては別の結果を返してきそうな気配も・・・。AMDやIntelで試してみたいところです。

NVIDIA 331.65 のドライバの結果。

ここからわかることは、次のような感じかと。

  • 配列の場合には [0] とか付加されて返ってくる. (NVIDIAの場合限定かも?)
  • サイズとして配列のサイズ(長さ)が返ってくる
  • 構造体メンバの場合は、構造体変数名が間に挟まっている
  • 構造体だからといって、1つのuniform location に集約されるわけではない

GLSLは構造体使えるから、お?とおもっていたのですが、実際のところは内部のメンバに対して1つずつロケーションが与えられているようです。

次回には、Uniform Blockでのリフレクションのチェックをしてみたいと思います。


最近のGLSLでは色々廃止になってる…


最近のGLSLでは結構廃止になっている部分が多い。ちょっと踏んだところをメモとして残しておきます。

varying, attribute 廃止

これは割と定番ですが、最近のGLSLでは廃止になってます。
代わりに in/out を使用して、シェーダーに対する入力、出力を記述していきます。

その割に、頂点入力としては glGetAttribLocation 使用して、頂点シェーダーの in で書かれた変数を取得するなど、APIとしてはアンバランスに感じます。

textureCUBEの廃止

キューブマップからのテクセルのサンプルで textureCUBEを使用していた部分が最近のものではコンパイルエラーとなるようです。自分の環境では以下のエラーが出力されました。

ではキューブマップからのフェッチはどうするのかというと、以下のような感じで良さそうです。

どうやらtextureCubeだけが廃止扱いではないようです。続く。

texture1D廃止

texture1D, texture1DProj, texture1DLod, texture1DProjLod が廃止になっています。代わりに texture 関数を使えとのことです。

texture2D廃止

texture2D, texture2DProj, texure2DLod, texture2DProjLod が廃止になっています。代わりに texture 関数を使えとのことです。

texture3D廃止

texture3D, texture3DProj, texture3DLod, texture3DProjLod が廃止になっています。代わりに texture 関数を使えとのことです。

shadow系の廃止

shadow1D, shadow2D, shadow1DProj, shadow2DProj, shadow1DLod, shadow2DLod, shadow1DProjLod, shadow2DProjLod が廃止になっています。

デフォルトのグローバルで使えた各変数

これらのものが廃止扱いになっています。あまり使うことはなかったかもしれませんが、たまに使われているケースがあってこのことを知らないと悩みます。
gl_FragColor もまた廃止なので注意が必要です。


Texture Buffer Object (TBO)について


どうやらこれはTextureBuffer-Objectではなく、Texture-BufferObject という意味であるとのことです。そしてこれは、VBO(Vertex buffer object)のバッファデータをテクスチャとして利用するための仕組みであるとのことです。

テクスチャのデータは glTexImage系の関数で準備をしますが、TBOについてはあくまでバッファオブジェクトなので glBufferData などでデータを転送します。そして転送後はこのバッファをテクスチャデータの供給元とすべく、テクスチャのIDとの関連付けを行います。これらの作業をコードで示すと以下のような感じになります。

注意点としては、関連付けるテクスチャを GL_TEXTURE_2Dではなく GL_TEXTURE_BUFFER にバインドする点でしょうか。

TBOの特徴

サンプラ不要でデータにアクセスすることが可能となる点が挙げられます。
通常の2Dテクスチャと違って samplerBuffer を使用することや texelFetch でインデックス指定でアクセスすることも違っています。TBOは1次元配列のように使うという感じでしょうか。

個人的に思うのはこれはジオメトリシェーダーからのストリームアウト(Transform feedback)での結果はVBOに書かれます。これをテクスチャとして使いたいケースでもあるのかなと思いました。
あるいは、バッファオブジェクトを作成して何らかのデータを格納しておきたいケースがあるとか。このデータをテクスチャとして参照することで、リードオンリーではあるものの割と大きなテーブルとして利用可能である、とか。


OpenGLとDirectX11で、あと何が足りてないか


ここ1ヶ月ほどは主に最近のOpenGLについて勉強しているわけですが、DirectX11と比べてまだ何が足りていないかまとめてみます。

やったこと

  • テクスチャ配列 (DX10だけど)
  • ジオメトリシェーダーでストリームアウト&ポイントスプライト (DX10だけど)
  • ハードウェアでのインスタンシング描画
  • テセレーションのシェーダーについて(TCS, TES)
  • シェーダーの実行関数の動的切替(DX11でいうところのDynamic Shader Linkage)
  • インダイレクト描画

まだやれていないこと

  • Texture Buffer (なんかよくわかっていない)
  • コンピュートシェーダー(OpenGL4.3で入った)
  • UAV関連(↑のコンピュートシェーダーの範疇かも)
  • NVIDIA の DirectX と OpenGL の inteop
  • NVIDIA の bindless 色々(GL4.4では標準化されてGL_ARB_bindless_texture).
  • Tiled Resource (DirectX11.2の機能らしい. GLではGL_ARB_sparse_textureだとか)

OpenGL Shader subroutine を試してみた


GLSLシェーダープログラムの関数を切り替える拡張である GL_ARB_shader_subroutine を試してみました。使ってみた感想としては C言語プログラムの関数ポインタを設定して実行先を切り替える、そんなものとずいぶん近い気がします。

シェーダープログラムでは、切り替える関数の宣言、切り替えに使う変数を先に宣言しておきます。
今回はLambertとPhongのライティングを切り替えるように関数を準備してみました。

なんとなくわかるかと思いますが、先に切り替える関数そのもののプロトタイプ宣言みたいなものを行います(1行目)。そして、関数を示す変数を uniform で用意します。main関数の箇所ではこの変数を関数ポインタのようにして、実際の処理関数を呼び出します。
 切り替える関数そのものは、subroutine(プロトタイプ宣言)のような修飾を関数先頭におこなって、プログラムコードを書きます。

続いて C++ 側のコードについて説明します。uniform 変数のようにして関数ポインタもどきを用意しましたが、これは通常のuniform 変数のようにロケーションを取得できません。取得する際には、 glGetSubroutineIndex という関数を用います。また関数がどのシェーダーに実装が入っているのかを識別するためにシェーダーのタイプもまた必要になっています。

そして、このインデックスをシェーダーに対してセットすることで、処理関数が決定されます。

経過時間で関数を切り替えて描画してみたのが以下になります。

DirectXとの比較

DirectXでは、この同様の機能は、シェーダーの動的リンクというものになるのではないかと思います。DirectX11からの機能で、HLSLでインターフェースクラスを作成し、実装はその継承先でという形をとります。つまり Cスタイルで切り替えるか、C++スタイルで切り替えるかの違いでしかないようです。どちらも同じように処理自体は実現可能のように思います。


テクスチャ配列の活用について (OpenGL)


テクスチャ配列の活用について考えてみました。昔はマテリアルに付随する各マップ(ディフューズ、スペキュラ、ノーマル)などを1つにまとめて使うのかなと思っていましたが、また別の使い道もあることがわかってきました。

その活用法というのが、テクスチャアトラスとして使うというものです。テクスチャアトラスとは複数の小さいテクスチャをまとめあげて大きなテクスチャを作成して、各描画ではその一部分を使うという物です。これの狙いはテクスチャの切り替えをなくして、1回の描画コールにまとめ上げることで描画負荷を減らす(特にCPU側)というものです。

さて、やり方ですが、2Dテクスチャ配列に各テクスチャ画像を詰め込んでいきます。このため各テクスチャは同じ解像度である必要はあります。図示するとこんな感じです。これを glTexImage3D関数にて転送しておきます。

texatlas_with_texture_array

そして各テクスチャを利用するポリゴンには、テクスチャ配列内でのインデックス値を頂点情報のどこかに格納しておきます。一番都合がいいのはテクスチャUVの3要素目(w)にいれておくことです。頂点情報をこのように作ると、ピクセルシェーダー(フラグメントシェーダー)で簡単に対象となるテクスチャから画素を参照することができます。

example_texture_array_atlas

これらの作業により、この例では1回の描画コマンド発行で4つのテクスチャを参照して描画することができるようになりました。同じ解像度である必要がある点を除けば、テクスチャ配列を活用して、お手軽なテクスチャアトラスの代わりとさせることができそうな気がします。