web-dev-qa-db-ja.com

人間が読める角度で回転行列を最初から作成するにはどうすればよいですか?

私が3Dプログラミングを行うのを常に妨げてきたのは、数学がどのように機能するかを理解できないことです。メソッドと関数を使用したプログラミングフローで数学をうまく進めることができ、それはすべて明確で論理的ですが、数学表記では、それから頭や尾を作ることはできません。

私はウェブサイトを読んだり、これを説明しようとしている機関のビデオを見たりしていますが、それらはすべて数学表記を使用していて、私は単にそれに迷い、私の心はそれを理解できるものに翻訳しません。そこに欠陥があるかもしれません。

また、誰かのコードを使用することは私の興味ではありません。その背後にあるメカニズム、つまりロジックを理解したいと思います。他の人のコードを使用したいのですが、それがどのように機能するかを本当に理解したいと思います。

質問

簡単な言葉で数学表記なし、プログラミング表記/関数/擬似コード、3つの軸すべてに沿って行列変換を実装する方法を説明できますか?

理想的には、私が持っている四角形/三角形のコレクションを回転させるために、glRotateと同様に3つの軸の角度を定義できるメソッド/オブジェクトを作成するためのマテリアル/理解です。 (OpenGL関数にアクセスせずに立方体の形状の3D回転をプログラムしようとしています。これは、表示リストで何かが変更されるたびに1回の描画呼び出しで実行されるためです。)

私は何をしましたか?

私は数学のコツをつかむために90度の変換関数を作成しようとしましたが、理論的には最も簡単なはずの適切な行列を作成できませんでした。あなたは私の失敗した試みをその栄光の中で見ることができます http://jsfiddle.net/bLfg0tj8/5/

Vec3 = function(x,y,z) {
    this.x = x;
    this.y = y;
    this.z = z;
}
Matrix = function Matrix() {
    this.matrixPoints = new Array();    
    this.rotationPoint = new Vec3(0,0,0);
    this.rotationAngle = 90;
}
Matrix.prototype.addVector = function(vector) {
    this.matrixPoints.Push(vector);
}
Matrix.prototype.setRotationPoint = function(vector) {
    this.rotationPoint = vector; 
}
Matrix.prototype.setRotationAngle = function(angle) {
    this.rotationAngle = angle;
}
Matrix.prototype.populate = function() {
    translateToOrigin =     [[1,0,0-this.rotationPoint.x],
                                  [0,1,0-this.rotationPoint.y],
                                  [0,0,0-this.rotationPoint.z]];
    rotationMatrix =         [[0,-1,0],
                                  [0,1,0],
                                  [0,0,1]];
    translateEnd =         [[1,0,this.rotationPoint.x],
                                  [0,1,this.rotationPoint.y],
                                  [0,0,this.rotationPoint.z]];
    currentColumn = 0;
    currentRow = 0;
    this.combomatrix = this.mergeMatrices(this.mergeMatrices(translateEnd,rotationMatrix),
                                          translateToOrigin);
}
Matrix.prototype.transform = function() {
    newmatrix = new Array();
    for(c = 0;c<this.matrixPoints.length;c++) {
        newmatrix.Push(this.applyToVertex(this.matrixPoints[c]));
    }
    return newmatrix;
}
Matrix.prototype.applyToVertex = function(vertex) {
    ret = new Vec3(vertex.x,vertex.y,vertex.z);
    ret.x = ret.x + this.combomatrix[0][0] * vertex.x +
            this.combomatrix[0][1] * vertex.y +
            this.combomatrix[0][2] * vertex.z;
    
    ret.y = ret.y + this.combomatrix[1][0] * vertex.x +
            this.combomatrix[1][1] * vertex.y +
            this.combomatrix[1][2] * vertex.z;
    
    ret.z = ret.z + this.combomatrix[2][0] * vertex.x +
            this.combomatrix[2][1] * vertex.y +
            this.combomatrix[2][2] * vertex.z;
    return ret;
}
Matrix.prototype.mergeMatrices = function(lastStep, oneInFront) {
    step1 = [[0,0,0],[0,0,0],[0,0,0]];
    step1[0][0] =   lastStep[0][0] * oneInFront[0][0] + 
                    lastStep[0][1] * oneInFront[1][0] + 
                    lastStep[0][2] * oneInFront[2][0];
    
    step1[0][1] =   lastStep[0][0] * oneInFront[0][1] + 
                    lastStep[0][1] * oneInFront[1][1] + 
                    lastStep[0][2] * oneInFront[2][1];
    
    step1[0][2] =   lastStep[0][0] * oneInFront[0][2] + 
                    lastStep[0][1] * oneInFront[1][2] + 
                    lastStep[0][2] * oneInFront[2][2];
    //============================================================
    step1[1][0] =   lastStep[1][0] * oneInFront[0][0] + 
                    lastStep[1][1] * oneInFront[1][0] + 
                    lastStep[1][2] * oneInFront[2][0];
    
    step1[1][1] =   lastStep[1][0] * oneInFront[0][1] + 
                    lastStep[1][1] * oneInFront[1][1] + 
                    lastStep[1][2] * oneInFront[2][1];
    
    step1[1][2] =   lastStep[1][0] * oneInFront[0][2] + 
                    lastStep[1][1] * oneInFront[1][2] + 
                    lastStep[1][2] * oneInFront[2][2];
    //============================================================
    step1[2][0] =   lastStep[2][0] * oneInFront[0][0] + 
                    lastStep[2][1] * oneInFront[1][0] + 
                    lastStep[2][2] * oneInFront[2][0];
    
    step1[2][1] =   lastStep[2][0] * oneInFront[0][1] + 
                    lastStep[2][1] * oneInFront[1][1] + 
                    lastStep[2][2] * oneInFront[2][1];
    
    step1[2][2] =   lastStep[2][0] * oneInFront[0][2] + 
                    lastStep[2][1] * oneInFront[1][2] + 
                    lastStep[2][2] * oneInFront[2][2];
    return step1;
}
Matrix.prototype.getCurrentMatrix = function() {
    return this.matrixPoints;
}
myvectors = [new Vec3(50,50,0), new Vec3(20,80,0), new Vec3(80, 80, 0)];

