プログラミング一覧

OpenGLでのストリームアウト その2

GL_NV_transformfeedbackのspecテキスト

こう書いてあった。

なるほど、1つ疑問が解けた。
今までTransformFeedbackAttribsNVを実行していたが、
これは固定機能での描画時に使うもののようだ。
(VSもGSも存在しないときと書いてある)。

つまりGLSLでやるならば、TransformFeedbackVaryingsNVを使って設定することが必要ということ。

こちらで試してみる。
ネット探してのサンプルだけを頼りに試行錯誤していましたが、
本家ドキュメントもしっかり読まないとダメですね。反省。

その他

昨日のGLchar*とint*の差違問題はどうやらNVIDIAからglext.hを持ってくると、int*となっている。
OpenGLのサイトから持ってくるとGLchar*となっている。

とりあえずNVIDIAのほうからヘッダ持ってくるとしよう。


OpenGLでのストリームアウト その1

OpenGLを用いてもDirectX10のストリームアウト相当の機能を使うことが出来ます。この機能のためにまず最初に拡張されたのが GL_NV_transform_feedback というエクステンションです。
最近ではこれがEXTになったようですが、単純にNVから格が上がっただけとはいえないようです。
APIが変更されているからです。

EXT版で試行錯誤しましたが、うまくいかず、NVのほうでまず動作を試してみようと思います。

目標

これらの点から次のことを目標にしています。

  • Cgを用いずにGLSLを使う
  • ひとまずNV拡張のほうを使ってみる
  • ストリームアウトの結果を見るために、ジオメトリシェーダーでは入力頂点を増やして出力する
  • 出力頂点を確認してみて、動作を確認する

OpenGLでのストリームアウト

OpenGLではVertexBufferObject(VBO)に対して結果を書き込むことができます。*1

他にも、tranform_feedback2, 3とかあったりしますが、
これらはOpenGL4系で追加だそうです。

*1 : バッファオブジェクトであれば書き込み先に出来るようです

GL_EXT_transform_feedbackの不審点

glBindBufferOffsetが関数実体を持っていなさそう。
glBindBufferOffsetNVだと関数実体は取れる。
こんな状態なのに、GetStringでエクステンション名は取れてしまう。
不思議です。

GL_NV_transform_feedback 拡張の罠

今のところいくつかの怪しい点で動作せず状態です。

  • glext.h内でglTransformFeedbackAttribsNVが const GLchar* *varyings の引数になっている。
    • ExtensionRegistryでは、ここはGLint*なはずなのに。

現時点での動作不良点

  • 書き込んだプリミティブ数が0のままで取れない…。
    • 書き込み先バッファは何かデータがかかれているようだが。
    • (glMapBufferのREAD_ONLYにて値を確認)

完全に動作するサンプルを検索しても見つからないので苦労しています。
NVIDIAではCgつかったサンプル例となっているし、そのほかもCg使ってしまっているし。
GLSLでやった例ってホント見あたらず。


Scissorによる挙動違い(DX vs GL)

DirectXとOpenGLとで、シザーの設定時のクリア挙動が異なるようです。座標系がそれぞれ異なるってのもあるけど、
そこは上下を逆転して対処したとしても、それだけでは互換動作は不可能。

フレームバッファの縦横と、ビューポートの縦横と、シザー設定の縦横
それぞれ違う設定にしてみて、双方のAPIで実行してみるとわかりやすいかと。

DirectXではビューポートとシザー設定のお互い被っている領域でクリア
GLではシザー設定に従ってクリア。

同じような振る舞いにさせるならシザー領域とビューポートの領域のAND集合を取るようにして、
できあがった矩形でシザーテストするようにしてクリア処理を入れるようにする。

GL面倒だけど、こういうった細かいことをきちんと実装できるだけ
柔軟性はあると思う。


Vertex Texture Fetchによるスキニング

