如何从头开始构建具有人类可读角度的旋转矩阵?

How do I compose a rotation matrix with human readable angles from scratch?

提问人:Tschallacka 提问时间:1/22/2015 最后编辑:Tschallacka 更新时间:11/9/2020 访问量:19693

问:

一直阻碍我进行 3D 编程的一件事是无法理解数学是如何工作的。我可以使用方法和函数在编程流程中很好地配合数学,然后对我来说一切都清晰且合乎逻辑,但在数学符号中,我无法从中得出正面或反面。

我一直在阅读网站,观看试图解释这一点的机构的视频,但它们都使用数学符号,我只是迷失在其中,我的头脑不会将其转化为可以理解的东西。我可能那里有缺陷。

另外,仅仅使用某人的代码不是我的兴趣,我想了解它背后的机制,逻辑。我很乐意使用别人的代码,但我真的很想了解它是如何工作的。

问题

你能用简单的术语向我解释一下,没有数学符号,只有编程符号/函数/伪码,如何实现沿所有 3 个轴的矩阵变换吗?

理想情况下,我想要的是编写方法/对象的材料/理解,我可以在其中定义类似于 glRotate 的 3 个轴的角度来旋转我拥有的四边形/三角形的集合。(我正在尝试对立方体形状的 3D 旋转进行编程,而无需访问 OpenGL 函数来为我执行此操作,因为每次显示列表中发生更改时,这都是在一次绘制调用中完成的。

我做了什么?

我曾尝试制作一个 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 个小时才明白,在将矩阵相乘时,您需要将每个步骤相加,而不仅仅是继续相乘。是的,解释。

一个我工作/想工作的实际例子

例如,我有一个立方体,从位于

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,因为在我将数据传递给 tesselator 对象的那一刻,我无法通过 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

因此,我给出的上述每个坐标都存储为“立方体”组中波前对象的面。

当立方体被添加到镶嵌器中时,它将被转换为世界中的正确坐标,并呈现正常。

但是,它总是呈现相同的内容。如果我希望它以一定角度渲染,我此时必须制作一个单独的波前对象才能做到这一点。在我看来,当它可以通过一些数学来解决时,这是疯狂的。

答案中需要

  1. 逐步解释如何构建翻译矩阵,并尝试向我解释数学。
  2. 解释如何将平移矩阵应用于面中的四边形/三角形,因为它们始终围绕其位置的中心定向

    x = 50.5 y = 100.5 z = 200.5

  3. 一些示例/伪代码与解释一起进行。

用于解释的编程语言并不真正相关,只要它在 C 系列中

请尽量远离数学符号/说话。我不知道什么是 alpha beta,thetha 是什么,我知道 x 轴、y 轴和 z 轴是什么。我确实知道什么是角度,但我不知道数学家为它找到的名字。

如果您想使用数学名称,请向我解释它们在 3D 世界/代码中是什么以及它们是如何形成/计算的。

我只是想按照以下方式制作一个方法/对象

Matrix.transformVertices(vertices[], 90deg x, 45 deg y, 0 deg z);
OpenGL 数学 矩阵 3D

评论

3赞 datenwolf 1/22/2015
数学符号的全部目的是使事情更容易理解。编程算法永远只是一般数学概念的一个特殊情况。当谈到线性代数时,我认为您可能会发现这非常有用(尤其是矩阵如何充当传入数据片段的“函数”): betterexplained.com/articles/linear-algebra-guide
2赞 Thomas 1/22/2015
你问的是不可能的。当然,您可以复制和粘贴矩阵类或旋转方法,我相信您可以在那里找到很多。在其中,您将看到数组按照一些 for 循环方案相互求和和相乘。如果你知道数组和for循环,那么你可以用纯粹的编程和无数学的方式来理解它。如果这对你来说还不够,你必须继续尝试学习数学。有时,如果所有教程都与您的思维方式不符,这很难,但也许您可以访问您居住的课程或课程。
0赞 Reto Koradi 1/22/2015
虽然我对这样一个精心写的问题投反对票会感到难过,但我同意之前的评论者的观点,即这是一个艰难的要求。而且我不认为它很适合 SO。在没有数学符号的情况下解释数学概念本身几乎是矛盾的。尽管这是相当基础的数学,但如果一切都必须从头开始解释,仍然需要大量的解释。尽管如此,如果我试一试,我仍然不确定我是否会比所有因写数学书而获得报酬的人做得更好。然后整个事情可能会被删除。
1赞 rob3c 7/4/2017
迈克尔·法拉第(Michael Faraday)也在数学符号方面苦苦挣扎,他似乎做得很好 en.wikipedia.org/wiki/Michael_Faraday
1赞 Francis Cugler 11/7/2018
我知道这个问题已经有大约一年的历史了,并且已经有一个公认的答案,我仍然想指出一些快速简单的事情,这些事情仍然可以帮助理解 3D 转换背后的一些数学原理。首先是简单使用开源和标题,只有C++库,你可以在这里找到它:glm.g-truc.net/0.9.9/index.html 和一个非常好的视频系列,涉及许多不同的数学领域,可能在这里找到:youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw/...。寻找线性代数系列!glm

答:

72赞 Spektre 1/22/2015 #1

所以问题实际上是理解 4x4 同次变换矩阵

好吧,如果没有背后的数学,唯一剩下的就是几何表示/意义,这对人类的抽象/理解要好得多。

  1. 那么什么是 4x4 矩阵呢?

    它是一些笛卡尔坐标系的表示,它由以下部分组成:

    1. 3 个基向量(每个轴一个) 红色、绿色、蓝色

      因此,如果红色、绿色、蓝色矢量彼此垂直,则坐标系是正交的。如果它们也是单位向量,那么它是正交的(例如单位矩阵)。

    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是变换矩阵
    • l是局部坐标系点 (LCS)M
    • g是全局坐标系点 (GCS)

    对于转置版本(DirectX),它是:

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

    那是因为转置的正交旋转矩阵本身也是逆

    OpenGL transform matrix

  2. 如何可视化它

    是的,您可以绘制矩阵数字,但乍一看它们没有意义,尤其是当数字发生变化时,因此请绘制轴向量,如上图所示。其中每个轴是从 到 的一条线originorigin + line_size*axis_vector

  3. 如何构建它

    只需计算轴向量和原点并将它们放入矩阵中即可。为确保正交性,请利用叉积(但要注意乘数的顺序以使用正确的方向) 这里是从方向获取 3 个基向量的示例

  4. 影响

    • 旋转是通过旋转轴来完成的,因此您可以通过参数圆方程计算每个轴......
    • 缩放是通过将轴乘以比例因子来完成的
    • 倾斜只是使用非垂直轴
  5. 旋转

    在大多数情况下,使用增量轮换。有两种类型

    • 局部旋转:它围绕局部坐标轴旋转,就像您将控制飞机、汽车或玩家一样......大多数引擎/游戏不使用这些,而是用欧拉角伪造它,这是一种廉价的解决方案(有很多怪癖和问题),因为大多数使用 OpenGL 的人甚至不知道这是可能的,而是堆叠调用列表......M'=M*rotation_matrixglRotate/glTranslate

    • 全局旋转 它围绕全局坐标系轴旋转M'=Inverse(Inverse(M)*rotation_matrix)

    其中 是任何标准旋转变换矩阵。rotation_matrix

    如果您有不同的矩阵布局(转置),则局部和全局旋转将以相反的方式计算......

    您还可以从以下角度计算您的:rotation_matrix3

    rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);
    

    请参阅 Wiki 旋转矩阵,3D 是您需要的。正如你所看到的,它们实际上只是单位圆参数方程。乘法顺序会改变角度收敛到目标位置的方式。这被称为欧拉角,我不使用它(我集成了阶跃变化,如果做得好,它没有任何限制,更不用说它更简单了)。Rx,Ry,RzBasic rotations

    无论如何,如果需要,您可以相对容易地将变换矩阵转换为欧拉角,请参阅:

  6. gl旋转

    如果您想要围绕任意轴旋转而不是旋转 3 个角度,那么有解决方法glRotate

    1. 为该轴创建变换矩阵N
    2. 然后将矩阵转换为它M
    3. 按角度旋转N
    4. 然后变换回全局坐标MN

    或者,您可以改用 Rodrigues_rotation_formula

    在这种情况下,要将 Matrix 转换为 Matrix/从 Matrix 转换,只需将轴转换为点并保持原点不变,但原点必须是 (0,0,0)!!或者转换后的向量必须具有。Nw=0

  7. 用法

    转换是累积的,这意味着:

    • p'=M1*M2*M3*M4*p;M=M1*M2*M3*M4; p'=M*p

    因此,如果您有很多点要变换,那么您将所有变换预先计算为单个矩阵并仅使用它。不需要将点乘以所有后续矩阵。好了,现在这个概念:

    您应该具有坐标系:3

    • 照相机C
    • 世界(通常为单位矩阵)
    • 对象(每个对象都有自己的矩阵)O

    因此,如果您有带有顶点的立方体,则必须对每个点执行从对象局部坐标到相机局部坐标的转换。一些 gfx api 会做一些工作,所以你只应用你必须做的事情,所以你真的需要:8p0,...,p7

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

    变换是累积的,单位矩阵不会改变任何内容,因此:

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

    因此,在绘制计算绘制对象之前,请获取对象的每个点并计算转换后并绘制/使用转换后的点......位于本地相机坐标系(屏幕的 x,y)中,但那里没有透视,因此在绘制之前,您还可以添加任何投影矩阵并在最后除以 cordinate ...投影也是累积的,因此它也可以在内部Qp(i)p(i)'p(i)'zQ

