Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
953 views
in Technique[技术] by (71.8m points)

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

The one thing that has always hindered me from doing 3D programming is failing to understand how math works. I can go along with math fine in programming flow using methods and functions, then its all clear and logical to me, but in mathematical notation, I just can't make heads or tails from it.

I have been reading websites, a watching videos of institutes trying to explain this, but they all use mathematical notation and I simply get lost in it, my mind won't translate it to something understandable. I might have a defect there.

Also, Just using someone's code isn't my interest, I want to understand the mechanics behind it, the logic. I'd be happy to use someone else's code, but I really want to understand how it works.

The question

Can you explain to me in simple terms without mathematical notation, just programming notation/functions/psuedocode, how to implement a matrix transform along all 3 axes?

Ideally what I want is the material/understanding to write a method/object where I can define the angles of 3 axes similar to glRotate to rotate the collection of quads/triangles I have. (I am trying to program a 3D rotation of a cube shapes without having access to OpenGL functions to do it for me because this is done in one draw call every time something changes in the display list.)

What have I done?

I have attempted at making a 90 degrees transform function to get the hang of the math but failed utterly in making a proper matrix which in theory should have been the simplest to do. You can see my failed attempt in all its glory on 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>
Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

So the question really is Understanding 4x4 homogenous transform matrices

well without the math behind the only thing that left is geometric representation/meaning which is far better for human abstraction/understanding.

  1. So what the 4x4 matrix is?

    It is representation of some Cartesian coordinate system and it is composed of:

    1. 3 basis vectors (one for each axis) red,green,blue

      So if the red,green,blue vectors are perpendicular to each other then the coordinate system is orthogonal. If they are also unit vectors then it is orthonormal (like for example unit matrix).

    2. origin point gray

    3. projection and homogenous side (unmarked bottom rest of the matrix)

      This part is there only for enabling rotation and translation at once, therefore point used must be homogenous that means in form (x,y,z,w=1) for points and (x,y,z,w=0) for direction vectors. If it was just (x,y,z) then the matrix would be 3x3 and that is not enough for translation. I will not use any projections they are uneasy to explain geometrically.

    This layout is from OpenGL notation there are also transposed representation out there (vectors are rows not columns)

    now how to transform any point to/from this coordinate system:

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

    where:

    • M is transform matrix
    • l is M local coordinate system point (LCS)
    • g is global coordinate system point (GCS)

    for the transposed version (DirectX) it is:

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

    That is because transposed orthogonal rotation matrix is also inverse of itself

    OpenGL transform matrix

  2. how to visualize it

    Yes you can draw the matrix numbers but they do not make sense at first look especially if the numbers are changing so draw the axises vectors as on image above. Where each axis is a line from origin to origin + line_size*axis_vector

  3. how to construct it

    Just compute axis vectors and origin and put them inside matrix. To ensure orthogonality exploit cross product (but be careful with order of multiplicants to use the right direction) Here example of getting 3 basis vectors from direction

  4. effects

    • rotation is done by rotating the axises so you can compute each axis by parametric circle equation ...
    • scaling is done by multiplying axises by scale factor
    • skewing is just using non perpendicular axises
  5. rotation

    For most cases the incremental rotation is used. There are two types

    • local rotation M'=M*rotation_matrix it rotates around local coordinate axises like you will control plane or car or player ... Most engines/games do not use these and fake it with euler angles instead which is a cheap solution (have many quirks and problems) because most people who using OpenGL do not even know this is possible and rather stack list of glRotate/glTranslate calls...

    • global rotation M'=Inverse(Inverse(M)*rotation_matrix) it rotates around global coordinate system axises.

    where rotation_matrix is any standard rotation transform matrix.

    If you have different matrix layout (transposed) then the rotations local and global are computed the other way around ...

    You can also compute your rotation_matrix from 3 angles like:

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

    see Wiki rotation matrices the 3D Rx,Ry,Rz from Basic rotations are what you need. As you can see they are just unit circle parametric equation really. The order of multiplication change how the angles converge to target position. This is called Euler angles and I do not use it (I integrate step changes instead which has no restrictions if done properly not to mention it is simpler).

    Anyway if you need you can convert transform matrix into euler angles relatively easily see:

  6. glRotate

    If you want glRotate which is rotation around arbitrary axis not by 3 angles then There is workaround:

    1. create transform matrix N for that axis
    2. then transform your matrix M to it
    3. rotate N by angle
    4. then transform M back from N to global coordinates

    Or you can use Rodrigues_rotation_formula instead

    To transform Matrix to/from Matrix in this case just transform axises as points and leave the origin as is but the origin of N must be (0,0,0)!!! or the vectors transformed must have w=0 instead.

  7. usage

    Transformations are cumulative that means:

    • p'=M1*M2*M3*M4*p; is the same as M=M1*M2*M3*M4; p'=M*p

    So if you have many points to transform then you precompute all transformations to single matrix and use just it. Do not need to multiply points by all subsequent matrices. OK now the concept:

    you should have 3 coordinate systems:

    • camera C
    • world (usually unit matrix)
    • object O (each object have its own matrix)

    so if you have cube with 8 vertexes p0,...,p7 then you have to perform transformation on each point from object local coordinates to camera local coordinates. Some gfx api do some of it so you apply only what you have to so you really need:

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

    the transforms are cumulative and unit matrix does not change anything so:

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

    so before drawing compute Q for drawed object then take each point p(i) of the object and compute the transformed p(i)' and draw/use the transformed one ... The p(i)' is in local camera coordinate system (x,y of the screen) but there is no perspective there so before drawing you can also add any of the projection matrices and divide by z cordinate at the end ... The projection is also cumulative so it can be also inside Q

[edit1] C++ example

//$$---- 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)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...