Ads by Google

--年--月--日 --:--

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

エピポーラ幾何

2009年03月24日 00:12

うーむ。。やっぱり時間が空いちゃうよね。。
あまり更新できてないのがつらい。

ってことで、エピポーラ幾何についての説明でもぼちぼち書いていきます。
自分の知識の整理にもなるし。
金出先生の因子分解法ぐらいまでいければいいなぁ。

もうちょっと進んで、SIFT や SURF なんていう、割と最近の話題まで取り上げられたら幸いです。

んだが、まずはじめにエピポーラ幾何から説明します。


エピポーラ幾何って言うのは二つのカメラとそのカメラが投影した映像に関する幾何学です。
当然、カメラが撮影している空間は共通の空間になります。
CGみたいな、完全に制御されていて座標まで正確に取れるのなら、ものすっごい簡単な理屈なんだよね。
数式の変形とちょっとした代数を使えばできるから。

ってことで、次回からはちょっとずつ説明していきます。

Exponential Map

2009年02月18日 00:16

前回からの続き。

前回はモーションブレンディング時の回転の補完で、複数のクォータニオンからひとつのクォータニオンを作るのに exp マップというのが必要というところまで説明しました。

そんな exp マップの実装方法ですが、、、
実はそんなに難しくありません。

対数空間上で混ぜ合わせ、その結果を実空間に戻すだけです。

・・・内容がこれだけじゃわかりませんね。
僕もなんでこれで正しく処理できるのか?がいまいち理解し切れませんでした。


まず対数空間とは何でしょうか?

その前に大前提として、クォータニオンは複素数の仲間です。

複素数の有名な公式のひとつにオイラーの公式があります。
っていうか、公式の中でもっとも有名な式だと思います。
 e^iθ = cosθ+ i sinθ
ってやつですね。

これを利用して、クォータニオンの自然対数を決めてみます。

回転を表す単位クォータニオン は
 q = cosθ + v sinθ
ですね。

v は
 ii = jj = kk = -1
 ij = k
 jk = i
 ki = j
 ijk = -1
の関係式を持つ複素ベクトルです。

オイラーの公式を使って、この q の自然対数を取ってみましょう。
 ln(q) = ln ( cosθ + v sinθ )
     = ln ( e^vθ )
     = vθ

上記の式変形から、クォータニオンの自然対数は複素ベクトル部に角度θをかけた物になるということがわかるかと思います。

同様に自然対数からの逆変換は、q の長さが θ になりますので、
 exp(q) = cos |q| + v / |q| sin |q|
と書くことができます。

これで対数空間への変換と逆変換がわかりました。
後は対数空間上で混ぜ合わせればいいだけです。

具体的にやってみましょう。
今 q1 と q2、q3 をそれぞれ、0.3、0.5、0.2 で混ぜ合わせ、新しい qt というクォータニオンを作りたいと思います。

擬似コードは以下のようになります。
 qt = exp( ln(q1) * 0.3 + ln(q2) * 0.5 + ln(q3) * 0.2 )

クォータニオンは積の結合則は成り立ちませんが、和の結合則は成り立ちますので、
 qt = exp( ln(q2) * 0.5 + ln(q1) * 0.3 + ln(q3) * 0.2 )
こんな感じに入れ替えても ok です。
(可換性といいます)