Vertex Texture Fetchを利用して、スキニングメッシュのサンプルを動かしてみました。
ひにけにxnaさんのところでは、XNAを用いての解説があったので、
ここでは、普通のDirectX SDKとC++による組み合わせでこれをやってみたいと思います。

使用するもの

  • SkinnedMeshのサンプルプログラム
  • ShaderModel 3.0に対応したグラフィックボード
    • Radeon X1000シリーズを除く。これはVTFに対応していないため。

ソースコード

各準備

まずは、ボーンのマトリックスを格納するためのテクスチャを準備します。
ここではグローバルにおいて、手抜きしました。
また、毎回書き換えるので、ダイナミックテクスチャにしています。

ボーン用テクスチャの概要

ボーン用のテクスチャは、横方向に使用したいボーンの数、縦方向に4というサイズで作成しました。
というのも、行列は4x4のfloat要素で、今回のテクスチャはRGBA32Fという形式のため、1テクセルに4要素格納できます。
そのため、4テクセル使えば、1つの行列が生成できるということを目的にしています。

横方向(U方向)はボーンのインデックスを示すことにして、
V方向には行列の行を格納することにしました。
このほうが配列っぽく見えるかなと思っています。

マトリックスパレット数調整のための変換部分

またついでに、ConvertToIndexedBlendedMesh を呼び出している部分も
これらの設定のボーン数をみるように修正が必要です。

マトリックスパレットの設定部分の修正

マトリックスパレット設定部分を次のように変更します。

テクスチャにボーンのマトリックスを格納していきます。
注意点としてはテクスチャのY方向へずらすときは、ロックしたときのピッチバイト数でずらさないといけない点です。
それと、シェーダー内ではマトリックスとしては取れず、
float4の4つの組で取れる感じになります。
そのためシェーダーに送る際に転置しておきます。
こうすることで、内積を利用して計算が可能になります。

プロジェクション用行列の設定修正

サンプルのSkinnedMeshのサンプルではmViewProjというシェーダー定数に
実はプロジェクション用の行列しか送っていないです。
ビュー行列はマトリックスパレットに対して適用していました。

この部分を変更します。
mViewProjにg_matProjを設定している部分で、g_matViewとの演算結果を格納するようにしておきます。

シェーダーコード

ほとんどの部分はサンプルのそのままです。
変更点は以下の通り

  • tex2Dlodにて該当するマトリックスパレット相当のテクセルから値を取得
  • 浮動小数用にサンプラを設定
  • VTF使うために、vs_3_0を設定。あわせてピクセルシェーダーも

実行結果

こんな感じになります。
理由はピクセルシェーダーでディフューズ用のテクスチャ等を処理せず、
単純にライトの強度を出しているからです。

しかし、これがVTF利用して動いています。

今は、横方向128でやりましたが、これを256にすることで
ボーンの上限256個までを一度に使うことが出来るようになります。


GLSLスキニング

GLSLでスキニングシェーダー書いてみた

わりとGLSLでスキニングシェーダーをやったという記事を見かけないので
ここで書いてみます。

大変だった点はGLSLでは、Cのようなキャストを受け付けない点。
そのためブレンドインデックスをどうやってint型にするかで悩みました。
実際には、下記のソースコードに示すように int( )で囲ってやる程度で済む話なのですが、
知らないと結構悩みます。

備考

マトリックスを使わずにfloat4 (すなわちvec4)でやっているのは
単に自分のスタイルなだけです。
先輩のシェーダースタイルに影響された可能性が大です。
matrix型のほうが変換がmulで書ける分だけソースコードはすっきりします。
float4の4つという表現だと、シェーダー定数設定をするコードがすっきりします。
またMatrixをfloat4x3型として使っているような場合で効率よくシェーダー定数を使うことが出来ます。

他にはライティングが適当です。
平行光源と若干のアンビエント成分いれたつもりのコードです。

ソースコード(VertexProgram)

