DirectXとOpenGLのカリングの設定方法の違い

投稿日: 更新日:


はじめに

こんにちはー!
今日は、3D描写するときのテストの話をします。

テストと聞いて何を思い浮かびますでしょうか。
試験とか、勉強とかそんなことをきっと思い浮かべるでしょう。

作成した3Dポリゴンの頂点を描写するときも、
本当に描写していいのか、無駄ではないのか試験をしています。

いくつかテスト方法を紹介しましょう。

背面カリング(裏面カリング)
モデルは多くの3角ポリゴンから構成されています。
モデルが回転すれば、裏を向いているポリゴンが出てきます。
そんな裏を向いている面を描写しないようにテストします。

クリッピング
視錐台(しすいだい)の中に、ポリゴンが収まっているかどうかテストします。
例えば、カメラより手前側にポリゴンが来ていないかといったチェックです。

バウンディングボリュームなど
見えている部分をより特定していく方法です。
衝突判定などにも、高速化のために使用するよ!

今回は、この中でも裏面カリングの設定方法について解説します。


カリングの仕組み

3Dオブジェクトは、細かな三角形ポリゴンによって構成されています。
それは三角形が最も小さな単位だからです。

では、この三角形の裏と表をどのように決めればいいのでしょうか。
まず前提条件として、ディスプレイに三角形を表示することを考えます。
三角形を構成するためには、3点の座標が必要です。
この3点の座標を囲めば、ポリゴンを作ることができるのです。

これに対して、裏と表を決める方法は、
三角形を結ぶ順番が、時計回りか、反時計回りかで決めればいいのです。
そう、決めの問題なのです!

例えば、表向きを時計回りと考えてみましょう。
3角形の頂点を裏返した場合は、
反時計回りの順序になるので裏と表が分かるわけなのです。


カリングの計算方法

3つの頂点が、時計回りか反時計回りかは簡単に計算できます。

下記の3つの頂点があり、a, b, c の3点を順番に通る三角形を考えます。
なお、2次元なので、 x と y 以外は 0 としています。

そして、
a, bへ向かうベクトルmを計算します。
a, cへ向かうベクトルnを計算します。

あとは、クロス積を計算しましょう。
計算にはmnの各ノルムと角度θを使用しますが、
ベクトルのまま計算することもできます。
なお、a, b, c の3次元目が 0 のため、下記のように簡略化されます。

クロス積の結果は、mnと直行するベクトルです。
従って3次元目の成分 、直行成分となっており、正負で方向が分かります。

ね!かんたん!


カリングの設定方法

各種エンジンでの設定方法をまとめてみます。
mk様の算譜記録帳にもまとまっております。

DirectX 9

次のように、裏面(描写しない面)が時計回りなのか、反時計回りなのかを設定します。
つまり、時計回りを表とみなす場合は、D3DCULL_CCWを設定する必要があります。

参考:D3DCULL

// 背面のカリングはしません。
SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

// 三角形の頂点が時計回りならば裏向きと見なし描画しません。
SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);

// 三角形の頂点が反時計回りならば裏向きと見なし描画しません。(デフォルト)
SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

DirectX 11

FrontCounterClockwise で表面の定義をして、
CullMode で描写しない面の設定が可能です。

参考:D3D11_RASTERIZER_DESC

// 三角形の頂点が反時計回りならば三角形は前向き
FrontCounterClockwise = TRUE;

// 三角形の頂点が時計回りならば三角形は前向き(デフォルト)
FrontCounterClockwise = FALSE;

// 常にすべての三角形を描画します。
CullMode = D3D11_CULL_NONE;

// 前向きの三角形を描画しません。
CullMode = D3D11_CULL_FRONT;

// 後ろ向きの三角形を描画しません。(デフォルト)
CullMode = D3D11_CULL_BACK;

OpenGL

glFrontFace で表面の定義をして、
glCullFace で描写しない面の設定が可能です。

参考:glFrontFace, glCullFace

// 三角形の頂点が時計回りならば三角形は前向き
glFrontFace(GL_CW);

// 三角形の頂点が反時計回りならば三角形は前向き(デフォルト)
glFrontFace(GL_CCW);

// 常にすべての三角形を描画します。
glDisable(GL_CULL_FACE);

// 前向きの三角形を描画しません。
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);

// 後ろ向きの三角形を描画しません。(デフォルト)
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

おわりに

今回は、描写テストでも基本的なカリングの解説をしました。

カリングは計算も簡単で、
お手軽に描写負荷を下げることができます。

レンダラーを作る方や、
頂点とインデックスで構成された
モデルデータのローダーを作成する方などの参考になると幸いです。

では、またね!


関連記事

3DCGの座標系の紹介
DirectXとOpenGLの回転行列、回転軸、回転方向
DirectXとOpenGLのビュートランスフォーム行列の違い
DirectXとOpenGLの射影トランスフォーム行列の違い
DirectXとOpenGLのビューポート行列の違い
DirectXとOpenGLのベクトル/行列演算の違い

