¿Cómo compongo una matriz de rotación con ángulos legibles para los humanos desde cero

opengl math matrix 3d


La única cosa que siempre me ha impedido hacer programación en 3D es no entender cómo funcionan las matemáticas.Puedo seguir la corriente de las matemáticas en la programación del flujo usando métodos y funciones,entonces todo es claro y lógico para mí,pero en la notación matemática,no puedo hacer cara o cruz con ello.

He estado leyendo sitios web,viendo videos de institutos tratando de explicar esto,pero todos usan notación matemática y simplemente me pierdo en ella,mi mente no la traduce a algo comprensible.Podría tener un defecto allí.

Además,usar el código de alguien no me interesa,quiero entender la mecánica detrás de esto,la lógica.Estaría feliz de usar el código de alguien más,pero realmente quiero entender cómo funciona.

La pregunta

¿Puede explicarme en términos simples sin notación matemática, solo notando programación / funciones / psuedocódigo, cómo implementar una transformación matricial a lo largo de los 3 ejes?

Idealmente lo que quiero es el material/entendimiento para escribir un método/objeto donde pueda definir los ángulos de 3 ejes similares a glRotate para rotar la colección de cuadrantes/trángulos que tengo.(Estoy tratando de programar una rotación 3D de las formas de un cubo sin tener acceso a las funciones de OpenGL para hacerlo por mí porque esto se hace en una llamada de dibujo cada vez que algo cambia en la lista de visualización).

¿Qué he hecho?

Intenté hacer una función de transformación de 90 grados para dominar las matemáticas, pero fracasé por completo en hacer una matriz adecuada, que en teoría debería haber sido la más simple de hacer. Puede ver mi intento fallido en toda su gloria en 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>

El texto verde es el triángulo original,el punto blanco el punto central,el rojo la transformación fallida (creo,porque no está alineado alrededor del punto central).El tutorial en el que estaba pensaba en cómo combinar matrices en una matriz combinada,pero creo que la he fastidiado en algún sitio.

Como dije,es muy difícil para mí entender la notación matemática y hablar.Y no ayuda el hecho de que la mayoría de los profesores se salten partes de la explicación.Me tomó 2 horas a solas para entender que cuando se multiplican matrices hay que sumar cada paso en lugar de seguir multiplicando.Yay por las explicaciones.

Un ejemplo práctico con lo que trabajo / quiero trabajar

Por ejemplo,tengo un cubo,cargado desde un archivo de objetos de frente de onda localizado en el mundo en

x = 50
y = 100
z = 200

El cubo se dibuja usando cuádriceps y algunos mapas UV.No hay problemas aquí.Se ve muy bien con todas las texturas mostradas correctamente.

Estas son las coordenadas de ubicación de cada "cara" del cubo que se dibuja usando un cuadrante.

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

Así que todo esto funciona muy bien.¿Pero qué pasa si quiero que este cubo gire 90 grados a lo largo del eje X y 45 grados alrededor del eje Z? No puedo usar glRotate porque en el momento en que paso los datos al objeto teselador no puedo hacer ninguna transformación matricial a través de las funciones de opengl porque sólo está tomando los datos,no los está renderizando en sí.

La forma en que se almacenan los datos es la siguiente:

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

Así que cada una de las coordenadas anteriores que di está almacenada como una cara del objeto de frente de onda en el grupo "cubo".

Cuando el cubo se añade al teselador se traduce a las coordenadas correctas del mundo y se vuelve normal.

Sin embargo,siempre hace lo mismo.Si quisiera que se renderizara en un ángulo tendría que hacer un objeto de frente de onda separado en este momento para poder hacerlo.En mi opinión,es una locura hacerlo cuando puede ser resuelto con algo de matemáticas.

Necesario en la respuesta

  1. Explicación paso a paso de cómo construir una matriz de traducción y un intento de explicarme las matemáticas.
  2. Explicación de cómo aplicar la matriz de traducción a los cuadriláteros/trángulos en las caras que se mantienen orientadas alrededor del centro de su ubicación

    x=50,5 y=100,5 z=200,5

  3. Algún ejemplo/pseudo código para acompañar la explicación.

El lenguaje de programación usado para explicar no es realmente relevante mientras esté en la familia C

Por favor,intente mantenerse alejado de la notación/habla matemática.No sé qué es el alfa beta,el thetha,pero sí sé qué es el eje x,el eje y y el eje z.Sé lo que son los ángulos,pero no sé los nombres que los matemáticos encuentran para ello.

Si desea utilizar nombres matemáticos,por favor explíqueme qué son en el mundo/código 3D y cómo se forman/calculan.

Simplemente quiero hacer un método/objeto a lo largo de las líneas de

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



Answer 1 Spektre


Entonces, la pregunta realmente es comprender matrices de transformación homogéneas 4x4

Bueno,sin la matemática detrás de la única cosa que queda es la representación/significado geométrico que es mucho mejor para la abstracción/entendimiento humano.

1. Entonces, ¿qué es la matriz 4x4?

Es la representación de algún sistema de coordenadas cartesianas y está compuesto por:

  1. 3 vectores básicos (uno para cada eje) rojo, verde, azul

    Entonces, si los vectores rojo, verde y azul son perpendiculares entre sí, entonces el sistema de coordenadas es ortogonal . Si también son vectores unitarios, entonces es ortonormal (como, por ejemplo, la matriz unitaria).

  2. punto de origen gris

  3. proyección y lado homogéneo (resto inferior sin marcar de la matriz)

    Esta parte solo está ahí para permitir la rotación y la traslación a la vez, por lo tanto, el punto utilizado debe ser homogéneo, lo que significa en forma (x,y,z,w=1) para los puntos y (x,y,z,w=0) para la dirección vectores Si fuera solo (x,y,z) entonces la matriz sería 3x3 y eso no es suficiente para la traducción. No utilizaré ninguna proyección que sea difícil de explicar geométricamente.