1頂点あたり4ボーンのスキニングシェーダーです。
1回のマトリックスパレットは36個です。

36個にした理由は、tiny.xがこの個数で描けるからです。


モデル読み込み

自前で読み込んでみる

DirectXの .xファイルを自前で読み込んで表示させてみました。
以前試行錯誤しながらがんばっていたConvertToIndexedBlendedMeshのニセモノを使って表示させることが出来るようになりました。

自分で変換するため、OpenGLの描画においても使用可能です。
以下にその例を表示してみます。

画像 補足
model_draw.png DirectでもOpenGLでもスキニングメッシュを読み込んで表示できてます。
さすがにシェーダーは別々に作成の必要がありますが…
モデルはキツネもどきさん作です。ありがとうございました。

OpenGLでも .x を使えるようになったため、プログラムが組みやすくなるかなと期待しています。

色々と作ったらこのWebに公開してみたいなと考えています。


C++のインナークラス

“○×(まるぺけ)つくろーどっとコム”さんのところで、
C++でのインナークラス使った例が出ていました。

数時間前の自分の知識では、この記事まずいだろ…と思っていたので
あやうく掲示板に書き込みをしてしまうところでした。

問題点(懸念点)

C++の場合、インナークラスであっても所属する親のクラスに
自由にアクセスは出来ない。
Javaのようなインナークラスの扱いは出来ず、同様に処理したければ所属する親に対して、
friend class MyInner;
などとフレンド宣言が必要である。

この動作は gccにて確認出来た。
しかしながらマイクロソフト製のコンパイラ(VisualStudio,VisualC++)では
フレンド宣言がなくとも自由にアクセスが可能。
よって、結果はコンパイラに依存していることがわかる。

じゃ、どちらがC++規約として正しいのかと人に聞いたことがある。
そのときには、厳密に正しいのはgccで、VisualStudio側のほうがチェックが甘いという結論に達した。

調査してみた

掲示板に書く前にはやっぱり調査いるよね!ということで調べてみた。

結論は、自分の知識が古かったようです。
gcc(g++ 4.1.2)でもインナークラスは所属クラスに対してアクセスを自由に出来ました。

よって、このバージョンのgcc以降であればうまく動作するようです。
そういえば以前エラーを出していたのは gcc3系だった気がします。

このあたりの調査を進めていくと、どうやらいつの年度のC++基準なのかが関係してくるようです。

  • 1998年基準準拠では、インナークラスは親のクラスに対して自由なアクセス権を持たない
  • 2003年基準準拠では、インナークラスは親のクラスに対して自由なアクセス権を持つ

結論

よって、これからの環境であれば、インナークラスは特に問題にならないようです。
ただ移植性を考えると不味いのでしょうが、それは所属親にfriend 宣言加えることで何とかなるでしょう。

とりあえず、MS依存だ!という結論に達さなかったため、
自分のなかの知識を更新しておかないといけないですね。

最後に、この調査の機会を与えてくれた、IKDさんに感謝です。


発想転換、再実装

新・ConvertToIndexedBlendedMesh相当品

アルゴリズムを見直して再作成しました。
前回は4つのマトリックスパレットで分割しようとした際に、
デバッグビルドだとうちの環境で5分くらいかかって、
かつ、描画バッチ数も200弱という悲惨なことになりましたが、
新しい方はそんな状況にはならず動くようになりました。

実行速度はデバッグで計っています。

パレット数 頂点数 描画バッチ数 変換時間
4 4881 24個 4.2秒
13 4578 4個 3.3秒
26 4474 2個 2.9秒

リリースビルドだと、2桁くらいは高速に動いているので
ようやく実用的になったと思います(13個のパレットのRelease時は15msで変換を完了)。
従来方式が複雑かつ長時間かかるという点で使い物にならなかっただけという話ですが。