数学理論的になぜこれが正しいのか?はわかってません('A`)
対数空間で加算してから実空間に戻すのと、実空間での積との間には関連がないからです。
(積の結合則が成り立たないため)

ただ、この手法を使えば複数のクォータニオンのブレンディング時に特異点が発生するのは回避できますし、長さが変化しないのも保障されるので、使い勝手はものすごくいいです。
モーションブレンディングに悩んでいる方は使ってみてください。

ちなみに、元になった論文は
http://www.cs.cmu.edu/~spiff/moedit99/expmap.pdf
にあります。

もし数学的にも完全にわかったら教えてくださるとうれしいですw

またまた

2009年02月17日 00:26

一ヶ月ぶりぐらいの日記です('A`)
うーん。。なかなか記事となるネタがないんですよね〜。。

とりあえず、exp マップを紹介します。


膝を思いっきり曲げて歩くモーションと普通に歩くモーション、それと走るモーションの3つを組み合わせて中腰で小走りに走るモーションを作りたい!なんて時があるかもしれません(なかなかないけど)

そんな時、モーションをいい感じにブレンドして処理できれば素敵ですよね。
モーフィングみたいに複数のモーションからひとつのモーションを作り出すような感じで。
そんなときに使うのがこの exp マップです。

なぜこの技術が必要なのか?
その原因がクォータニオンです。
最近のゲームでは姿勢の制御や回転アニメーションはもっぱらクォータニオンで行っています。

クォータニオンを使ってる場合、ただ単にそれぞれの要素を線形補完してしまうと、長さが変化してしまいます。
長さがあるうちは単位化してしまえば何とかなりそうですが、長さが 0 になることもありうるわけです。
そうなると、単位化時に 0 除算エラーを招いてしまいます。
長さを保つ球面線形補完を使えばいいじゃんって思うかもしれませんが、球面線形補完は2つの間を補完するためだけにしか使えません。


そもそもなぜクォータニオンを使うのか?
クォータニオン以外の方法で回転を表す方法にオイラー角というのがあります。
ロール、ピッチ、ヨーの3この角度で制御するやつですね。
クォータニオンなんかより直感的に解り易いですし、データの数も少ないです。
しかし、このオイラー角、実は特異点があるという問題があります。
ロール→ピッチと処理した後ヨーの回転を行うとき、ヨーの回転軸がロールかピッチの軸とかぶってしまった場合、ジンバルロックというヨーの回転が意味を成さない(何も回転しない)現象がおきてしまうからです。
一自由度減ってしまうわけですね。

他にオイラー角から行列を作成するのは積算の回数が多いので、あまり効率的じゃないというのもあります。


さて、そんな問題を一気に解決する exp マップの方法なんですが、、、、
これは折角のネタなので次の回にまわしたい思います。

GL で畳み込み処理

2009年01月15日 00:02

シェーダーを使わずに gl でカーネルによる畳み込み

画像をぼかしたり、エッジの抽出にガウスやラプラシアン、LoG や DoG なんていうカーネルを使い畳み込みをかけたい場合があります。
実は GL にはこういう画像の積分計算用のフィルタ処理関数があります。

[GL で畳み込み処理]の続きを読む

Occlusion Query

2008年12月25日 00:29

リアルタイム 3D において Occlusion Culling は長年の課題でした。
実際に描画されるかどうかは Depth Test や Alpha Test が行われた後にしかわからないわけですから、Depth Buffer を参照する術を持たない(あっても物凄く非効率だった)Shader 技術が普及する以前の GPU では現実的な話ではありませんでした。

もっとも、頂点シェーダーが重い!というのは最近ではあまり考えられなくなりましたが、それでも描画がされないのならモーフィング処理などを行わなくても良くなるので、効果がないわけではありません。
さらに、最近の GPU は unified shader に対応しているのが当たり前になってるので、頂点シェーダーを通さなければそれだけ Pixel Shader に時間を費やせます。
また、オブジェクト描画によるフラグメントの更新が行われないのがわかるのなら、そのオブジェクトが使用する Texture を転送しなくてもいいワケなので、バス幅の節約にもなります。

さて、こんな Occlusion Culling、フラグメントまでいったかどうかの判定ですむわけですから、最近のハードウェアなら簡単に実装できそうな気もします。

実は、OpenGL では 1.5 (2003年7月にリリース)から採用されている機能で、DirectX には 10 から Occlusion Predicate Query という名前で実装されています。
二つとも、Bounding Sphere や Axis Aligned Bounding Box、Oriented Bounding Box などの簡易オブジェクトによる本描画前の遮断問い合わせを実現するための機能です。
従来は難しかったフラグメント単位での問い合わせが出来る点でなかなかに強力です。
OpenGL では GLSL の本格導入前(GLSL の本格導入は 2.0 から)から実装されているのが面白いですね。


そんな Occlusion Culling、GL での実装は簡単です。

1:まずオブジェクト ID を生成する(別になくても OK)
2:glBeginQuerry で遮断問い合わせ開始を宣言
3:実際に簡易的なオブジェクトの描画を行う
4:glEndQuerry で遮断問い合わせ完了を宣言
5:Depth Test をパスしたサンプル数を取得する
6:生成したオブジェクト ID を削除する。

です。


ちょっとここで注意。
windows で OpenGL をプログラムする場合、以下の点に注意しないといけません。

OpenGL を管轄する khronos グループでは OpenGL SDK という名前の SDK は配布していません!
OpenGL 1.1 までなら Windows Platform SDK に付属している OpenGL32.dll で使用できます。
  
OpenGL 1.2 以上 (1.5 や 2.0 など)の機能は Windows 付属のライブラリ(OpenGL32.dll)では扱えません。
使う場合は、OpenGL の HP から glext.h を落としてきてインクルードし、wglGetProcAddress 関数などで取得します。
  
 例:
  glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)wglGetProcAddress( "glGenFramebuffersEXT" );

また、glew というライブラリを使うことで、wglGetProcAddress を使用しなくても、いきなり glGenFramebuffersEXT(...) という感じで意識せずに使うことができます(オススメ)



wglGetProcAddress、glewInit などを使用する場合、必ず一つはコンテキストがないと失敗します。
コンテキストは wglCreateContext 関数で作成できます。



特に 1.2 以降にも対応している最新の OpenGL32.dll を探そうとするとハマるので注意してください。。
そんな gl.h や OpenGL32.dll は存在しません。
1.2 以降の関数には wglGetProcAddress や glewInit を通じてのみ、アクセス可能です。



閑話休題。
では、遮断問い合わせ処理に必要な関数をそれぞれ上げてみましょう。


void glGenQueries( GLsizei n, GLuint* ids )

ids に n 個の問い合わせオブジェクトの識別 ID を返します。




void glBeginQuery( GLenum target, GLuint id )

クエリー用の描画開始を宣言します。
target には GL_SAMPLES_PASSED だけを指定できます。
id には glGenQueries で生成した ID を渡してください。
フラグメント処理が通ったかどうかの判定だけなのですが、レンダーステートは反映されます。
ですので、レンダリング時間を増やすようなモードは無効にしたほうがいいでしょう。
(例えば Stencil 処理など)




void glEndQuery( GLenum target )

クエリー用の描画終了を宣言します。
target には同じく GL_SAMPLES_PASSED だけ指定可能です。




void glGetQueryObjectiv( GLuint id, GLenum pname, GLint* params )
void glGetQueryObjectuiv( GLuint id, GLenum pname, GLint* params )

二つとも、まったく同じ内容の関数です。
指定したクエリー id のフラグメント処理が行われたかどうかを問い合わせます。
id には glGenQueries で生成し、glBeginQuery で描画が行われた ID を渡してください。
pname には GL_QUERRY_RESULT、GL_QUERY_RESULT_AVAILABLE が指定できます。
GL_QUERRY_RESULT の場合は、params にはパスしたフラグメントかサンプル(MRT が有効な場合)数が帰ります。
これで params が 0 だった場合は、オブジェクトが全て遮断されていることになります。
GL_QUERY_RESULT_AVAILABLE を指定した場合、params には GL_TRUE か GL_FALSE が入ります。
GL_TRUE の場合は、渡した id での問い合わせが可能なことを意味し、GL_FALSE の場合は不可能なことを意味します。




void glDeleteQueries( GLsizei n, const GLuint* ids )

指定した n 個の ids を削除します。
ids には glGenQueries で生成した ID を渡してください。




以上の関数を使うことで occlusion culling が出来ます。
今回は GL の説明しかしませんでしたが、DirectX の場合も同じような機能ですので、そちらを参考にして下さい。



最近の記事