DirectXとOpenGLのベクトル/行列演算の違い

投稿日: 更新日:


はじめに

こんにちー。
なたでです。

今日も引き続き勉強会です。

これまで、座標系とその変換を学んできました。
今回、実際にその計算を行うため、
具体的にはベクトルや行列演算について
どのようにソースコード上で書けばいいか学んでいきましょう。

ということで・・・
DirectX、OpenGL、各シェーディング言語の型や計算の特徴をまとめるぞー!(おー!)


言語ごとのベクトルと行列の演算

主流なエンジンでのベクトルと行列の型の例の紹介と、演算例を確認しましょう。
.

DirectX

D3DXVECTOR4
x, y, z, w成分を持つ横ベクトル
D3DXMATRIX
4行4列の行列
D3DXMatrixMultiply
行列同士の掛け算を行う

// ベクトル(横ベクトル)の宣言
D3DXVECTOR4 v4 = D3DXVECTOR4( x, y, z, w);
D3DXVECTOR4 v3 = D3DXVECTOR3( x, y, z);
// 行列の宣言
D3DXMATRIX M = D3DXMATRIX(
	_11, _12, _13, _14,   // row 1
	_21, _22, _23, _24,   // row 2
	_31, _32, _33, _34,   // row 3
	_41, _42, _43, _44 ); // row 4
// 行列同士の掛け算 A * B = C
D3DXMatrixMultiply(&C, &A, &B);
// ベクトルのドット積 a・b = c
c = D3DXVec3Dot(&a, &b);
// ベクトルのクロス積 a×b = c
D3DXVec3Cross(&c, &a, &b);
// 3次元ベクトルと4x4行列との積 aM = b
D3DXVec3TransformCoord(&b, &a, &M)

.

HLSL(High Level Shading Language)

DirectX用のシェーディング言語です。

float4
x, y, z, w / r, g, b, a 成分を持つ横ベクトル
float4x4
4行4列の行列
mul

// ベクトル(横ベクトル)の宣言
float4 v4 = float4( x, y, z, w);
float3 v3 = float3( x, y, z);
// 行列の宣言
float4x4 M = float4(
	_11, _12, _13, _14,   // row 1
	_21, _22, _23, _24,   // row 2
	_31, _32, _33, _34,   // row 3
	_41, _42, _43, _44 ); // row 4
float4x4 M = float4(
	_m00, _m01, _m02, _m03,   // row 1
	_m10, _m11, _m12, _m13,   // row 2
	_m20, _m21, _m22, _m23,   // row 3
	_m30, _m31, _m32, _m33 ); // row 4
	
// 行列同士の掛け算 A * B = C
C = A * B;
// ベクトルのドット積 a・b = c
c = dot(a, b)
// ベクトルのクロス積 a×b = c
c = cross(a, b)
// n次元ベクトルとnxX行列との積 aM = b
b = mul(a, M);

補足として成分ごとの演算 (DirectX HLSL)によると、
行列のコンストラクターのパッキング順は、常に行優先になるとのことです。

.

OpenGL

GLdouble v[4];
4つの値を持つ縦ベクトル(型はGLfloat / GLdouble の2種類)
GLdouble M[16];
4行4列の行列(型はGLfloat / GLdouble の2種類)
glMatrixMode + glMultMatrix
行列同士の掛け算を行う

// ベクトル(縦ベクトル)の宣言
GLfloat v4[4] = { x, y, z, w };
GLfloat v3[3] = { x, y, z };
// 行列の宣言
GLfloat M[16] = {
	m00, m10, m20, m30,	// column 1
	m01, m11, m21, m31,	// column 2
	m02, m12, m22, m32,	// column 3
	m03, m13, m23, m33	// column 4
};

// OpenGL単体では計算には向いていない
// OpenGLでは、あらかじめ設定した行列に対して操作を行うことができる

// 行列同士の掛け算 A * B = C
glMatrixMode(GL_MODELVIEW); // とりあえずモデルビュー変換用の行列で計算する
glLoadMatrixf(A);	// A をロードする
glMultMatrixf(B);	// A * B
glGetFloatv(GL_MODELVIEW, C)	// 計算結果を取り出す

注意点として、縦ベクトルのため行列との掛け算は Mv の順序で計算されます。
作成した行列を最終的にベクトルvに対して掛け算をするため、
行列を準備する際は、逆の順番で行列同士の掛け算が必要となります。
.

GLSL(OpenGL Shading Language)

OpenGL用のシェーディング言語です。

vec4
x, y, z, w / r, g, b, a / s, t, p, q 成分を持つ縦ベクトル
mat4
4行4列の行列

// ベクトル(縦ベクトル)の宣言
vec4 v4 = vec4( x, y, z, w);
vec3 v3 = vec3( x, y, z);
// 行列の宣言
mat4 M = mat4(
	m00, m10, m20, m30,	// column 1
	m01, m11, m21, m31,	// column 2
	m02, m12, m22, m32,	// column 3
	m03, m13, m23, m33	// column 4
);

