座標変換(VertexTransform)

DESC プログラム内での関数名は次のように変換先の空間を指定する. WARNING スクリーンスペースでの原点は左下原点となるように指定する。 WARNING glFrustum, glOrtho をコールすると現在の行列に積算される。 必ず glLoadIdentity() でクリアすること。
glMatrixMode( GL_PROJECTION ); glLoadIdentity(); // 必須 glFrustum();
以下の方法では 現在の PROJECTION 行列に glFrustum() で生成した行列をかけることになる。
glMatrixMode( GL_PROJECTION ); glFrustum();
POINT 頂点を xyz を指定すると w 成分は 暗黙 で 1 になる POINT 転置する理由 行列は列行関係なく, 最後の 4 つに T がある  ( Memory 的には 4*3 がよいので, 転置する ) GL の仕様書で扱う行列は "列順" で表記される [ 0][ 4][ 8][12] [ 1][ 5][ 9][13] [ 2][ 6][10][14] [ 3][ 7][11][15] ベクトルは 列ベクトルとして扱う。 [ x ] [ y ] [ z ] [ w ] これは数式の表記の問題であって, 処理には関係ない。 POINT 守ることは次のことだけ 列順, 行順の混乱しないように, 1 次元配列で考えること。 LoadMatrix() をする際は, 最後の4成分 が移動成分である必要がある。
X 軸 m[0] m[1] m[2] Y 軸 m[4] m[5] m[6] Z 軸 m[8] m[9] m[10] 原点 m[12] m[13] m[14]
例えば、Z 方向に -10 移動する行列は次のように指定する。
float m[] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -10, 0, // 最後の4要素が移動成分 }; glLoadMatrixf( m ); drawCube();
ProjectionSpace pos.w == ViewSpace pos.z


行列積の順番

POINT 列順, 行順の表記に関係なく 行列変換の API は最後にコールされた順番に頂点が変換される。 T * R * v
glLoadIdentity(); glTranslatef( 1, 0, 0 ); // 横に1移動する glRotatef( 45, 0, 1, 0 ); // Y 軸に回転してから drawCube();
R * T * v
glLoadIdentity(); glRotatef( 45, 0, 1, 0 ); // Y 軸中心に 45 度を回転をする。 glTranslatef( 1, 0, 0 ); // 横に1移動してから drawCube();
カメラ座標系への変換は最後にするので, コードとしては最初にかく。 C * T * R * v
glLoadIdentity(); convertCameraSpace(); // 以下のコードで先ずはワールド空間に配置する。 glTranslatef( 1, 0, 0 ); glRotatef( 45, 0, 1, 0 ); drawCube();
1次式の別表現( 定義 )が行列とベクトルの積
x2 = a*x + b*y y2 = c*x + d*y
上の式を行列とベクトルの積として定義する
V2 = ( a, b ) V ( c, d )
スケール変換を行列積であらわす
x2 = sx*x y2 = sy*y
V2 = ( sx, 0 ) V ( 0, sy )



行列とベクトルの積

行列とベクトルの積を OpenGL に計算させるには 行列同士の積を利用する。 2個目の行列の第一列をベクトルとみなして、 glMultMatrix() で計算する。
glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( m ); float mv[16] = { v[0], v[1], v[2], 0, }; // ベクトルの代わりに glMultMatrixf( mv ); // 結果をとりだす。 glGetFloatv(GL_MODELVIEW_MATRIX, mv ); // 結果 v[0] = mv[0]; v[1] = mv[1]; v[2] = mv[2];



モデルビュー変換