function drawVectors(vectors,color) {
    for(c=0;c<vectors.length;c++) {
        document.getElementById("whoa").innerHTML += '<div style="color:'+color+';position:absolute;left:'+vectors[c].x+'px; top:'+vectors[c].y+'px;z-index:'+vectors[c].z+';">('+c+').</div>';
    }
}
matrix = new Matrix();
for(c=0;c<myvectors.length;c++) {
    matrix.addVector(myvectors[c]);
}
matrix.setRotationPoint(new Vec3(50,70,0));
matrix.populate();
somematrix = matrix.transform();
drawVectors(matrix.getCurrentMatrix(),"Lime"); // draw current matrix that was hand coded
drawVectors([matrix.rotationPoint],'white'); // draw rotation point
drawVectors(somematrix,"red"); // transformed matrix... somehow two points merge
<div id="whoa" style="position:relative;top:50px;left:150px;background-color:green;color:red;width:400px;height:300px;">
    &nbsp;
</div>

緑のテキストは元の三角形、白は中心点、赤は失敗した変換を示しています(中心点の周りに配置されていないためだと思います)。マトリックスを結合マトリックスに結合する方法を考えていたチュートリアルですが、どこかで失敗したと思います。

私が言ったように、私が数学表記を理解して話すことは本当に本当に難しいです。そして助けにならないのは、ほとんどの教師が説明の一部をスキップすることです。行列を乗算するときは、単に乗算を続けるのではなく、各ステップを足し合わせる必要があることを理解するために、2時間だけかかりました。説明のためにイェーイ。

私が一緒に仕事をしている/やりたいことの実際的な例

たとえば、次の世界にあるwavefrontobjファイルからロードされたキューブがあります。

x = 50
y = 100
z = 200

立方体は、クワッドといくつかのUVマッピングを使用して描画されます。ここでは問題ありません。すべてのテクスチャが正しく表示され、美しくレンダリングされます。

これらは、クワッドを使用して描画された立方体の各「面」の位置座標です。

// Front face
-1.0, -1.0,  1.0,
 1.0, -1.0,  1.0,
 1.0,  1.0,  1.0,
-1.0,  1.0,  1.0,

// Back face
-1.0, -1.0, -1.0,
-1.0,  1.0, -1.0,
 1.0,  1.0, -1.0,
 1.0, -1.0, -1.0,