プログラムコードは400行ほどに(従来コードは700行)。
STLのデータ構造とアルゴリズムを使いまくってます。
そのせいでデバッグでこの速度を出すのが難しかったです。
ただ気を配って書けばSTL使った場合デバッグで重くて使い物にならない!という状況は避けられると感じました。

ポイント

考えをあらためたポイントは、以下の点です

  • 頂点を共有することにがんばらない
  • 変換アルゴリズムはシンプルに

前回の足りなくなったら頂点を複製という点から、頂点は最初から複製前提で考える
という考えに切り替えたのが大きいです。


ConvertToIndexedBlendedMesh調査中

先日の状態よりはうまくいくようになったのですが、
使用できるボーンのマトリックスパレット数が少ないときの挙動でどうにも限界が。

問題点

パレット内でのインデックス割り当てのコンフリクトを解消できない。

頂点において、以前使用したときのマトリックスパレットのインデックスと同じにしなくてはならないのですが、
時たまそのインデックスがコンフリクトして3角形を描けない状況へ陥ってしまいます。

例:
頂点1は、インデックス1,3にボーンID 4,5を割り当てて使用
頂点2は、インデックス0,1にボーンID 6,5を割り当てて使用
このときに、インデックス1にボーンID4,5がコンフリクト

本家ConvertToIndexedBlendedMesh関数

どうがんばってみても自力実装での
ConvertToIndexedBlendedMesh関数がうまくできなかったので、
本家のほうを調べてみました。

使用したプログラムはSDK付属のSkinnedMeshです。データはtiny.xを使用。

パレット数 面数 頂点数
26 6841 4474
13 6841 4578
8 6841 4646
4 6841 4903

ちなみにConvertToIndexedBlendedMesh関数を呼び出す前はこんな状態でした。

面数 頂点数
6841 4432

まとめ

どうやら入力時の頂点の数と、変換後の頂点の数は一致しなくてもよいらしい。
当たり前といえばあたりまえだけど、面の数が変わらないという点で、
インデックスデータの長さも変換前後で変化しないということみたい。

自前変換の手順

自前でやっていた内容をメモしておきます。意外と面倒だったりするので…。

  1. そのマテリアルで描けるデータ列(面データ)の頂点を随時処理
  2. このデータ列全て処理が完了したらループを抜けて、次のマテリアルへ。
  3. ある程度パレットが埋まるまで面単位で処理をしてみる。-(A)
    1. 以前に使用されていた場合の、パレット内インデックスを今回に予約。
    2. (このとき以前未使用なら、自由に再割り当てしてOKということで今スキップ)
    3. 今回のパレットがある程度うまったら、再度(A)ループをやり直す
    4. このとき前回はスキップした部分を埋めていく
    5. 未使用のデータについて現在のパレット内での空きを利用して再割り当てを行っていく

という状態で割り当てていったのですが。
自由に割り当てという部分でもうちょっと調停が必要だったのかもしれません。

まとめ

自分のやっていた方法では、変換前後で頂点の数は変化しません。
一方ConvertToIndexedBlendedMesh関数では、前後で数が変化します。

おそらくコンフリクトしたときに、頂点を再生成し現在の状況にあうように変換しているのではないかと考えられます。
この機構をいれれば、変換はうまくいくのではないかと思います。

もうちょっとがんばらないとダメそうですね。


エディットコンティニューの制限

エディットコンティニュー

VisualStudio(VC)のなかなかな強力な機能であるエディットコンティニューについて。

簡単に説明すると、デバッグ実行しながらプログラムを修正できて、
修正コードを適用させた状態で実行を継続できる機能です。

その制限というか条件のひとつに、こんなことをやるとダメらしい
というのを発見したので、メモしておきます。

ローカルスコープにある変数の属性を変えるようなことは不可能らしい。

今までは単なる変数だったのを、同名で配列にしてみたりとかは不可能。
そういう場合には別の変数名を使えと言うことなんでしょう。