// 行列同士の掛け算 A * B = C
C = A * B;
// ベクトルのドット積 a・b = c
c = dot(a, b)
// ベクトルのクロス積 a×b = c
c = cross(a, b)
// nxX行列とn次元ベクトルとの積 Ma = b
b = M * a;

OpenGLのヘルパー関数(glMatrixMode、glMultMatrix)と違って、
固定化された行列に対して操作していく必要はありません。
行列を掛け算していくときは、次のように1行に1つの掛け算を順番に書くことができます。

// 最後の計算が、aM = b の場合(ベクトルが縦行列なので実際はできない)
B = A * B;
C = B * C;
X = v * C;

// 最後の計算が、Ma = b の場合(ベクトルが縦なのでこの書き方できる。)
B = B * A;
C = C * B;
X = C * v;

.

WebGL

標準では用意されていないため、
次のようなJavaScriptライブラリを使用して管理します。(いろいろある)
glMatrix
vec3など、GLSLと似た感覚の型名をもてる(列管理)
Sylvester
古くからある行列用の一般的なライブラリ。WebGLでも拡張jsを追加すれば利用可能(行管理)

多くのライブラリで共通して言えることは、
OpenGLと同様に、行列のデータを列でもつことです。

[0,  1,  2,  3, // 1列目
 4,  5,  6,  7, // 2列目
 8,  9, 10, 11, // 3列目
12, 13, 14, 15] // 4列目

この理由は、あらかじめ列で持っていくことで、
変換行列を uniformMatrix4fv などでGPU内のシェーダーに
アップロードする際に余計な変換をする必要がないためです。


縦ベクトルと横ベクトルの違い

これまで、DirectXではベクトルは横ベクトルとなって
行列と掛け算するときは、vMとなる。
そして、OpenGLではベクトルは縦ベクトルとなり
行列と掛け算するときは、Mvとなることが分かりました。

しかも、行列を初期化する際の順序も
行で初期化するのか、列で初期化するのか、混乱してきます。

一度ここで、整理したいと思います。

次のような3×3行列を考えましょう。

DirectXの横ベクトルで演算した場合、次のようになります。

OpenGLの縦ベクトルで演算した場合、次のようになります。

そして、ソースコード上、行列を初期化する場合は次のようになることです。

ここまでを、まとめると、非常に複雑で混乱しそうです。
しかし、よくみると気が付くことがあります。

初期化時の配列の位置と、実際に掛け算したときの結果を色付けしました。

なんと、DirectXとOpenGLとで一見、違いがないではありませんか!
これは初期化の順序が異なりますが、掛け算する順序も変わるため
最終的には、コード上の見た目の順序でいえば、掛け算の結果は一致するわけです。

もちろん、行列同士を掛け算する際は、DirecXとOpenGLとで掛ける順序を気にする必要はありますが、
上記のことをしっていると少し混乱がおさまるかとおもいます。


おわりに

今回は、実際の行列/ベクトル演算の書き方を確認しました。

DirectXとOpenGLとで、掛け算をする順序がことなること、
行列の初期化方法が異なることが分かりました。
ただ、掛け算の順序と行列初期化方法の違いにより、
見た目上の掛け算は、一致している部分があることも分かりました。

以上、お疲れ様です。

DirectXとOpenGLのビューポート行列の違い

投稿日: 更新日:


はじめに

こんにちは!
3DCGの再勉強中のなたでです!

今まで、次の変換行列の違いをみてきました。

・DirectXとOpenGLのビュートランスフォーム行列の違い
・DirectXとOpenGLの射影トランスフォーム行列の違い

本日は変換の最後のビューポート行列の違いを調べてみましょう。
といっても、ここまでくると右手座標系・左手座標系という話はなくなっているので、
DirectX と OpenGL とで、きっと同じだと思いますが……。

おさらいですが、ビューポート変換は
正規化デバイス座標系から、スクリーン座標系への変換となります。

関連記事です。もし読んでないなら下から上に読んでいくといいよ!
DirectXとOpenGLの射影トランスフォーム行列の違い
DirectXとOpenGLのビュートランスフォーム行列の違い
3DCGの座標系の紹介
DirectXとOpenGLの回転行列、回転軸、回転方向


数式の比較

.

DirectX

D3DVIEWPORT9構造体でビューポートの定義をして、
SetViewportで設定できるようです。

行列というよりは設定値ですね。
ちなみにデフォルト値は次のようです。

D3DVIEWPORT9 vp;
vp.X = 0;
vp.Y = 0;
vp.Width = RenderTarget.Width;
vp.Height = RenderTarget.Height;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;

vp.X, vp.Y が描写する左上の座標
vp.Width, vp.Height が描写幅
vp.MinZ, vp.MaxZ が深度値の変換です

なお、実際の行列自体は、ビューポートとクリッピング (Direct3D 9)に記載されています。

.

OpenGL

OpenGLでも行列というより関数になっています。
glViewportglDepthRangeで直接設定する形です。

void glViewport( GLint x, GLint y, GLsizei width, GLsizei height);
これの注意点として、ウィンドウで設定している場合は、左下が原点になります。
Windowsの原点は右上で、下に行けばy+にいくのに対して、
glViewportの原点は左下で、上に行けばy+になるわけです。

void glDepthRange( GLdouble nearVal, GLdouble farVal);
描写先の深度値変換の設定です。
0 – 1が初期値です。
※参考 [DEV][CG]DirectXのPerspectiveとOpenGL Frustumの違い

なお、行列は次のようになります。

※参考 ビューポート変換行列 – code snippets

行列は何を表しているのでしょうか

これまでのおさらいをしましょう。
カメラ座標系から射影トランスフォームをしたものがクリッピング座標系になり、
クリッピング座標系から x, y, zw で割った遠近除算したものが
正規化デバイス座標系です。この正規化デバイス座標系から、スクリーン座標系へ変換します。

この正規化デバイス座標系は、次の値の範囲に収まります。
-1 <= x <= 1
-1 <= y <= 1
-1 <= z <= 1 (IF OpenGL)
0 <= z <= 1 (IF DirectX)

これらをふまえて、まず DirectX の行列

この式が実際にどのようなことをしているか考えてみましょう。
実際に計算すると次のようになります。

行列の計算は次のようになるので、

ベクトルに行列を掛け算して、式変形すると次のようになります。

同じく、OpenGLの行列も計算してみましょう。

さきほどのおさらいで x, y,がとる範囲(-1~1)をみるとわかるように、
行列の計算によりオフセットの位置から、ウィンドウの幅分へ引き延ばしていることが分かります。
ここで、ウィンドウの大きさに引き延ばすからこそ、前回の 射影トランスフォーム行列 にて、
ウィンドウのアスペクト比を使用して補正していたのですね。

1つ気になるところといえば、DirectXでは が反転している点があります。
これはDirectXの出力先のスクリーン画面では、yが大きいほど下方向に描写されるためです。
3D空間上ではもちろん上がプラスため、ビューポート変換で反転しているわけです。

OpenGLの場合は、そもそも出力先のスクリーン画面が上がプラスの設計のため
行列を作っても y の反転は不要となっております。

z については、0がMinZ に 1がMaxZ になるような式になることが分かります。

DirectXとOpenGLとで、射影トランスフォーム後の 値の範囲が異なるため
微妙に式が違っています。実際に計算してあてはめると次のようになります。

OpenGLでも結局、最終的なz値は 0 <= z <= 1 の範囲に収めているようですね。

なお、この MinZMaxZ の設定を利用すると、たとえば、
1度目の描写は、MinZ = 0.0, MaxZ = 0.5
2度目の描写は、MinZ = 0.5, MaxZ = 1.0
のように、1度目の描写の後ろ面に、2度目の描写を行うといったことができます。


おわりに

ここまでのいくつかの回で、
3DCGに必要な座標系と、その座標系への変換をまとめました。
そして、右手系と左手系、OpenGLとDirectXとで
数式のどこに差が表れてくるのかが分かりました。

これまでDirectXを使っていた方が、WebGL(OpenGL)を始めたい。
逆に、WebGLを使っていた方が左手座標系のUnityを使いたいなどがあるかもしれません。
そんな時に、これらのまとめを見て、座標系や、
DirectX/OpenGLの各変換のこと動きを知っておくと
理解が進みやすいのかなと思います。

以上、ありがとうございました。

DirectXとOpenGLの射影トランスフォーム行列の違い

投稿日: 更新日:


はじめに

こんにちは!

引き続いて座標変換の話をします。
本日は射影トランスフォーム行列を覗いてみましょう。

前回の関連記事(予備知識として下から上に読んでおきましょう)
DirectXとOpenGLのビュートランスフォーム行列の違い
3DCGの座標系の紹介
DirectXとOpenGLの回転行列、回転軸、回転方向


数式の比較

.

DirectXとOpenGLのヘルパー関数

これまでと同様、行列作成用の関数が用意されています。

DirectX
matrix *D3DXMatrixPerspectiveFovLH( matrix *pOut, float fovy, float Aspect, float zn, float zf );
左手座標系のパースペクティブ射影行列を作成
matrix *D3DXMatrixPerspectiveFovRH( matrix *pOut, float fovy, float Aspect, float zn, float zf );
右手座標系のパースペクティブ射影行列を作成

OpenGL
void gluPerspective( double fovy, double aspect, double zNear, double zFar);

.

ヘルパー関数を使用したときに作成される行列

DirectXは、マイクロソフトのページの行列
OpenGLは、OpenGLプログラミングメモの「gluPerspectiveを置き換える」の行列を参考にします。

式は、DirectXのD3DXMatrixPerspectiveFovLHをベースにして、
ゲーム3D数学完全ホワイトボックスなパースペクティブ射影変換行列
を参考にしつつ、式変形したものを示します。

引数は次の通り
fovY  視体積の上下方向の視野角(0度から180度)
Aspect 近平面、遠平面のアスペクト比(Width / Height
Far  カメラから遠平面までの距離(ファークリッピング平面)
Near  カメラから近平面までの距離(ニアークリッピング平面)

一度これらを次のようにまとめます。
zoomYの逆関数、Aspectの求め方を補足で書きました。

上記の変数を使用して、DirectXとOpenGLの各行列を書くと次のようになります。

.

右手系と左手系の違い

D3DXMatrixPerspectiveFovLHとD3DXMatrixPerspectiveFovRHを比較すると分かりやすいのですが、
行列の3行目の正負が反転しています。
D3DXMatrixPerspectiveFovRHと同じ右手系のOpenGLのgluPerspectiveの3行目の正負が等しいです。
この正負の反転により、掛け算の最終結果のZ値の値を反転させています。

これまで右手座標系の計算では手前をZ値のプラスとして考えていましたが、
この反転の計算により、奥をプラスに変換させます。
つまり左手座標系へ変換させているわけです。
.

行列は一体何をしているのか

一度、このプロジェクショントランスフォーム行列の式を実際に使用した場合を少し考えてみましょう。
行列は、ベクトルに掛け算して使用します。

掛け算した結果は、クリッピング座標系・クリップ座標系と呼ばれます。そして、この座標系から、wで割ったものが正規化デバイス座標系と呼ばれます。

上記の式からx値とy値については、簡単に何をしているのか想像がつくようになります。ABzoomXzoomYですので、アスペクト比に関わる値を掛けて縦と横幅を調整することに繋がっています。

z値に関しては、上記の式変形ではまだ分かりにくいので、さらに値を代入して数式を変形していきましょう。
今回は、分かりやすくD3DXMatrixPerspectiveFovLHの値を代入しました。

このようにすると、zの値が、NearからFarまでの値をとると、0~1の結果になることが分かるかと思います。

さらにイメージしやすくなるように、1と10の値を代入してみましょう。

これをグラフに書くと次のようになります。

xが1から10をとると、f(x)が0から1までをとることが分かります。

射影行列を位置ベクトルに掛け算した結果のz値の値を、0未満と1以上の場合を表示させないと作ることで、
NearからFarまでにある座標のデータを表示させるという作りが可能となります。
なお空間のNearからFarまでを切り出すため、クリッピング座標系と呼ばれていると思われます。

ところで、数式から分かるように Near = Far と設定してしまうと、0割り算エラーが発生してしまいます。
ある程度値を持ったうえで、 Far > Near となるような値を設定する必要があります。

DirectXとOpenGLの式の違いの理由

右手系の行列を作成するD3DXMatrixPerspectiveFovRHと
右手系のOpenGLのgluPerspectiveとの式が微妙に異なることにお気づきでしょうか。
本来同一になるはずなのに、Near Far が関係するZ値の計算部分が少し違います。

なぜ、このようになっているのでしょうか。

先ほどと同じようにグラフを作ってみましょう。

さらに、NearFarの値を適当に決めてグラフを描きます。

なんと、OpenGLでは、xが1から10をとると、f(x)が-1から1までをとります。
OpenGLの射影変換後のz値は正負をとるということになります。

DirectXではz値が0から1なので、このような違いが行列に現れてきたのです。


おわりに

今回は、座標系の変換の中で最も?よくわからない
プロジェクショントランスフォーム行列をとりあげました。

右手座標系だったものはこの変換により左手座標系になることが分かりました。

DirectXの右手座標系のD3DXMatrixPerspectiveFovRHと
OpenGLの右手座標系のgluPerspectiveは基本的に同じですが、
DirectXのZ値は0~1までに正規化されるのに対して、
OpenGLのZ値は-1~1までに正規化されるため
微妙に行列が違っているということが分かりました。

よくわからない式もグラフをかくと、分かりやすくてよいですね。

以上。おつかれさまでした!

DirectXとOpenGLのビュートランスフォーム行列の違い

投稿日: 更新日:


はじめに

こんこん!

今回は、前回に引き続き座標変換の話をします。
特に、ビュートランスフォームの部分の計算式を見てみましょう。
気になっていたのは、DirectXとOpenGLとで違いがあるのかという点です。
おそらく違いはないはずですが、

前回の関連記事
3DCGの座標系の紹介
DirectXとOpenGLの回転行列、回転軸、回転方向


数式の比較

.

DirextX

ビュートランスフォームを作るために
DirectXでは次のヘルパー関数が用意されています。
matrix *D3DXMatrixLookAtLH( matrix *pOut, vector *pEye, vector *pAt, vector *pUp);
左手座標系ビュー行列を作成する
matrix *D3DXMatrixLookAtRH( matrix *pOut, vector *pEye, vector *pAt, vector *pUp);
右手座標系ビュー行列を作成する

DirectXは左手座標系といわれていますが、
右手座標系と左手座標系の両方の行列が用意されています。

上記のサイトに書いてある式をまとめてみましょう。

なお引数のベクトルは、次の3つです。

・カメラの座標の位置ベクトル e
・カメラの注視点の位置ベクトル a
・カメラの上への方向ベクトル u

D3DXMatrixLookAtLH と D3DXMatrixLookAtRH の違いは、
最初のベクトルzを反転しているかしていないかとなっております。

ここで、最初に出てくる主要なベクトルをまとめてみましょう。

・ベクトル z
カメラ座標から注視点座標への単位方向ベクトル
から への方向ベクトルを求めるには、「 ― 」となります。
normalze(正規化)により距離を1にして単位方向ベクトルにしています。
これが、ワールド座標系にて、カメラ座標系で使用したいz軸方向になります。
ここでは、奥行きベクトル と呼ぶことにしましょう。

・ベクトル x
カメラの上向きのベクトル と、奥行きベクトル クロス積です。
クロス積は、ベクトルXとベクトルYに直行する単位方向ベクトルを計算することができます。
左手座標系で、uzのクロス積の結果は、右側への方向ベクトルになります。
なお、右手座標系で、uzのクロス積の結果は、左側への方向ベクトルになりますが、
zを計算した時点で、右手座標系では値を反転させているため、この結果も右側への方向ベクトルになります。
ここでは、右ベクトルxと呼ぶことにしましょう。
(座標系の違いの話は、DirectXとOpenGLの回転行列、回転軸、回転方向を参照)

・ベクトル y
奥行きベクトルzと、右ベクトルxのクロス積です。
左手座標系で、zxのクロス積の結果は、上側への方向ベクトルになります。
これもまた、右手座標系で、zxのクロス積の結果は、上側への方向ベクトルになりますが、
zを計算した時点で、右手座標系では値を反転させているため、この結果も上側への方向ベクトルになります。
ここでは、上ベクトルyと呼ぶことにしましょう。

最後の行列で、左上の3×3がベクトル x, y, zとなっております。
ここはカメラが向く回転情報となり、回転行列となります。
つまり、上のクロス積などの計算はこの回転行列を求めていたこととなります。

そして、行列の下のeがある部分が移動行列となります。
カメラの座標に、 x, y, zドット積をしているのですが、各方向成分を抜き出すことに相当します。
通常、カメラの位置まで移動させてから回転の場合は、カメラの座標まで引き算すればいいのですが、
今回は、回転させてからカメラの位置まで移動させるということをしているため、ドット積になっています。
.

OpenGL

OpenGLでは、次のヘルパー関数が用意されています。
matrix gluLookAt( vector &eye, vector &center, vector &up )
ビュー行列を作成する

OpenGLは、右手座標系です。
したがって、上記のサイトに書いてあるものは、DirectXのD3DXMatrixLookAtRHと同等のはずです。

ですが、式がすこしちがうように見えます。一度、書いてある式をまとめてみましょう。

ここで、OpenGLの行列は、
DirectXと比べると転置行列となっているため、
DirectXに合わせるようにしましょう。

ここで、ベクトル を反転させてみましょう。
反転させたあとに、xyの値は以前のままとしたいため、クロス積の計算順序を反転させます。

……ここで、D3DXMatrixLookAtRHと同じ式になりました。


おわりに

DirectXとOpenGLの違いシリーズの第2弾。
今回は、ビュートランスフォームの違いを見てみました。

サイトを一見すると、別の式に見えますが、
OpenGLのgluLookAtは、DirectXのD3DXMatrixLookAtRHと同じようですね。

こんどは、プロジェクショントランスフォーム、
ビューポート変換などにちがいがないか、調べていきたいです。

以上、ありがとうございました!

3DCGの座標系の紹介

投稿日:


はじめに

こんにちは!

今日は、3DCGにかかせない座標系の話をするよ!


座標系の種類

では、オブジェクトから画面まで、どのような座標系があるか紹介します。
.

座標系一覧

呼び方が変わりますが、細かく分けると次のような流れになっています。
各々の変換とよばれている部分が行列を掛け算することに相当します。
座標はベクトルとし、このベクトルに座標を掛け算して、次の座標系へ移動していきます。
この手法は、アフィン変換と呼ばれています。
より具体的に話すと、3Dの座標のベクトルは[x, y, z, 1]の4点を持ち、ここに4×4行列を掛け算していく処理です。
なぜ3Dの座標なのに、4つの値をもっているかというと、移動のアフィン変換をするときに必要となるためです。
.
1. オブジェクト座標系・ローカル座標系・モデル座標系
. ↓(ワールドトランスフォーム・モデリング変換)
2. ワールド座標系・グローバル座標系
. ↓(ビュートランスフォーム・視野変換・ビューイング変換)
3. 視点座標系・カメラ座標系
. ↓(プロジェクショントランスフォーム・投影変換・射影トランスフォーム)
4. クリッピング座標系・クリップ座標系
. ↓(遠近除算)
5. 正規化デバイス座標系
. ↓(ビューポート変換)
6. ウインドウ座標系・スクリーン座標系
.

1. ローカル座標系

描写したいオブジェクト自身が開始点[0, 0, 0]となる座標系です。
ボーンとか持つ場合はさらに細かい座標系があります。
絵を見せれば一発なのですが、そこはググってください。
.

2. ワールド座標系

世界の座標です。この世界の中にいろいろなモデルが設置されます。
表示させたいオブジェクト座標の全てのオブジェクトを一度この世界に配置させます。
ワールド座標への変換をワールドトランスフォームと呼びます。
この変換に必要な行列は、オブジェクトの座標と回転情報から作成します。
DirectXでは、D3DXMatrixScaling, D3DXMatrixRotation, D3DXMatrixTranslation
OpenGLでは、glMatrixMode(GL_MODELVIEW)にてglScale、glRotate、glTranslate
.

3. カメラ座標系

カメラの前に世界が広がっている仮定した座標系です。
カメラの視点の座標を[0,0,0]とする変換です。
カメラ座標系への変換をビュートランスフォームと呼びます。
この変換に必要な行列は、カメラの座標とカメラが注視している座標から作成します。
DirectXでは、D3DXMatrixLookAtLH
OpenGLでは、gluLookAt
.

4. クリッピング座標系

近くのものは大きく、遠くのものは小さくなる空間です。
実際には、近くのものは大きく、遠くのものは小さくする透視変換と
近くのものも、遠くのものも同じ大きさにする正射影変換の2種類があります。
透視変換では、目の前にある木の本数は少なく、遠くの山のほうたくさんの木が見えることとなります。
この近くが小さく、遠くのほうが大きい台形の形が見える体積=視体積を、視錐台(しすいだい)と呼びます。
この変換を、プロジェクショントランスフォームと呼びます。
DirectXでは、D3DXMatrixPerspective
OpenGLでは、glMatrixMode(GL_PROJECTION)にてgluPerspective
.

5. 正規化デバイス座標系

プロジェクショントランスフォームは、行列を掛けただけですが、この行列を掛けた後の座標ベクトル(x, y, z, w)にたいして、
(x/w, y/w, z/w)とwで割った後の座標系です。
この計算は行列の掛け算(アフィン変換)で計算できないため、
プロジェクショントランスフォームの中でwに遠くのものを小さくするといった情報をつめこみ、wで割るというのを単独で処理をさせます。
なお、wで割ることで歪むのですが、逆にwで掛け算すれば元の値にもどること示します。
すこし脱線しますが、テクスチャマッピングする際は、
このwの値を保持して計算を行わないと、昔の3Dゲームの壁や床のテクスチャのような歪み方をします。
これをパースペクティブコレクトと呼ばれます。
.

6. スクリーン座標系

実際にウィンドウなどに表示させるための変換です。
表示させる画面の横サイズ、縦サイズから計算させます。
DirectXでは、SetViewport
OpenGLでは、glViewport


さいごに

今回は、様々な座標系があることを説明しました。

ローカル座標系から、ワールド座標系、カメラ座標系へは直感的でわかりやすいのですが、
クリッピング座標系、正規化デバイス座標系、スクリーン座標系については、なんとなく理解しているだけで、
実際の内部の変換はどのようになっているか、あんまり知らない方も多いかと思います。

次回は、その変換の話にふみこんで解説していきたいですねー。
なお、「その70 完全ホワイトボックスなパースペクティブ射影変換行列」が
とても参考になりますので、気になる方はみると良いです!

ではまた!

DirectXとOpenGLの回転行列、回転軸、回転方向

投稿日: 更新日:


はじめに

こんにちは!
ハンドスピナーを最近買ったなたでです!

今日も勉強会をしましょう。
本日は、DirectXとOpenGLの軸の違いに焦点をあてて
回転行列をおさらいしてみましょう。


掛ける順序

3D環境といえば、DirectX、OpenGLです。
今回、回転用の標準関数を調査して比較していきたいと思います。

比較する前に基本的な掛け算のおさらいをしましょう。


上記を確認してください。
これは、DirectX と OpenGL の計算方法の違いを表しています。

DirectX では、横行列に対して正方行列を掛け算して、横行列を求めます。
OpenGL では、正方行列に縦行列を掛け算して、縦行列を求めます。


標準関数の中身

3D環境といえば、DirectX、OpenGLです。
それぞれ、回転用の標準関数が用意されています。
.

DirectX

DirectXでは回転させる命令として、
D3DXMatrixRotation(D3DXMatrixRotationX/D3DXMatrixRotationY/D3DXMatrixRotationZ) が用意されています。

トランスフォーム (Direct3D 9) より
.

OpenGL

OpenGLでは、
glRotate(glRotated/glRotatef) が用意されています。
OpenGL Programming Guide に、標準関数の中の回転行列が書いてあります。
一見、DirectXの回転行列の転置行列となっていますが、
OpenGLでは、「掛ける順序」のように異なるため、この違いは吸収されます。
混乱しないように、DirectXのように掛け算した場合を考えたいと思います。

.

回転行列の特徴

X、Y、Zを順番に見ていくと、
対角成分が1となっている箇所があります。
回転行列は、ベクトルに掛け算することで回転させることができますが、
1となっている部分は、以前の位置と変わらないことを示します。

例えば、ベクトルa=[x,y,z]に行列rotateYをかけた場合、
a[y] の位置は1を掛けるだけであるため、変わらないわけです。
たしかに、Y軸を中心に回転させるので、yの値が変わってしまうのはおかしいですよね。

ところで、Wikipediaを見ると2次元の回転行列が書いてあります。

Wikipediaの2次元の回転行列は、θはどちらの方向に回転するのでしょうか
Wikipediaでは、下記のように書いてあります。
> ユークリッド空間の2次元空間では、原点中心の θ 回転(反時計回りを正とする)の回転行列」

この記述はすこし正確ではありませんが(後述)、
教科書でよくある次のような座標系での反時計回りを指します。

.

転置行列の特徴

Wikipediaの2次元の回転行列と、
D3DXMatrixRotationやglRotateの回転行列は似ています。

具体的には、転置行列のような関係になっていることに気が付くと思います。

ところで、回転行列は、直行行列です。
直行行列の特徴は、転置させると逆行列となります。

ここから、単純に考えるとWikipediaの2次元の回転行列は、反時計回りなので、
その転置行列に似た、D3DXMatrixRotationやglRotateは、時計回り?といった疑問がわきます。
.

回転方向

D3DXMatrixRotationやglRotateは、
実際にどのような回転方向になっているでしょうか。
同様の行列のため、同じ回転方向なのでは?
と一見思えるかもしれません……が

実はこれを考えるためには、
軸の方向の定義を考える必要があります。

例えば、

上記のような場合と

上記のような場合の2種類の座標系があった場合、
X軸の正方向で時計回りした場合、
上の図と下の図とで、体感的に回転方向が変わってくるはずです。

つまり、X軸での時計回りについては、
Y軸とZ軸の正の方向を定義しないといけません。

そして、先ほど触れた Wikipediaに記述されている
「 ユークリッド空間の2次元空間では、原点中心の θ 回転(反時計回りを正とする)」
という記述は、右がX、上がYと定義しておかないと、
「反時計回りを正とする」とは言えないのです……。


軸の定義

DirectXが左手座標系
OpenGLは右手座標系
です。
座標系 (Direct3D 9) より
.

DirectXの座標系

座標系 (Direct3D 9)を見ると、
次のような座標系となることが分かります。

上の図の座標系で各軸の方向を考慮すると、
DirectX の回転は次のようになります。

D3DXMatrixRotationX
回転方向のプラスは、原点からX軸の正の方向を向いたときに、反時計回りをさす。

D3DXMatrixRotationY
回転方向のプラスは、原点からY軸の正の方向を向いたときに、反時計回りをさす。

D3DXMatrixRotationZ
回転方向のプラスは、原点からZ軸の正の方向を向いたときに、反時計回りをさす。

図に表すとこのようになります。

先ほどの数式を見る限り、時計回りに回転すると思いきや、
軸を考慮すると反時計回りをしめしていることが分かります。
.

OpenGLの座標系

OpenGL座標系は、右手座標系です。

これも同様に、
OpenGL の glRotate をあてはめると、

glRotate X
回転方向のプラスは、原点からX軸の正の方向を向いたときに、時計回りをさす。

glRotate Y
回転方向のプラスは、原点からY軸の正の方向を向いたときに、時計回りをさす。

glRotate Z
回転方向のプラスは、原点からZ軸の正の方向を向いたときに、時計回りをさす。

図に表すとこのようになります。
右ねじの法則のようですね。

座標系の違いと回転行列

回転行列を見ると、DirectXとOpenGLは同じにみえました。
しかし、座標系が異なっており、
Z軸のみが反転している関係があることから、
DirectXとOpenGLとで回転の向きが逆向きになっています。

ただし!
反時計回りとは言っても
Z軸のみに関しては、そもそも方向自体が逆にになっているため、
見た目上の動きは、DirectXとOpenGLとで同様の回転方向に見えます。


おわりに

今回、DirectXとOpenGLの標準関数の回転行列の式の解説、
そして、DirectXとOpenGLの座標系を比べて、
正方向が時計回りか反時計回りかを調べました。

DirectXとOpenGLは、Z軸の方向が逆という話はよくききますが、
見た目上の回転方向にも違いがあるという点はそんなにききません。

両方を考える場合は、これらを注意する必要があります。