[编辑1]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] 通过键盘进行相机和对象控制的局部旋转

由于最近在这里被问了很多,所以我的一些示例答案与演示:

评论

1赞 Spektre 1/22/2015
@MichaelDibbets不,那是不对的 1.顶点仍然是顶点,您只需将其转换为同源坐标,因此 (x,y,z) 变为 (x,y,z,1),因此您可以将它和 4x4 矩阵相乘
1赞 Spektre 1/22/2015
@MichaelDibbets 2.矩阵代表你的对象(立方体)的坐标系,如果它 si 未平移未旋转,那么它是单位矩阵(1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1)经过一些运动,旋转它会变得不同。如果你想构造一个,那么在全球世界坐标系中取你的对象向量的轴,X轴,Y轴,Z轴,并用它们填充矩阵,然后取原点并填充它,最后在底部添加(0,0,0,1)。我使用物体的中点作为原点,轴是 Z 表示向前运动,Y 表示向上运动,X 表示右运动......
1赞 Spektre 1/22/2015
@MichaelDibbets大多数网格已经像您的立方体示例一样转换为 (0,0,0),因此不需要 point1 ,点 2 是使用单个变换矩阵在单个步骤中完成的(这会将对象平移并旋转到所需位置),点 3 不会转换回来,而是转换为相机视图坐标,这也可以在步骤 2 中完成。我将尝试用 C++ 为您制作工作示例......
1赞 Spektre 1/22/2015
@MichaelDibbets添加了C++示例并修复了局部/全局旋转被反转的一些东西,并且当前矩阵布局的相机/世界/对象转换累积顺序错误
3赞 Spektre 11/24/2015
@Dudeson (debuleted) 顺便说一句,如果您有兴趣,请参阅无处不在的要点