// Top face
-1.0,  1.0, -1.0,
-1.0,  1.0,  1.0,
 1.0,  1.0,  1.0,
 1.0,  1.0, -1.0,

// Bottom face
-1.0, -1.0, -1.0,
 1.0, -1.0, -1.0,
 1.0, -1.0,  1.0,
-1.0, -1.0,  1.0,

// Right face
 1.0, -1.0, -1.0,
 1.0,  1.0, -1.0,
 1.0,  1.0,  1.0,
 1.0, -1.0,  1.0,

// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0,  1.0,
-1.0,  1.0,  1.0,
-1.0,  1.0, -1.0

したがって、これはすべてうまく機能します。しかし、この立方体をx軸に沿って90度、z軸に沿って45度回転させたい場合はどうすればよいでしょうか。 glRotateを使用することはできません。これは、データをテッセレーターオブジェクトに渡す時点では、データ自体を実際にレンダリングするのではなく、データを取り込むだけなので、opengl関数を介してマトリックス変換を実行できないためです。

データの保存方法は次のとおりです。

WaveFrontObject()
   |
   |-> Groups(String groupname)
        |
        |-> Faces()
              |
              |-> Vertex(float x, float y, float z)[] 
              |-> Float UVmap[] corresponding to each vertex
              |-> drawFace() // Draws the face as a quad or triangle

したがって、上記の各座標は、グループ「cube」の波面オブジェクトの面として保存されます。

立方体がテッセレータに追加されると、世界の正しい座標に変換され、通常の状態になります。

ただし、常に同じようにレンダリングされます。ある角度でレンダリングしたい場合は、この時点で別のウェーブフロントオブジェクトを作成してそれを実行できるようにする必要があります。私の意見では、それがいくつかの数学で解決できるときに行うのは狂気です。

回答が必要

  1. 変換行列を作成する方法と数学を私に説明する試みを段階的に説明します。
  2. 位置の中心を中心に向きを保ちながら、面の四角形/三角形に変換行列を適用する方法の説明

    x = 50.5 y = 100.5 z = 200.5

  3. 説明に沿ったいくつかの例/擬似コード。

説明に使用される使用されているプログラミング言語は、Cファミリーにある限り、実際には関係ありません。

数学表記/話すことは避けてください。アルファベータ、シータが何であるかわかりません。x軸、y軸、z軸が何であるかを知っています。私は角度が何であるかを知っていますが、数学者がそれのために見つける名前を知りません。

数学の名前を使用したい場合は、3Dの世界/コードでの名前とその形成/計算方法を説明してください。

私は単にの線に沿ってメソッド/オブジェクトを作りたいです

Matrix.transformVertices(vertices[], 90deg x, 45 deg y, 0 deg z);
24
Tschallacka

したがって、問題は実際には4x4の同種変換行列を理解する

残された唯一のものの背後にある数学がなければ、人間の抽象化/理解にとってはるかに優れた幾何学的表現/意味です。

1。では、4x4マトリックスとは何ですか?

これは、いくつかのデカルト座標系の表現であり、次のもので構成されています。

  1. _3_基底ベクトル(各軸に1つ)赤、緑、青

    したがって、赤、緑、青のベクトルが互いに垂直である場合、座標系は直交になります。それらが単位ベクトルでもある場合、それは正規直交です(たとえば単位行列のように)。

  2. 原点グレー

  3. 投影と均質な側面(マトリックスのマークされていない下部)

    この部分は、回転と平行移動を一度に有効にするためだけにあります。したがって、使用するポイントは均一である必要があります。つまり、ポイントの場合は_(x,y,z,w=1)_、方向ベクトルの場合は_(x,y,z,w=0)_の形式になります。 _(x,y,z)_だけの場合、行列は_3x3_になり、変換には不十分です。幾何学的に説明するのが難しい投影法は使用しません。

このレイアウトはOpenGL表記からのものです。転置表現もあります(ベクトルは列ではなく行です)

次に、この座標系との間で任意の点を変換する方法:

_g=M*l;
l=Inverse(M)*g;
_

どこ:

  • Mは変換行列です
  • lMローカル座標系ポイント(LCS)です
  • gはグローバル座標系ポイント(GCS)です

転置バージョン(DirectX)の場合:

_l=M*g;
g=Inverse(M)*l;
_