POINT OpenGL にはカメラをここに置くという命令はない。 モデルのワールドでの位置はカメラが中心の座標系として指定する。 カメラの位置を原点とする右手座標系のため、カメラの正面に置くためには Z値がマイナスである必要がある。 行列の変換がわかりずらいならば、直接z値にマイナスの値を指定すれば良い。
App::onDraw() { // 変換をしないように単位行列をロードしておく。 glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); // 直接マイナスの値を指定する。 float z = -1.0f; glBegin( GL_TRIANGLES ); glVertex3f( 0, 0, z ); glVertex3f( 0, 1, z ); glVertex3f( 1, 1, z ); glEnd(); }
移動行列を使って、カメラの正面に移動させる。
App::onDraw() { glMatrixMode( GL_MODELVIEW ); // 同一のモデルを複数の位置で描画する。 glLoadIdentity(); glTranslatef( 0, 0, -1.0f; ); drawTriangle(); glLoadIdentity(); glTranslatef( 1.0f, 0, -1.0f; ); drawTriangle(); } void drawTriangle() { glBegin( GL_TRIANGLES ); glVertex3f( 0, 0, 0 ); glVertex3f( 0, 1, 0 ); glVertex3f( 1, 1, 0 ); glEnd(); }



回転とスケール

モデルを回転するには glRotate() を使う。 移動と回転の順番によって結果は異なる。 通常はモデルを回転してから、ワールド( カメラ原点 )の位置へ移動する( 置く )。
[ 単位行列 ] // glLoadIdentity() でカレントの行列は単位行列になる。 [ 移動行列 ] // glTranslatef() で移動行列をかける T * R [ 回転行列 ] // glRotatef() で回転行列となる。 R 描画コールをした時点でこの行列によって頂点が変換される。 行列操作のコマンドは現在の行列に対して右から掛けられる。 先に回転 glRotate() をコールした後に glTranslate() をコールすると glRotate 後の座標系で移動することになる。 ( または移動してから回転という操作になる。 )
App::onDraw() { glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( 0, 0, -1 ); // Z=-1 の位置に置く。 glRotatef( 45, 0, 1, 0 ); // Y軸を中心に45度回転してから drawTriangle(); }
// DirectX の本では. --------------------------- 射影空間 ( ProjectionSpace ) ( z/w ) <-> [0:1] に正規化された NearPlane からの距離. ProjectionSpace の w は ViewSpace の z と等しい.


投影変換

モデルを配置したら、カメラの位置を焦点として画面にモデルを投影させる。 この変換を投影変換といい、画面に投影することで遠くのものは小さく、 近くのものは大きく見えるようになる。 カメラでいうと画角の調整に相当する。 投影変換をするには glFrustum() を使う。 6つのパラメータで四角錐( 視錐台 )の形を定義する。 この範囲内のものが画面に描画される。 遠くのものほど, 小さく写すので x, y をカメラからの距離に応じて割る。
X = x/z; Y = y/z;
z の値を変換する1次式
Z = a*z + b
near のときに 0, far の時に 1 となるような係数を求める。
0 = -n * a + b 1 = -f * a + b
// b を消す。 1 = (n - f) * a 1 a = -------- n-f // b = n*a だから n b = -------- n-f
ハードウェアで透視変換をしてもらう。
// 縦横比の調整 X *= W/H; // 4 次元目に 範囲変換前の Z をセットしておく。これで XY が割られる。 w = z; // Z の値は 0:1 の範囲に線形変換する Z =
ハードに渡す頂点は次の条件をみたす必要がある。 自前で投影変換をした後の頂点は以下を満たす必要がある。
[.] X, Y は [ -1 : 1 ] ( 範囲外は 画面に表示されない ) [.] w は xz(z) を割ってほしい値をいれる [.] z は すでに変換すみなので, 先に z をかけておく



カメラを動かす

SAMPLE http://ooo.iiyudana.net/bin/gl/camera_move.exe( カメラを動かす ) 注視点を軸にカメラを回すには、 カメラのワールドの行列の逆行列を各モデルのワールド行列に掛ければよい。 というより、カメラから見た位置に変換するにはカメラの逆行列をかける。 例えば、回転だけで考えると カメラが右( X=1 の方向 )に90度向けた場合、 カメラを基準に見れば、すべてのモデルを左に−90度回したことと等しい。 移動に関しても同じで カメラを 右に10移動することと、すべてのモデルを左に−10移動させることは等しい。 以上のことからカメラのワールドの行列がわかれば、 その逆の変換をすればカメラから見た位置が決まる。 カメラのワールドでの位置と回転(向き)から逆の変換をする。
void tranforrmCameraSpace( float tx, float ty, float tz, float rx, float ry ) { // カメラ位置の逆の変換 glTranslatef( -tx, -ty, -tz ); // カメラの向きの逆の変換 glRotatef( -rx, 1, 0, 0 ); glRotatef( -ry, 0, 1, 0 ); }
各モデルのワールドの位置をカメラから見た位置に変換する。
glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( 0, 0, 10 ); // モデルを 0, 0, 10 に置く tranforrmCameraSpace( 0, 10, 0, 30, 40 ); // 0, 10, 0 に置いたカメラから見た位置に変換する。 drawCube(); glLoadIdentity(); glTranslatef( 1, 1, 1 ); tranforrmCameraSpace( 0, 10, 0, 30, 40 ); drawCube();



行列の結果を取得する

行列の現在の値を取得するには glGet() で取得する。 POINT glTranslate はそのときの座標系の Local 方向へ移動する 今 移動 [Xx][Yx][Zx][Tx] [Xx][Yx][Zx][Tx] [Xx][Yx][Zx][Ty] [Xx][Yx][Zx][Ty] [Xx][Yx][Zx][Tz] [Xx][Yx][Zx][Tz] [ 0][ 0][ 0][ 1] [ 0][ 0][ 0][ 1] // 今の座標系のうち, X 方向へ寄与する量 + 元の X 位置 X = Xx * Tx + Yx * Ty + Zx * Tz + Tx( 元の位置 ) 結果をチェック
glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( 1, 2, 3 ); float m[16]; glGetFloatv( GL_MODELVIEW_MATRIX, m ); print( m ); 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0.000000 2.000000 0.000000 0.000000 1.000000 3.000000 0.000000 0.000000 0.000000 1.000000 // Translation に 1, 2, 3
0.707107 0.707107 0.000000 0.000000 -0.707107 0.707107 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 glLoadIdentity(); glRotatef( 45, 0, 0, 1 );
0.707107 0.707107 0.000000 0.000000 -0.707107 0.707107 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.707107 0.707107 0.000000 1.000000 // 45 度回転して, そのときの X 軸方向{ 0.707107 0.707107, 0 }へ 1 の長さ移動 glLoadIdentity(); glRotatef( 45, 0, 0, 1 ); glTranslatef( 1, 0, 0 );
0.707107 0.707107 0.000000 0.000000 -0.707107 0.707107 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0.000000 1.000000 // 移動した場所で回転 glLoadIdentity(); glTranslatef( 1, 0, 0 ); glRotatef( 45, 0, 0, 1 );
回転と同じく Scale の影響もうける
glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glScalef( 0.5, 0.5f, 0.5f ); glTranslatef( 1, 0, 0 ); 0.500000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.500000 0.000000 0.000000 1.000000
I * MT * MR


glMatrixMode

SYNTAX glMatrixMode DESC 行列操作をする行列スタックを選択する Texture Matrix は Texture Unit ごとにある。 TIP GL_TEXTURE の場合は, 操作するスタックは glActiveTexture() で選択している Unit になる DEFAULT 単位行列 ERROR GL_STACK_UNDERFLOW GL_STACK_OVERFLOW
// UNIT 1 を選択 glActiveTexture( GL_TEXTURE1 ); // UNIT 1 の Texture Matrix を設定 // 以後, Matrix 操作は この Stack が対象 glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glTranslatef( 1, 0, 0 ); // ModelView Stack へ切り替え glMatrixMode( GL_MODELVIEW );



glMultMatrix

SYNTAX glMultMatrixf( const GLfloat *m ) DESC スタック最上位の行列に右からかける カレントの行列を C とすると
C * v
glMultMatrix( M ) をコールすると, M を右から掛けることになる。
C * M * v



glLoadMatrix

SYNTAX void glLoadMatrixf( const GLfloat *m); DESC 指定した行列に置き換える。 m は列優先の行列。
m[0] m[4] m[ 8] m[12] v[0] m[1] m[5] m[ 9] m[13] v[1] m[2] m[6] m[10] m[14] v[2] m[3] m[7] m[11] m[15] v[3]
glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( 1, 0, 0 ); glTranslatef( 1, 0, 0 ); // この時点で x = 2 の行列 float m[16] = {0}; glLoadMatrixf(m); // これをコールした時点で0行列



glRotate

SYNTAX glRotatef( float angle, float x, float y, float z ); DESC x, y, z で指定した軸を中心に r 度回転する行列を右からかける。 角度の単位はラジアンではなく、360度での設定。


glTranslate

DESC x, y, z 軸にそった平行移動をする行列を作成して 右からかける 次の行列を glMultMatrix() することと同じ
1 0 0 x 0 1 0 y 0 0 1 z 0 0 0 1
次の2つは同じ
glTranslatef( 1, 2, 3 ); float m[] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1, } glMultMatrixf( m );



glScale

DESC x, y, z 軸にそって拡大縮小をする行列を作成して 右からかける


視点座標の定義

DESC GL の視点座標の定義は以下のもの。
[.] 視点は原点 { 0, 0, 0 } [.] 右手座標系 [.] 視線の方向( 正面 )は Z 軸のマイナスの方向 [.] +Y が上 [.] +X が右
POINT カメラに写るためにはモデルビュー変換後の Z 座標が - であることが必須になる。


glPushMatrix.glPopMatrix

DESC [ cur Mtx を cp ] したものを最上段に配置 POINT MtxTex は Unit ごとに設定する. ( 選択は glActiveTexture で ) 自前で mtx 計算をする場合は不要な処理のはず. 余計な Error を防ぐために使用を禁止する. DEFAULT Stk ごとに 1 つの MtxUnit をふくむ. EX GL_MAX_( MODELVIEW | PROJECTION | TEXTURE )_STACK_DEPTH


座標変換 pipeline

次の流れで頂点座標がスクリーン上のピクセル位置まで変換される
[ オブジェクト座標 ] | モデリング( ワールド )変換 | [ ワールド座標 ] | 視野変換( カメラから見た位置へ変換 ) | [ 視点座標 ] | 投影変換 ( 投影面に写像する ) | [ クリップ空間 ] | w 除算 | [ NormalizedDeviceCoordinate ] | ビューポート変換( 印画紙に引き伸ばす ) | [ スクリーン座標(画面) ]



クリップ座標

ModelViewProjection 行列の結果の座標 透視除算( w で割る ) ( 透視投影のみ有効 四角い箱にする ) 投影行列が w の値をきめる 平行投影なら w = 1 になる Clip 座標の点は 以下のようにある -w < [x,y,z] < w Frustum の外の Primitive はこの時点で除去 部分的にでる Primitive は, 範囲内だけが Rasterize される


正規化された デバイス座標

-1.0 < [x,y,z] < 1.0 処理 透視除算 w 成分で x, y, z 成分をわる これによって 遠いものが 小さく見えるように変換される x : Window の SubPixel y : Window の SubPixel


window 座標

SAMPLE http://ooo.iiyudana.net/bin/gl/multi_screen.exe( 複数のビューポート ) 処理 viewport 変換 ( WARNING, x, y だけでなく z も写像される ) 変換された z 値は DepthBuffer にはいり、デプステストで使われる。 DESC 左下原点 | | | | ----------------------------- ( [0,0] - [x,y] , Z: depth buf [0:1] ( 正規化された Depth 値 ) 0 : DepthBuffer の前面 1 : DepthBuffer の背面 WARNING: x,y,z は浮動小数点でもつ ラスタライズのため )


glViewport

SYNTAX void glViewport( GLInt x, y, // mapping 先の 原点位置 ( 負数も指定できる ) GLInt w, h, // 幅, 高さ ) DESC 最終的に写像される矩形領域を定義する。 x, y, w, h は Window 左下を原点とするスクリーン座標 DEFAULT 正規化 Device 座標を Window Size 全体に写像する。 Window Size が変更されないなら指定は不要 ^-------------------- | | H|------------ | | | | | | | --------------------> 0,0 W Normalized Device Coordinate -------- | | | | | | -------- メタファは印画紙 VertexTransform 後の 値は Film WARNING 指定した Buffer ( Color | Depth | Stencil )全体をクリアする 一部分ではない
// Viewport 変換もステートのひとつなので // 好きなタイミングで変更できる float W = 1024; float H = 768; // 左半分 に表示 glViewport( 0, 0, W, H ); drawScene(); // 右半分 に表示 glViewport( 0, W/2, W, H ); drawScene();



glFrustum

SYNTAX glFrustum( float l, float r, float b, float t, float n, float f // 視点からクリップ面までの距離 ( 正の値 ) ); DESC 投影変換( 遠くのものを小さく変換する )行列を作成してカレントの行列に掛ける。
// 透視変換行列を指定する。 glMatrixMode( GL_PROJECTION ); WARNING // 乗算するので 初期化 しとく glLoadIdentity(); // 通常は左右対称の投影するため, // left = -right // bottom = -top の関係が成り立つ。 さらに画面の縦横比(アスペクト)に合わせることで、ウィンドウ変換した際に歪まないようにする。 そのため画面サイズ(アスペクト)と画角から r, l, t, b は決まる。 n, f はニアクリップとファークリップの位置を指定する。 n より手前と f より奥はすべてクリップされて表示されない。 画角と n, f のクリップ面の距離から指定すると直感的にわかりやすい。 WARNING n, f の値はデプスバッファの精度に影響を与えるためシーンのサイズに合わせる必要がある。 n を小さくして、 f を大きくすれば良いというものではない。 f/n の値を大きくすればするほど、精度が落ちる。 n を 0 に近づけると、 f/n は無限大に発散する。 log2(f/n) bit の精度が失われる。 float l = -0.75; float r = -l; float b = -l * h/w; float t = -b; glFrustum( l, r, b, t, 1, 1000.0f );



glOrtho

SYNTAX void glOrtho( GLDouble l, GLDouble r, GLDouble b, GLDouble t, GLDouble n, GLDouble f // カメラからの距離 ) DESC 平行な視体積の行列を作成する。 near , far は [ 正負0 ] 可能 視体積 の外の obj はクリップされる メタファーとしては, カメラから見た世界での BOX を定義 WARNING 行列スタック上で累積するので, glLoadIdentity(); 忘れないこと ERROR GL_INVALID_VALUE( l = r, b = t, n = f ) TIP 投影行列が ClipSpace へ変換する. ( Ortho の場合は常に w = 1.0f ) WARNING ortho proj mtx 内に obj が内包されていない カメラ座標系( カメラから見た )の点がボックス領域に入る必要がある
glOrtho( 0, 800, 600, 0, 0, 1 ); ERROR pnt A { -10.0f, 0.0f, 0.0f } OK pnt B { 10.0f, 10.0f, 0.0f }



gluPerspective

SYNTAX gluPerspective( GLDouble fovy, // 縦( Y )方向の角度 ( 単位 degree ) GLDouble aspect, // 縦横比 ( w/h ) GLDouble near, far // 視点からクリップ面までの距離 ( 正の値 ) ) DESC view frustum を定義する。 アスペクトはビューポートを同じ比率にするのが一般的。 glFrustum などと同じく、カレントの行列に乗算される。 上書きする場合は, glLoadIdentity() を先に呼ぶこと。 fovy/2 とあるので、縦方向半分ではなく, 縦全体の角度指定になる。
/ / / -------------------------- 120 度の指定では縦方向はこの範囲が見れる。 \ \ \
この計算と同じになる。
float ang = angle/180.0f * 3.14f; float t = tan(ang/2) * n; float b = -t; float r = t * ratio; float l = -r; glFrustum( l, r, b, t, n, f );



gluLookAt

SYNTAX void gluLookAt( GLdouble eyeX, eyeY, eyeZ, GLdouble centerX, centerY, centerZ, GLdouble upX, GLdouble upY, GLdouble upZ); DESC カメラ座標系への変換行列を作成する。( カメラのワールド行列ではない ) この変換により、 注視点は Z軸のマイナスの値に, 視点は原点は移動する。 現在の行列を積算するかどうかは書いてないが, 行列を取得してみればわかる。 UPベクターは 視線ベクトルと平行になってはいけない。


DepthBuffer

非線形 ( /w ) 指数関数的 次の比率が 目安 [.] 16bit Depth Buffer -> far / near = 50 [.] 24bit Depth Buffer -> far / near = 10000


ProjectionSpace

DESC ProjectionSpace( 射影空間 ) とその変換について MEMO DirectX [-1,-1,0] <-> [1,1,1] の 箱に変換される W 除算前は ↑の範囲にはない OpenGL [-1,-1,-1] <-> [1,1,1] の 箱に変換される W 除算前は ↑の範囲にはない。 POINT 投影変換 した後の pos.w は ViewSpace における奥行値になる これは Projection Matrix の 3 行目から明らか
(vpos.x) (vpos.y) (vpos.z) 0,0-1,0 (vpos.w) = -vpos.z ( カメラの奥行 ) ということは w 除算とは 奥行で xyz を割ること , VertexShader で出力する 位置( pos )情報は w 除算してない -> どうやら Pipeline の中で行われる
TIP ShadowMap で利用する Depth 値の算出方法
// [ ModelSpace ] ---> [ ClipSpace ] float4 pos = mul( mvp, IN.pos ); // POINT // ここでは まだ w 除算しない // 理由は 線形補間では 補間式が異なるから // この違いは shadowmap, priority shadowmap で明らかになる // OUT.depth = pos.zzzw; return OUT;
// Fragment Shader で w 除算すること // POINT // ここで [0:1] の範囲に おさまる // 逆に w 除算をする前は [0:1] におさまってない // float4 col = IN.depth.z / IN.depth/w;
TIP 画面いっぱいに Quad をかく指定
// でも考えてみると Z の指定が これでいい ? // 変換はしない glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); // W = 1 にすること glVertex3f(-1, -1, -0.3f, 1 ); glVertex3f( 1, -1, -0.3f, 1 ); glVertex3f( 1, 1, -0.3f, 1 ); glVertex3f(-1, 1, -0.3f, 1 );



行列操作

POINT [.] 行列操作 の Command は 行列スタックの最上部につまれ右から乗算される
// Code で記述した順番と反対になる [ glTranslate ] [ glRotate ] { glRotate(); // 移動した後に回転 glTranslate(); // 先に移動処理がかかる // 描画 draw(); } // 数式でかくと R * T * v // 言葉で言えば 移動してから, 原点( 0,0,0 )を中心に回転