如何从头开始构建具有人类可读角度的旋转矩阵?
How do I compose a rotation matrix with human readable angles from scratch?
一直阻碍我进行 3D 编程的一件事是无法理解数学是如何工作的。我可以在使用方法和函数的编程流程中很好地进行数学运算,然后对我来说一切都清晰且合乎逻辑,但是在数学符号中,我就是无法从中得出正面或反面。
我一直在浏览网站,观看试图解释这一点的研究所的视频,但它们都使用数学符号,我只是迷失在其中,我的思想不会将其转化为可以理解的东西。我可能那里有缺陷。
此外,仅仅使用别人的代码不是我的兴趣,我想了解它背后的机制,逻辑。我很乐意使用别人的代码,但我真的很想了解它是如何工作的。
问题
你能简单地给我解释一下吗没有数学符号,只是编程notation/functions/psuedocode,如何实现沿所有3个轴的矩阵变换?
理想情况下,我想要的是 material/understanding 编写一个 method/object,我可以在其中定义 3 个轴的角度,类似于 glRotate 以旋转我拥有的 quads/triangles 的集合。 (我正在尝试编写一个立方体形状的 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;">
</div>
绿色文本是原始三角形,白色点是中心点,红色点是失败的转换(我认为,因为它没有围绕中心点对齐)。我正在学习的教程让我想到如何将矩阵组合成一个组合矩阵,但我想我在某个地方搞砸了。
正如我所说,我真的很难理解数学符号和说话。没有帮助的是大多数老师跳过了部分解释。我花了 2 个小时才明白,在乘法矩阵时,你需要将每一步相加,而不是继续相乘。好的解释。
我工作的一个实际例子with/want
例如,我有一个立方体,它是从位于世界
的波前 obj 文件加载的
x = 50
y = 100
z = 200
立方体是使用四边形和一些 uv 映射绘制的。这里没有问题。它渲染精美,所有纹理都正确显示。
这些是使用四边形绘制的每个 "face" 立方体的位置坐标。
// 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
因此我给出的上述每个坐标都存储为组中波前对象的面"cube"。
当立方体被添加到曲面细分器时,它被转换到世界中的正确坐标并呈现正常。
然而,它始终呈现相同的效果。如果我想让它以一个角度渲染,我必须在此时制作一个单独的波前对象才能做到这一点。在我看来,当它可以用一些数学来解决时,这样做是疯狂的。
答案中需要
- 逐步解释如何构建翻译矩阵并尝试向我解释数学。
说明如何将平移矩阵应用到面部中的 quads/triangles,同时它们保持围绕其位置中心
x = 50.5
y = 100.5
z = 200.5
一些 example/pseudo 代码与解释一起进行。
用于解释的所用编程语言并不真正相关,只要它属于 C 系列
请尽量远离数学notation/speak。我不知道 alpha beta、thetha 是什么,我知道 x 轴、y 轴和 z 轴是什么。我知道什么是角,但我不知道数学家给它起的名字。
如果您想使用数学名称,请向我解释它们在 3D world/code 中是什么以及它们是如何 formed/calculated。
我只是想按照
的方式制作一个method/object
Matrix.transformVertices(vertices[], 90deg x, 45 deg y, 0 deg z);
所以真正的问题是理解 4x4 齐次变换矩阵
好吧,没有背后的数学,唯一剩下的就是几何 representation/meaning,这对人类来说要好得多 abstraction/understanding。
那么4x4矩阵是什么?
它是一些笛卡尔坐标系的表示,它由以下部分组成:
3
基向量(每个轴一个)红、绿、蓝
因此,如果红、绿、蓝向量相互垂直,则坐标系是正交。如果它们也是单位向量,那么它是 正交 (例如单位矩阵)。
原点灰色
投影和同质边(矩阵底部未标记的其余部分)
此部分仅用于同时启用旋转和平移,因此使用的点必须是同质的,这意味着点的形式为 (x,y,z,w=1)
,方向向量的形式为 (x,y,z,w=0)
。如果它只是 (x,y,z)
,那么矩阵将是 3x3
,这不足以进行翻译。我不会使用任何难以从几何学上解释的投影。
此布局来自 OpenGL 表示法 还有转置表示法(矢量是行而不是列)
现在如何变换任意点to/from这个坐标系:
g=M*l;
l=Inverse(M)*g;
其中:
M
是变换矩阵
l
是M
局部坐标系点(LCS)
g
为全球坐标系点(GCS)
对于转置版本 (DirectX) 是:
l=M*g;
g=Inverse(M)*l;
那是因为转置正交旋转矩阵本身也是逆矩阵
- 有关详细信息,请参阅 transform matrix anatomy and 3D graphic pipeline
如何形象化
是的,你可以画出矩阵数字,但乍一看它们没有意义,尤其是当数字在变化的时候,所以画出轴向量,如上图所示。其中每个轴是从 origin
到 origin + line_size*axis_vector
的一条线
如何构建
只需计算轴向量和原点并将它们放在矩阵中。为确保正交性利用 叉积 (但要注意乘数的顺序以使用正确的方向)此处
效果
- 旋转是通过旋转轴完成的,因此您可以通过参数圆方程计算每个轴...
- 缩放是通过将轴乘以比例因子来完成的
- 倾斜只是使用非垂直轴
旋转
大多数情况下使用增量旋转。有两种
局部旋转 M'=M*rotation_matrix
它围绕局部坐标轴旋转,就像你将控制飞机或汽车或玩家......大多数engines/games 不要使用这些并用 euler angles 来伪造它,而是一个廉价的解决方案(有很多怪癖和问题),因为大多数使用 OpenGL 的人甚至不知道这是可能的,而是堆栈glRotate/glTranslate
个调用列表...
全局旋转 M'=Inverse(Inverse(M)*rotation_matrix)
它围绕全局坐标系轴旋转。
其中 rotation_matrix
是任何标准旋转变换矩阵。
如果您有不同的矩阵布局(转置),则局部和全局旋转将以相反的方式计算...
您还可以从 3
角度计算您的 rotation_matrix
,例如:
rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);
请参阅 Wiki rotation matrices Basic rotations
中的 3D Rx,Ry,Rz
是您所需要的。如您所见,它们实际上只是单位圆参数方程。乘法的顺序改变了角度如何收敛到目标位置。这叫做 Euler angles,我不使用它(我集成了步进变化,如果做得好没有限制,更不用说它更简单了)。
无论如何,如果您需要,您可以相对轻松地将变换矩阵转换为欧拉角,请参阅:
glRotate
如果你想要 glRotate
绕任意轴旋转而不是 3 个角度那么 There is workaround:
- 为该轴
创建变换矩阵N
- 然后将矩阵
M
转换为它
- 旋转
N
角度
- 然后将
M
从 N
转换回全局坐标
或者您可以使用Rodrigues_rotation_formula代替
要转换 Matrix to/from Matrix 在这种情况下只需将轴转换为点并保持原点不变但 N
的原点必须是 (0,0,0 )!!! 或转换后的向量必须有 w=0
而不是。
用法
转换是累积的,这意味着:
p'=M1*M2*M3*M4*p;
等同于 M=M1*M2*M3*M4; p'=M*p
因此,如果您有很多点需要变换,那么您可以将所有变换预先计算为单个矩阵并仅使用它。不需要将点乘以所有后续矩阵。 OK现在的概念:
你应该有3
个坐标系:
- 相机
C
- 世界(通常是单位矩阵)
- 对象
O
(每个对象都有自己的矩阵)
因此,如果您的立方体具有 8
个顶点 p0,...,p7
,那么您必须对每个点执行从对象局部坐标到相机局部坐标的转换。一些 gfx api 做了一些,所以你只应用你真正需要的东西:
p(i)'=inverse(C)*unit*M*p(i);
变换是累积的,单位矩阵不会改变任何东西,所以:
Q=inverse(C)*M; p(i)'=Q*p(i);
所以在为绘制的对象绘制计算 Q
然后取对象的每个点 p(i)
并计算转换后的 p(i)'
和 draw/use 转换后的点 ... p(i)'
位于本地相机坐标系(屏幕的 x,y),但那里没有透视图,因此在绘制之前,您还可以添加任何投影矩阵并在末尾除以 z
坐标...投影也是累积的,所以它也可以在 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();
}
//---------------------------------------------------------------------------
这是它的样子:
以及带有背面剔除的 GIF 动画:
[备注]
如果您有更多问题,请评论我...
[Edit2] 经常需要的基本 3D 矢量运算
如果您不知道如何计算矢量运算,例如 cross/dot 乘积或绝对值,请参阅:
// 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] 通过键盘进行相机和对象控制的局部旋转
由于最近在这里被问了很多,我的一些示例答案和演示:
- stationary camera view control (partial pseudo inverse matrix)
- camera and player control (inverse matrix)
一直阻碍我进行 3D 编程的一件事是无法理解数学是如何工作的。我可以在使用方法和函数的编程流程中很好地进行数学运算,然后对我来说一切都清晰且合乎逻辑,但是在数学符号中,我就是无法从中得出正面或反面。
我一直在浏览网站,观看试图解释这一点的研究所的视频,但它们都使用数学符号,我只是迷失在其中,我的思想不会将其转化为可以理解的东西。我可能那里有缺陷。
此外,仅仅使用别人的代码不是我的兴趣,我想了解它背后的机制,逻辑。我很乐意使用别人的代码,但我真的很想了解它是如何工作的。
问题
你能简单地给我解释一下吗没有数学符号,只是编程notation/functions/psuedocode,如何实现沿所有3个轴的矩阵变换?
理想情况下,我想要的是 material/understanding 编写一个 method/object,我可以在其中定义 3 个轴的角度,类似于 glRotate 以旋转我拥有的 quads/triangles 的集合。 (我正在尝试编写一个立方体形状的 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;">
</div>
绿色文本是原始三角形,白色点是中心点,红色点是失败的转换(我认为,因为它没有围绕中心点对齐)。我正在学习的教程让我想到如何将矩阵组合成一个组合矩阵,但我想我在某个地方搞砸了。
正如我所说,我真的很难理解数学符号和说话。没有帮助的是大多数老师跳过了部分解释。我花了 2 个小时才明白,在乘法矩阵时,你需要将每一步相加,而不是继续相乘。好的解释。
我工作的一个实际例子with/want
例如,我有一个立方体,它是从位于世界
的波前 obj 文件加载的x = 50
y = 100
z = 200
立方体是使用四边形和一些 uv 映射绘制的。这里没有问题。它渲染精美,所有纹理都正确显示。
这些是使用四边形绘制的每个 "face" 立方体的位置坐标。
// 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
因此我给出的上述每个坐标都存储为组中波前对象的面"cube"。
当立方体被添加到曲面细分器时,它被转换到世界中的正确坐标并呈现正常。
然而,它始终呈现相同的效果。如果我想让它以一个角度渲染,我必须在此时制作一个单独的波前对象才能做到这一点。在我看来,当它可以用一些数学来解决时,这样做是疯狂的。
答案中需要
- 逐步解释如何构建翻译矩阵并尝试向我解释数学。
说明如何将平移矩阵应用到面部中的 quads/triangles,同时它们保持围绕其位置中心
x = 50.5 y = 100.5 z = 200.5
一些 example/pseudo 代码与解释一起进行。
用于解释的所用编程语言并不真正相关,只要它属于 C 系列
请尽量远离数学notation/speak。我不知道 alpha beta、thetha 是什么,我知道 x 轴、y 轴和 z 轴是什么。我知道什么是角,但我不知道数学家给它起的名字。
如果您想使用数学名称,请向我解释它们在 3D world/code 中是什么以及它们是如何 formed/calculated。
我只是想按照
的方式制作一个method/objectMatrix.transformVertices(vertices[], 90deg x, 45 deg y, 0 deg z);
所以真正的问题是理解 4x4 齐次变换矩阵
好吧,没有背后的数学,唯一剩下的就是几何 representation/meaning,这对人类来说要好得多 abstraction/understanding。
那么4x4矩阵是什么?
它是一些笛卡尔坐标系的表示,它由以下部分组成:
3
基向量(每个轴一个)红、绿、蓝因此,如果红、绿、蓝向量相互垂直,则坐标系是正交。如果它们也是单位向量,那么它是 正交 (例如单位矩阵)。
原点灰色
投影和同质边(矩阵底部未标记的其余部分)
此部分仅用于同时启用旋转和平移,因此使用的点必须是同质的,这意味着点的形式为
(x,y,z,w=1)
,方向向量的形式为(x,y,z,w=0)
。如果它只是(x,y,z)
,那么矩阵将是3x3
,这不足以进行翻译。我不会使用任何难以从几何学上解释的投影。
此布局来自 OpenGL 表示法 还有转置表示法(矢量是行而不是列)
现在如何变换任意点to/from这个坐标系:
g=M*l; l=Inverse(M)*g;
其中:
M
是变换矩阵l
是M
局部坐标系点(LCS)g
为全球坐标系点(GCS)
对于转置版本 (DirectX) 是:
l=M*g; g=Inverse(M)*l;
那是因为转置正交旋转矩阵本身也是逆矩阵
- 有关详细信息,请参阅 transform matrix anatomy and 3D graphic pipeline
如何形象化
是的,你可以画出矩阵数字,但乍一看它们没有意义,尤其是当数字在变化的时候,所以画出轴向量,如上图所示。其中每个轴是从
的一条线origin
到origin + line_size*axis_vector
如何构建
只需计算轴向量和原点并将它们放在矩阵中。为确保正交性利用 叉积 (但要注意乘数的顺序以使用正确的方向)此处
效果
- 旋转是通过旋转轴完成的,因此您可以通过参数圆方程计算每个轴...
- 缩放是通过将轴乘以比例因子来完成的
- 倾斜只是使用非垂直轴
旋转
大多数情况下使用增量旋转。有两种
局部旋转
M'=M*rotation_matrix
它围绕局部坐标轴旋转,就像你将控制飞机或汽车或玩家......大多数engines/games 不要使用这些并用 euler angles 来伪造它,而是一个廉价的解决方案(有很多怪癖和问题),因为大多数使用 OpenGL 的人甚至不知道这是可能的,而是堆栈glRotate/glTranslate
个调用列表...全局旋转
M'=Inverse(Inverse(M)*rotation_matrix)
它围绕全局坐标系轴旋转。
其中
rotation_matrix
是任何标准旋转变换矩阵。如果您有不同的矩阵布局(转置),则局部和全局旋转将以相反的方式计算...
您还可以从
3
角度计算您的rotation_matrix
,例如:rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);
请参阅 Wiki rotation matrices
Basic rotations
中的 3DRx,Ry,Rz
是您所需要的。如您所见,它们实际上只是单位圆参数方程。乘法的顺序改变了角度如何收敛到目标位置。这叫做 Euler angles,我不使用它(我集成了步进变化,如果做得好没有限制,更不用说它更简单了)。无论如何,如果您需要,您可以相对轻松地将变换矩阵转换为欧拉角,请参阅:
glRotate
如果你想要
glRotate
绕任意轴旋转而不是 3 个角度那么 There is workaround:- 为该轴 创建变换矩阵
- 然后将矩阵
M
转换为它 - 旋转
N
角度 - 然后将
M
从N
转换回全局坐标
N
或者您可以使用Rodrigues_rotation_formula代替
要转换 Matrix to/from Matrix 在这种情况下只需将轴转换为点并保持原点不变但
N
的原点必须是 (0,0,0 )!!! 或转换后的向量必须有w=0
而不是。用法
转换是累积的,这意味着:
p'=M1*M2*M3*M4*p;
等同于M=M1*M2*M3*M4; p'=M*p
因此,如果您有很多点需要变换,那么您可以将所有变换预先计算为单个矩阵并仅使用它。不需要将点乘以所有后续矩阵。 OK现在的概念:
你应该有
3
个坐标系:- 相机
C
- 世界(通常是单位矩阵)
- 对象
O
(每个对象都有自己的矩阵)
因此,如果您的立方体具有
8
个顶点p0,...,p7
,那么您必须对每个点执行从对象局部坐标到相机局部坐标的转换。一些 gfx api 做了一些,所以你只应用你真正需要的东西:p(i)'=inverse(C)*unit*M*p(i);
变换是累积的,单位矩阵不会改变任何东西,所以:
Q=inverse(C)*M; p(i)'=Q*p(i);
所以在为绘制的对象绘制计算
内Q
然后取对象的每个点p(i)
并计算转换后的p(i)'
和 draw/use 转换后的点 ...p(i)'
位于本地相机坐标系(屏幕的 x,y),但那里没有透视图,因此在绘制之前,您还可以添加任何投影矩阵并在末尾除以z
坐标...投影也是累积的,所以它也可以在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();
}
//---------------------------------------------------------------------------
这是它的样子:
以及带有背面剔除的 GIF 动画:
[备注]
如果您有更多问题,请评论我...
[Edit2] 经常需要的基本 3D 矢量运算
如果您不知道如何计算矢量运算,例如 cross/dot 乘积或绝对值,请参阅:
// 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] 通过键盘进行相机和对象控制的局部旋转
由于最近在这里被问了很多,我的一些示例答案和演示:
- stationary camera view control (partial pseudo inverse matrix)
- camera and player control (inverse matrix)