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 = ( 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 をカメラからの距離に応じて割る。
z の値を変換する1次式
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 =
ハードに渡す頂点は次の条件をみたす必要がある。
自前で投影変換をした後の頂点は以下を満たす必要がある。
■ カメラを動かす
SAMPLE
カメラを動かす
注視点を軸にカメラを回すには、
カメラのワールドの行列の逆行列を各モデルのワールド行列に掛ければよい。
というより、カメラから見た位置に変換するにはカメラの逆行列をかける。
例えば、回転だけで考えると
カメラが右( 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 とすると
glMultMatrix( M ) をコールすると, M を右から掛けることになる。
■ 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 の視点座標の定義は以下のもの。
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
複数のビューポート
処理
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 )を中心に回転