これは、転置された直交回転行列もそれ自体の逆行列であるためです

OpenGL transform matrix

2。それを視覚化する方法

はい、マトリックス番号を描くことはできますが、特に番号が変化している場合は、最初は意味がないので、上の画像のように軸ベクトルを描きます。ここで、各軸はOriginから_Origin + line_size*axis_vector_までの線です。

3。それを構築する方法

軸ベクトルと原点を計算し、それらを行列内に配置するだけです。直交性の悪用外積を確実にするため(ただし、正しい方向を使用するように乗数の順序に注意してください)

4。効果

  • 回転は軸を回転させることによって行われるため、パラメトリック円方程式によって各軸を計算できます...
  • スケーリングは、軸にスケール係数を掛けることによって行われます。
  • スキューは非垂直軸を使用しているだけです

5。回転

ほとんどの場合、増分回転が使用されます。 2つのタイプがあります

  • ローカル回転_M'=M*rotation_matrix_プレーン、車、またはプレーヤーを制御するように、ローカル座標軸を中心に回転します...ほとんどのエンジン/ゲームは使用しませんこれらとオイラー角で偽造します。これは安価な解決策です(多くの癖や問題があります)。OpenGLを使用しているほとんどの人はこれを知らないからです_glRotate/glTranslate_呼び出しの可能なスタックリスト...

  • グローバル回転M'=Inverse(Inverse(M)*rotation_matrix)グローバル座標系の軸を中心に回転します。

ここで、_rotation_matrix_は任意の標準回転変換行列です。

異なるマトリックスレイアウト(転置)がある場合、ローカル回転とグローバル回転は逆に計算されます...

次のような_rotation_matrix_角度から_3_を計算することもできます。

_rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);
_

Wiki回転行列 _Rx,Ry,Rz_の3D _Basic rotations_が必要です。ご覧のとおり、これらは実際には単なる単位円パラメトリック方程式です。乗算の順序によって、角度がターゲット位置に収束する方法が変わります。これはオイラー角と呼ばれ、私はそれを使用しません(代わりに、ステップ変更を統合します。適切に実行されれば、より簡単であることは言うまでもなく、制限はありません)。

とにかく、必要に応じて、変換行列をオイラー角に比較的簡単に変換できます。

6。 glRotate

glRotateが必要な場合は、代わりにクォータニオンを使用する必要があります。これは、3つの角度ではなく、軸を中心とした回転であるためです。回避策があります:

  1. その軸の変換行列Nを作成します
  2. 次に、行列Mをそれに変換します
  3. Nを角度で回転させる
  4. 次に、MNからグローバル座標に変換し直します

または、代わりに Rodrigues_rotation_formula を使用できます

この場合、行列を行列との間で変換するには、軸を点として変換し、原点をそのままにしますが、Nの原点は(0,0,0)でなければなりません! !!または変換されたベクトルには、代わりに_w=0_が必要です。

7。使用法

変換は累積的です。つまり、次のことを意味します。

  • _p'=M1*M2*M3*M4*p;_は_M=M1*M2*M3*M4; p'=M*p_と同じです

したがって、変換するポイントが多い場合は、すべての変換を単一の行列に事前計算して、それだけを使用します。ポイントに後続のすべての行列を掛ける必要はありません。 OK、コンセプトは次のとおりです。

_3_座標系が必要です。

  • カメラC
  • 世界(通常は単位行列)
  • オブジェクトO(各オブジェクトには独自のマトリックスがあります)

したがって、_8_頂点_p0,...,p7_を持つ立方体がある場合は、各ポイントでオブジェクトのローカル座標からカメラのローカル座標への変換を実行する必要があります。一部のgfxapiはその一部を実行するため、必要なものだけを適用するため、本当に必要です。

  • p(i)'=inverse(C)*unit*M*p(i);

変換は累積的であり、単位行列は何も変更しません。

  • Q=inverse(C)*M; p(i)'=Q*p(i);

したがって、描画する前に、描画されたオブジェクトの計算Qを取得し、オブジェクトの各ポイントp(i)を取得して、変換されたp(i)'を計算し、変換されたオブジェクトを描画/使用します... p(i)'はローカルカメラ座標系(画面のx、y)にありますが、そこには遠近法がないため、描画する前に、任意の投影行列を追加して、でz座標で除算することもできます。 end ...射影も累積的であるため、Q内にも含めることができます。

[edit1] C++の例

_//$$---- Form CPP ----
//---------------------------------------------------------------------------
// apart from math.h include you can ignore this machine generated VCL related code
#include <vcl.h>
#pragma hdrstop
#include "win_main.h"
#include <math.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main; // pointer to main window ...
//---------------------------------------------------------------------------
// Here is the important stuff some math first
//---------------------------------------------------------------------------
const double deg=M_PI/180.0;
double divide(double x,double y);
void  matrix_mul       (double *c,double *a,double *b); // c[16] = a[16] * b[16]
void  matrix_mul_vector(double *c,double *a,double *b); // c[ 4] = a[16] * b[ 4]
void  matrix_subdet    (double *c,double *a);           // c[16] = all subdets of a[16]
double matrix_subdet   (          double *a,int r,int s);//      = subdet(r,s) of a[16]
double matrix_det      (          double *a);           //       = det of a[16]
double matrix_det      (          double *a,double *b); //       = det of a[16] and subdets b[16]
void  matrix_inv       (double *c,double *a);           // c[16] = a[16] ^ -1
//---------------------------------------------------------------------------
double divide(double x,double y)
        {
        if (!y) return 0.0;
        return x/y;
        }
void  matrix_mul       (double *c,double *a,double *b)
        {
        double q[16];
        q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
        q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
        q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
        q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
        q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
        q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
        q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
        q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
        q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
        q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
        q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
        q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
        q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
        q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
        q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
        q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
        for(int i=0;i<16;i++) c[i]=q[i];
        }
void  matrix_mul_vector(double *c,double *a,double *b)
        {
        double q[3];
        q[0]=(a[ 0]*b[0])+(a[ 1]*b[1])+(a[ 2]*b[2])+(a[ 3]);
        q[1]=(a[ 4]*b[0])+(a[ 5]*b[1])+(a[ 6]*b[2])+(a[ 7]);
        q[2]=(a[ 8]*b[0])+(a[ 9]*b[1])+(a[10]*b[2])+(a[11]);
        for(int i=0;i<3;i++) c[i]=q[i];
        }
void  matrix_subdet    (double *c,double *a)
        {
        double   q[16];
        int     i,j;
        for (i=0;i<4;i++)
         for (j=0;j<4;j++)
          q[j+(i<<2)]=matrix_subdet(a,i,j);
        for (i=0;i<16;i++) c[i]=q[i];
        }
double matrix_subdet    (         double *a,int r,int s)
        {
        double   c,q[9];
        int     i,j,k;
        k=0;                            // q = sub matrix
        for (j=0;j<4;j++)
         if (j!=s)
          for (i=0;i<4;i++)
           if (i!=r)
                {
                q[k]=a[i+(j<<2)];
                k++;
                }
        c=0;
        c+=q[0]*q[4]*q[8];
        c+=q[1]*q[5]*q[6];
        c+=q[2]*q[3]*q[7];
        c-=q[0]*q[5]*q[7];
        c-=q[1]*q[3]*q[8];
        c-=q[2]*q[4]*q[6];
        if (int((r+s)&1)) c=-c;       // add signum
        return c;
        }
double matrix_det       (         double *a)
        {
        double c=0;
        c+=a[ 0]*matrix_subdet(a,0,0);
        c+=a[ 4]*matrix_subdet(a,0,1);
        c+=a[ 8]*matrix_subdet(a,0,2);
        c+=a[12]*matrix_subdet(a,0,3);
        return c;
        }
double matrix_det       (         double *a,double *b)
        {
        double c=0;
        c+=a[ 0]*b[ 0];
        c+=a[ 4]*b[ 1];
        c+=a[ 8]*b[ 2];
        c+=a[12]*b[ 3];
        return c;
        }
void  matrix_inv       (double *c,double *a)
        {
        double   d[16],D;
        matrix_subdet(d,a);
        D=matrix_det(a,d);
        if (D) D=1.0/D;
        for (int i=0;i<16;i++) c[i]=d[i]*D;
        }