Este diseño es de notación OpenGL, también hay representación transpuesta (los vectores son filas, no columnas)

ahora cómo transformar cualquier punto hacia/desde este sistema de coordenadas:

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

where:

  • M es matriz de transformación
  • l es M punto de sistema de coordenadas local (LCS)
  • g es un punto del sistema de coordenadas global (GCS)

para la versión transpuesta ( DirectX ) es:

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

Esto se debe a que la matriz de rotación ortogonal transpuesta también es inversa de sí misma

OpenGL transform matrix

2. cómo visualizarlo

Sí, puede dibujar los números de la matriz, pero no tienen sentido a primera vista, especialmente si los números están cambiando, así que dibuje los vectores axises como en la imagen de arriba. Donde cada eje es una línea desde el origin hasta el origin + line_size*axis_vector

3. cómo construirlo

Simplemente calcule los vectores de eje y el origen y colóquelos dentro de la matriz. Para garantizar la ortogonalidad, explote el producto cruzado (pero tenga cuidado con el orden de los multiplicantes para usar la dirección correcta)

4. efectos

  • la rotación se hace rotando los ejes para que puedas calcular cada eje por la ecuación del círculo paramétrico...
  • La escalada se hace multiplicando los ejes por el factor de escala
  • El sesgo es sólo el uso de ejes no perpendiculares

5. rotación

En la mayoría de los casos se utiliza la rotación incremental.Hay dos tipos

  • rotación local M'=M*rotation_matrix gira alrededor de los ejes de coordenadas locales como si controlaras el avión, el automóvil o el jugador ... La mayoría de los motores / juegos no los usan y lo falsifican con ángulos de Euler, lo cual es una solución barata (tienen muchas peculiaridades y problemas) porque la mayoría de las personas que usan OpenGL ni siquiera saben que esto es posible y más bien apilan la lista de llamadas glRotate/glTranslate ...

  • rotación global M'=Inverse(Inverse(M)*rotation_matrix) matrix_ rotación) gira alrededor de los ejes del sistema de coordenadas global.

donde rotation_matrix es cualquier matriz de transformación de rotación estándar.

Si tienes un diseño de matriz diferente (transpuesto)entonces las rotaciones local y global se calculan al revés...

También puede calcular su rotation_matrix desde 3 ángulos como:

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

ver las matrices de rotación Wiki el 3D Rx,Ry,Rz de las Basic rotations son lo que necesitas. Como puede ver, en realidad son ecuaciones paramétricas de círculo unitario. El orden de multiplicación cambia la forma en que los ángulos convergen a la posición objetivo. Esto se llama ángulos de Euler y no lo uso (integro los cambios de paso en su lugar, que no tienen restricciones si se hace correctamente, sin mencionar que es más simple).

De todas formas,si lo necesitas puedes convertir la matriz de transformación en ángulos de euler relativamente fácil de ver:

6. glRotate

Si quieres glRotate , entonces debes usar quaternions en su lugar porque ¡eso es rotación alrededor del eje, no en 3 ángulos! Hay una solución alternativa:

  1. crear matriz de transformación N para ese eje
  2. luego transforma tu matriz M en ella
  3. girar N por ángulo
  4. luego transforma M de N a coordenadas globales

O puede usar Rodrigues_rotation_formula en su lugar

Para transformar Matrix a / de Matrix en este caso, simplemente transforme los ejes como puntos y deje el origen como está, ¡pero el origen de N debe ser (0,0,0)! o los vectores transformados deben tener w=0 en su lugar.

7. uso

Las transformaciones son acumulativas,es decir:

  • p'=M1*M2*M3*M4*p; es lo mismo que M=M1*M2*M3*M4; p'=M*p

Así que si tienes muchos puntos que transformar,entonces precalculas todas las transformaciones a una sola matriz y la usas sólo.No es necesario multiplicar los puntos por todas las matrices subsiguientes.Bien,ahora el concepto:

debes tener 3 sistemas de coordenadas:

  • cámara C
  • mundo (normalmente una matriz de unidades)
  • objeto O (cada objeto tiene su propia matriz)

así que si tiene un cubo con 8 vértices p0,...,p7 , entonces debe realizar la transformación en cada punto desde las coordenadas locales del objeto a las coordenadas locales de la cámara. Algunos gfx api hacen algo de eso, por lo que aplica solo lo que tiene que realmente necesita:

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

las transformaciones son acumulativas y la matriz de unidades no cambia nada de eso:

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

así que antes de dibujar el cálculo Q para el objeto dibujado , tome cada punto p(i) del objeto y calcule el p(i)' transformado y dibuje / use el transformado ... El p(i)' está en el sistema de coordenadas de la cámara local (x, y de la pantalla) pero no hay una perspectiva allí, así que antes de dibujar también puede agregar cualquiera de las matrices de proyección y dividir por z cordinate al final ... La proyección también es acumulativa, por lo que también puede estar dentro de Q

[edit1] Ejemplo de 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();
    }
//---------------------------------------------------------------------------

así es como se ve:

cube example

Y la animación GIF con la selección de la cara posterior:

animation

[notes]

Si tiene más preguntas,entonces coméntelas...

[Edit2] operaciones básicas de vectores 3D a menudo necesarias

Si no sabes cómo calcular las operaciones vectoriales como los productos cruzados/puntos o el valor absoluto,mira:

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

aquí mi matemática de vectores 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] rotaciones locales para el control de cámara y objetos a través del teclado

Como esto se ha preguntado mucho últimamente aquí algunos ejemplos de respuestas mías con demostraciones: