サーチ…
行列の紹介
あなたがOpenGLやその他のグラフィックスAPIでプログラミングしているとき、あなたは数学でそれほど良くないときには、レンガの壁に当たってしまいます。ここでは、3Dオブジェクトを使って動き/スケーリングや他の多くのクールなものを実現する方法の例を説明します。
実際の事例を考えてみましょう...あなたはOpenGLで素晴らしい(3次元の)立方体を作りました。あなたはそれをどの方向にでも移動したいと思っています。
glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
Unity3dのようなゲームエンジンでは、これは簡単でしょう。あなたはtransform.Translate()を呼び出すだけで済みますが、OpenGLには数学ライブラリは含まれていません。
良い数学ライブラリはglmですが、あなたのためにすべての(重要な)数学的方法をコード化します。
最初に、OpenGLの3dオブジェクトには多くの情報が含まれていることを理解する必要があります。お互いに依存する変数がたくさんあります。これらすべての変数を管理するスマートな方法は、行列を使うことです。
行列は、列と行に書かれた変数の集合です。行列は、1x1,2x4または任意の任意の数にすることができます。
[1|2|3]
[4|5|6]
[7|8|9] //A 3x3 matrix
あなたは彼らと一緒に本当にクールなものをすることができます...しかし、彼らは私のキューブを動かして私を助けることができますか?これを実際に理解するには、まずいくつかのことを知る必要があります。
- あなたはどのようにポジションからマトリックスを作るのですか?
- どのように行列を翻訳しますか?
- OpenGLにどうやって渡しますか?
私たちの重要な行列データとメソッドをすべて含むクラスを作成しましょう(C ++で書かれています)
template<typename T>
//Very simple vector containing 4 variables
struct Vector4{
T x, y, z, w;
Vector4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w){}
Vector4(){}
Vector4<T>& operator=(Vector4<T> other){
this->x = other.x;
this->y = other.y;
this->z = other.z;
this->w = other.w;
return *this;
}
}
template<typename T>
struct Matrix4x4{
/*!
* You see there are columns and rows like this
*/
Vector4<T> row1,row2,row3,row4;
/*!
* Initializes the matrix with a identity matrix. (all zeroes except the ones diagonal)
*/
Matrix4x4(){
row1 = Vector4<T>(1,0,0,0);
row2 = Vector4<T>(0,1,0,0);
row3 = Vector4<T>(0,0,1,0);
row4 = Vector4<T>(0,0,0,1);
}
static Matrix4x4<T> identityMatrix(){
return Matrix4x4<T>(
Vector4<T>(1,0,0,0),
Vector4<T>(0,1,0,0),
Vector4<T>(0,0,1,0),
Vector4<T>(0,0,0,1));
}
Matrix4x4(const Matrix4x4<T>& other){
this->row1 = other.row1;
this->row2 = other.row2;
this->row3 = other.row3;
this->row4 = other.row4;
}
Matrix4x4(Vector4<T> r1, Vector4<T> r2, Vector4<T> r3, Vector4<T> r4){
this->row1 = r1;
this->row2 = r2;
this->row3 = r3;
this->row4 = r4;
}
/*!
* Get all the data in an Vector
* @return rawData The vector with all the row data
*/
std::vector<T> getRawData() const{
return{
row1.x,row1.y,row1.z,row1.w,
row2.x,row2.y,row2.z,row2.w,
row3.x,row3.y,row3.z,row3.w,
row4.x,row4.y,row4.z,row4.w
};
}
}
最初に、4 x 4行列のデフォルトのコンストラクタで非常に特異なことに気付きます。呼び出されたときにゼロで始まるわけではありませんが、
[1|0|0|0]
[0|1|0|0]
[0|0|1|0]
[0|0|0|1] //A identity 4 by 4 matrix
すべての行列は対角線上の行列から始める必要があります。 (>。<というだけで)
さて、叙事詩のキューブで4 x 4の行列を宣言しましょう。
glUseProgram(cubeProgram)
Matrix4x4<float> position;
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, cubeData);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
今、私たちは実際に私たちが最終的にいくつかの数学を始めることができるすべての変数を持っています!翻訳をしよう。 Unity3dでプログラミングしたことがあれば、Transform.Translate関数を覚えているかもしれません。独自の行列クラスで実装しよう
/*!
* Translates the matrix to
* @param vector, The vector you wish to translate to
*/
static Matrix4x4<T> translate(Matrix4x4<T> mat, T x, T y, T z){
Matrix4x4<T> result(mat);
result.row1.w += x;
result.row2.w += y;
result.row3.w += z;
return result;
}
これはキューブを動かすのに必要な数学です(回転しないかスケーリングしてください)。すべての角度で動作します。実際のシナリオでこれを実装しましょう
glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
私たちのシェーダは、私たちの素晴らしい行列を使用する必要があります
#version 410 core
uniform mat4 mv_matrix;
layout(location = 0) in vec4 position;
void main(void){
gl_Position = v_matrix * position;
}
それはうまくいくはずですが、私たちのプログラムには既にバグがあります。あなたがz軸に沿って移動すると、オブジェクトは薄い空気の中に右に消えていくように見えます。これは、投影行列がないためです。このバグを解決するには、2つのことを知る必要があります。
- 投影行列はどのように見えますか?
- どのようにポジションマトリックスと組み合わせることができますか?
まあ、私たちは視点を作ることができます(私たちはすべて3次元を使用しています)matrixコード
template<typename T>
Matrix4x4<T> perspective(T fovy, T aspect, T near, T far){
T q = 1.0f / tan((0.5f * fovy) * (3.14 / 180));
T A = q / aspect;
T B = (near + far) / (near - far);
T C = (2.0f * near * far) / (near - far);
return Matrix4x4<T>(
Vector4<T>(A,0,0,0),
Vector4<T>(0,q,0,0),
Vector4<T>(0,0,B,-1),
Vector4<T>(0,0,C,0));
}
それは恐ろしく見えますが、この方法は、実際にどのくらい遠く(そしてどれくらい近い)と視野を調べたいかの行列を計算します。
今、我々は投影行列と位置行列を持っています。しかし、それらをどのように組み合わせますか?まあ楽しいのは、実際には2つの行列を互いに乗算できるということです。
/*!
* Multiplies a matrix with an other matrix
* @param other, the matrix you wish to multiply with
*/
static Matrix4x4<T> multiply(const Matrix4x4<T>& first,const Matrix4x4<T>& other){
//generate temporary matrix
Matrix4x4<T> result;
//Row 1
result.row1.x = first.row1.x * other.row1.x + first.row1.y * other.row2.x + first.row1.z * other.row3.x + first.row1.w * other.row4.x;
result.row1.y = first.row1.x * other.row1.y + first.row1.y * other.row2.y + first.row1.z * other.row3.y + first.row1.w * other.row4.y;
result.row1.z = first.row1.x * other.row1.z + first.row1.y * other.row2.z + first.row1.z * other.row3.z + first.row1.w * other.row4.z;
result.row1.w = first.row1.x * other.row1.w + first.row1.y * other.row2.w + first.row1.z * other.row3.w + first.row1.w * other.row4.w;
//Row2
result.row2.x = first.row2.x * other.row1.x + first.row2.y * other.row2.x + first.row2.z * other.row3.x + first.row2.w * other.row4.x;
result.row2.y = first.row2.x * other.row1.y + first.row2.y * other.row2.y + first.row2.z * other.row3.y + first.row2.w * other.row4.y;
result.row2.z = first.row2.x * other.row1.z + first.row2.y * other.row2.z + first.row2.z * other.row3.z + first.row2.w * other.row4.z;
result.row2.w = first.row2.x * other.row1.w + first.row2.y * other.row2.w + first.row2.z * other.row3.w + first.row2.w * other.row4.w;
//Row3
result.row3.x = first.row3.x * other.row1.x + first.row3.y * other.row2.x + first.row3.z * other.row3.x + first.row3.w * other.row4.x;
result.row3.y = first.row3.x * other.row1.y + first.row3.y * other.row2.y + first.row3.z * other.row3.y + first.row3.w * other.row4.y;
result.row3.z = first.row3.x * other.row1.z + first.row3.y * other.row2.z + first.row3.z * other.row3.z + first.row3.w * other.row4.z;
result.row3.w = first.row3.x * other.row1.w + first.row3.y * other.row2.w + first.row3.z * other.row3.w + first.row3.w * other.row4.w;
//Row4
result.row4.x = first.row4.x * other.row1.x + first.row4.y * other.row2.x + first.row4.z * other.row3.x + first.row4.w * other.row4.x;
result.row4.y = first.row4.x * other.row1.y + first.row4.y * other.row2.y + first.row4.z * other.row3.y + first.row4.w * other.row4.y;
result.row4.z = first.row4.x * other.row1.z + first.row4.y * other.row2.z + first.row4.z * other.row3.z + first.row4.w * other.row4.z;
result.row4.w = first.row4.x * other.row1.w + first.row4.y * other.row2.w + first.row4.z * other.row3.w + first.row4.w * other.row4.w;
return result;
}
Ooef ..実際にはもっと酷く見えるコードがたくさんあります。それはforループで行うことができますが、私は(おそらく間違って)これは決して行列で作業したことのない人にとってはこれがもっとはっきりしていると思っていました。
コードを見て繰り返しパターンに気づく。行を追加して続行する列を掛けます(これは任意のサイズ行列で同じです)
*行列との乗算は通常の乗算とは異なります。 AXB!= B×A *
これを投影してポジションマトリックスに追加する方法を知りました。実際のコードは次のようになります。
glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
position = Matrix4x4<float>::multiply(Matrix<float>::perspective<float>(50, 1 , 0.1f, 100000.0f), position);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
今私たちのバグは潰れて、私たちの立方体は遠くにかなり叙事詩的に見えます。キューブをスケールする場合は、数式は次のとおりです。
/*!
* Scales the matrix with given vector
* @param s The vector you wish to scale with
*/
static Matrix4x4<T> scale(const Matrix4x4<T>& mat, T x, T y, T z){
Matrix4x4<T> tmp(mat);
tmp.row1.x *= x;
tmp.row2.y *= y;
tmp.row3.z *= z;
return tmp;
}
対角変数を調整する必要があります。
ローテーションについては、Quaternionsを詳しく見ていく必要があります。