//---------------------------------------------------------------------------
// now the object representation
//---------------------------------------------------------------------------
const int pnts=8;
double pnt[pnts*3]=     // Vertexes for 100x100x100 cube centered at (0,0,0)
    {
    -100.0,-100.0,-100.0,
    -100.0,+100.0,-100.0,
    +100.0,+100.0,-100.0,
    +100.0,-100.0,-100.0,
    -100.0,-100.0,+100.0,
    -100.0,+100.0,+100.0,
    +100.0,+100.0,+100.0,
    +100.0,-100.0,+100.0,
    };
const int facs=6;
int fac[facs*4]=        // faces (index of point used) no winding rule
    {
    0,1,2,3,
    4,5,6,7,
    0,1,5,4,
    1,2,6,5,
    2,3,7,6,
    3,0,4,7,
    };
double rep[16]=        // 4x4 transform matrix of object (unit from start) at (0,0,+100)
    {
    1.0,0.0,0.0,  0.0,
    0.0,1.0,0.0,  0.0,
    0.0,0.0,1.0,100.0,
    0.0,0.0,0.0,1.0,
    };
double eye[16]=        // 4x4 transform matrix of camera at (0,0,-150)
    {
    1.0,0.0,0.0,   0.0,
    0.0,1.0,0.0,   0.0,
    0.0,0.0,1.0,-150.0,
    0.0,0.0,0.0,1.0,
    };
//---------------------------------------------------------------------------
// this is how to draw it
//---------------------------------------------------------------------------
void obj(double *pnt,int pnts,int *fac,int facs,double *rep,double *ieye)
    {
    // variables for drawing
    int i;
    double p0[3],p1[3],p2[3],p3[3],m[16],d;
    // gfx api variables (change to your stuff) Main is the main form of this application
    TCanvas *scr=Main->bmp->Canvas;
    double xs2=Main->ClientWidth/2,ys2=Main->ClientHeight/2;
    double v=xs2*tan(30.0*deg); // 60 degree viewing angle perspective projection

    matrix_mul(m,ieye,rep);             // cumulate all needed transforms

    for (i=0;i<facs*4;)                 // go through all faces
        {
        // convert all points of face
        matrix_mul_vector(p0,m,&pnt[fac[i]*3]); i++;
        matrix_mul_vector(p1,m,&pnt[fac[i]*3]); i++;
        matrix_mul_vector(p2,m,&pnt[fac[i]*3]); i++;
        matrix_mul_vector(p3,m,&pnt[fac[i]*3]); i++;
        // here goes perspective divide by z coordinate if needed
        d=divide(v,p0[2]); p0[0]*=d; p0[1]*=d;
        d=divide(v,p1[2]); p1[0]*=d; p1[1]*=d;
        d=divide(v,p2[2]); p2[0]*=d; p2[1]*=d;
        d=divide(v,p3[2]); p3[0]*=d; p3[1]*=d;
        // here is viewport transform (just translate (0,0) to middle of screen in this case
        p0[0]+=xs2; p0[1]+=ys2;
        p1[0]+=xs2; p1[1]+=ys2;
        p2[0]+=xs2; p2[1]+=ys2;
        p3[0]+=xs2; p3[1]+=ys2;
        // draw quad
        // I use VCL GDI TCanvas you use what you have ...
        // and wireframe only to keep this simple (no Z buffer,winding culling,...)
        scr->Pen->Color=clAqua;     // perimeter wireframe
        scr->MoveTo(p0[0],p0[1]);
        scr->LineTo(p1[0],p1[1]);
        scr->LineTo(p2[0],p2[1]);
        scr->LineTo(p3[0],p3[1]);
        scr->LineTo(p0[0],p0[1]);
//      scr->Pen->Color=clBlue;     // face cross to visualy check if I correctly generate the fac[]
//      scr->MoveTo(p0[0],p0[1]);
//      scr->LineTo(p2[0],p2[1]);
//      scr->MoveTo(p1[0],p1[1]);
//      scr->LineTo(p3[0],p3[1]);
        }
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void TMain::draw()
    {
    if (!_redraw) return;
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));

    // compute inverse of camera need to compute just once for all objects
    double ieye[16];
    matrix_inv(ieye,eye);
    // draw all objects
    obj(pnt,pnts,fac,facs,rep,ieye);

    Main->Canvas->Draw(0,0,bmp);
    _redraw=false;
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    // window constructor you can ignore this ... (just create a backbuffer bitmap here)
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    pyx=NULL;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    // window destructor release memory ... also ignoe this
    if (pyx) delete pyx;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    // on resize event ... just resize/redraw backbuffer also can ignore this
    xs=ClientWidth;  xs2=xs>>1;
    ys=ClientHeight; ys2=ys>>1;
    bmp->Width=xs;
    bmp->Height=ys;
    if (pyx) delete pyx;
    pyx=new int*[ys];
    for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    // repaint event can ignore
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    // timer event to animate the cube ...
    _redraw=true;

    // rotate the object to see it in motion
    double ang,c,s;

    ang=5.0*deg; c=cos(ang); s=sin(ang);    // rotate baround z by 5 degrees per timer step
    double rz[16]= { c, s, 0, 0,
                    -s, c, 0, 0,
                     0, 0, 1, 0,
                     0, 0, 0, 1 };

    ang=1.0*deg; c=cos(ang); s=sin(ang);    // rotate baround x by 1 degrees per timer step
    double rx[16]= { 1, 0, 0, 0,
                     0, c, s, 0,
                     0,-s, c, 0,
                     0, 0, 0, 1 };
    matrix_mul(rep,rep,rz);
    matrix_mul(rep,rep,rx);

    draw();
    }
//---------------------------------------------------------------------------
_

これがどのように見えるかです:

cube example

そして、背面カリングを使用したGIFアニメーション:

animation

[メモ]

さらに質問がある場合は、コメントしてください...

[Edit2]よく必要な基本的な3Dベクトル演算

クロス/ドット積や絶対値などのベクトル演算を計算する方法がわからない場合は、以下を参照してください。

_// cross product: W = U x V
W.x=(U.y*V.z)-(U.z*V.y)
W.y=(U.z*V.x)-(U.x*V.z)
W.z=(U.x*V.y)-(U.y*V.x)
// dot product: a = (U.V)
a=U.x*V.x+U.y*V.y+U.z*V.z
// abs of vector a = |U|
a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))
_

ここに私のC++ベクトル数学:

_static double vector_tmp[3];
double divide(double x,double y) { if ((y>=-1e-30)&&(y<=+1e-30)) return 0.0; return x/y; }
double* vector_ld(double x,double y,double z)          { double *p=vector_tmp; p[0]=x; p[1]=y; p[2]=z; return p;}
double* vector_ld(double *p,double x,double y,double z) {                      p[0]=x; p[1]=y; p[2]=z; return p;}
void  vector_copy(double *c,double *a)         { for(int i=0;i<3;i++) c[i]=a[i];       }
void  vector_abs(double *c,double *a)          { for(int i=0;i<3;i++) c[i]=fabs(a[i]); }
void  vector_one(double *c,double *a)
        {
        double l=divide(1.0,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
        c[0]=a[0]*l;
        c[1]=a[1]*l;
        c[2]=a[2]*l;
        }
void  vector_len(double *c,double *a,double l)
        {
        l=divide(l,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
        c[0]=a[0]*l;
        c[1]=a[1]*l;
        c[2]=a[2]*l;
        }
void  vector_neg(double *c,double *a)          { for(int i=0;i<3;i++) c[i]=-a[i];      }
void  vector_add(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]+b[i]; }
void  vector_sub(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]-b[i]; }
void  vector_mul(double *c,double *a,double *b) // cross
        {
        double   q[3];
        q[0]=(a[1]*b[2])-(a[2]*b[1]);
        q[1]=(a[2]*b[0])-(a[0]*b[2]);
        q[2]=(a[0]*b[1])-(a[1]*b[0]);
        for(int i=0;i<3;i++) c[i]=q[i];
        }
void  vector_mul(double *c,double *a,double  b) { for(int i=0;i<3;i++) c[i]=a[i]*b; }
void  vector_mul(double *c,double  a,double *b) { for(int i=0;i<3;i++) c[i]=a*b[i]; }
double vector_mul(         double *a,double *b) { double c=0; for(int i=0;i<3;i++) c+=a[i]*b[i]; return c; } // dot
double vector_len(double *a) { return sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])); }
double vector_len2(double *a) { return (a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2]); }
_

[Edit3]キーボードを介したカメラとオブジェクトの制御のためのローカル回転

これは最近よく聞かれるので、ここでデモを使った私の答えの例をいくつか示します。

44
Spektre