Как составить матрицу вращения с человеком читаемые углы с нуля

opengl math matrix 3d


Единственное,что всегда мешало мне заниматься программированием в 3D-это непонимание того,как работает математика.Я могу прекрасно справиться с математикой в потоке программирования,используя методы и функции,то все это для меня понятно и логично,но в математической нотации я просто не могу сделать из этого ни головы,ни хвосты.

Я читал сайты,смотрел видео с институтами,пытающимися объяснить это,но все они используют математическую нотацию,и я просто теряюсь в этом,мой разум не хочет переводить это на что-то понятное.Возможно,у меня там есть дефект.

Также,просто использование чьего-то кода меня не интересует,я хочу понять механику,лежащую за этим,логику.Я был бы счастлив использовать чужой код,но я действительно хочу понять,как он работает.

Вопрос

Можете ли вы объяснить мне в простых терминах без математической нотации, просто программируя нотацию / функции / psuedocode, как реализовать матричное преобразование по всем 3 осям?

В идеале я хочу материал/понимание,чтобы написать метод/объект,где я могу определить углы 3 осей,похожие на glRotate,чтобы повернуть коллекцию квадрицепсов/треугольников,которые у меня есть.(Я пытаюсь запрограммировать трехмерное вращение кубических фигур,не имея доступа к функциям 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

Куб нарисован с помощью квадрицепсов и некоторой ультрафиолетовой картинки.Здесь нет проблем.Он прекрасно рендерит со всеми текстурами,показывая их правильно.

Это координаты местоположения каждой "грани" куба,которая нарисована с помощью квадранта.

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

Так что все работает отлично.Но что,если я хочу,чтобы этот куб повернулся на 90 градусов вдоль оси х и на 45 градусов вокруг оси z? Я не могу использовать glRotate,потому что в тот момент,когда я передаю данные объекту тесселятора,я не могу делать никаких матричных преобразований к нему через функции opengl,потому что он просто принимает данные,а не рендерит их как таковые.

Способ хранения данных следующий:

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

Таким образом,каждая из приведенных мною координат хранится как лицо объекта волнового фронта в группе "куб".

Когда куб добавляется к тесселятору,он переводится в нужные координаты в мире и становится нормальным.

Тем не менее,он всегда делает одно и то же.Если бы я хотел,чтобы он рендерил под углом,мне пришлось бы сделать отдельный объект волнового фронта в данный момент,чтобы иметь возможность сделать это.В моем opnion это безумие делать,когда это может быть решена с помощью некоторой математики.

Нужно в ответе

  1. Объяснение шаг за шагом,как построить матрицу перевода и попытка объяснить мне математику.
  2. Пояснение,как применить матрицу перевода к квадрантам/треугольникам в лицах,которые они продолжают ориентироваться вокруг центра своего местоположения.

    x=50.5 y=100.5 z=200.5

  3. Какой-нибудь пример/псевдокод,который можно использовать вместе с объяснением.

Использованный язык программирования,используемый для объяснения,на самом деле не актуален,пока он в семействе C

Пожалуйста,постарайтесь держаться подальше от математической нотации/речи.Я не знаю,что такое альфа-бета,тета,я знаю,что такое ось х,ось у и ось z.Я знаю,что такое углы,но не знаю,какие имена найдут для этого математики.

Если вы хотите использовать математические имена,пожалуйста,объясните мне,что они собой представляют в 3D мире/коде и как они формируются/вычисляются.

Я просто хочу сделать метод/объект по аналогии с

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



Answer 1 Spektre


Таким образом, вопрос на самом деле заключается в понимании однородных матриц преобразования 4x4

без математики остаётся только геометрическое представление/средства,что гораздо лучше для человеческой абстракции/понимания.

1. Так что же такое матрица 4х4?

Она представляет собой представление некоторой декартовой системы координат и состоит из нее:

  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;

where:

  • M - матрица преобразования
  • l - точка M локальной системы координат (LCS)
  • g - точка глобальной системы координат (GCS)

для транспонированной версии ( DirectX ) это:

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

Это связано с тем, что транспонированная матрица ортогонального вращения также является обратной

OpenGL transform matrix

2. как это визуализировать

Да, вы можете нарисовать матричные числа, но на первый взгляд они не имеют смысла, особенно если числа меняются, поэтому нарисуйте векторы осей, как на изображении выше. Где каждая ось является линией от origin до origin + line_size*axis_vector

3. как его построить

Просто рассчитайте векторы оси и начало координат и поместите их в матрицу. Для обеспечения ортогональности использовать перекрестное произведение (но будьте осторожны с порядком мультипликаторов, чтобы использовать правильное направление)

4. эффекты

  • Вращение осуществляется путем поворота осей,так что можно вычислить каждую ось по уравнению параметрической окружности ...
  • масштабирование осуществляется путем умножения осей на коэффициент масштабирования
  • Перекос-это просто использование не перпендикулярных осей.

5. вращение

В большинстве случаев используется инкрементальное вращение.Существует два типа

  • локальное вращение M'=M*rotation_matrix вращается вокруг локальных координатных осей, как если бы вы управляли самолетом, автомобилем или игроком ... Большинство движков / игр не используют их и вместо этого имитируют углы Эйлера, что является дешевым решением (имеет много причуд и проблемы), потому что большинство людей, которые используют OpenGL, даже не знают, что это возможно, и скорее составляют список glRotate/glTranslate ...

  • глобальное вращение M'=Inverse(Inverse(M)*rotation_matrix) вращается вокруг осей глобальной системы координат.

где rotation_matrix - любая стандартная матрица преобразования вращения.

Если у вас разная матричная раскладка (транспонирование),то вращения локальные и глобальные вычисляются наоборот ...

Вы также можете вычислить ваш rotation_matrix из 3 углов, например:

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

см. вики-матрицы поворотов, 3D Rx,Ry,Rz из Basic rotations - это то, что вам нужно. Как вы можете видеть, они просто являются параметрическим уравнением единичного круга. Порядок умножения меняет то, как углы сходятся к заданному положению. Это называется углами Эйлера, и я не использую его (вместо этого я интегрирую пошаговые изменения, которые не имеют ограничений, если все сделано правильно, не говоря уже о том, что это проще).

В любом случае,если вам нужно,вы можете преобразовать матрицу преобразования в эйлер углы относительно легко увидеть:

6. glRotate

Если вы хотите glRotate , вам следует использовать кватернионы, потому что это вращение вокруг оси, а не на 3 угла! Есть обходной путь:

  1. создать матрицу преобразования N для этой оси
  2. затем преобразуйте свою матрицу M в нее
  3. повернуть N на угол
  4. затем преобразовать M обратно из N в глобальные координаты

Или вы можете использовать Rodrigues_rotation_formula вместо

Чтобы преобразовать Матрицу в / из Матрицы в этом случае, просто преобразуйте оси как точки и оставьте начало координат как есть, но начало N должно быть (0,0,0) !!! или преобразованные векторы должны иметь w=0 .

7. использование

Трансформации являются кумулятивными,что означает:

  • p'=M1*M2*M3*M4*p; такой же, как M=M1*M2*M3*M4; p'=M*p

Таким образом,если у вас много точек для преобразования,то вы прекомпилируете все преобразования в единую матрицу и используете только ее.Не нужно умножать точки на все последующие матрицы.Хорошо,теперь концепция:

у вас должно быть 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)' и нарисуйте / используйте преобразованную точку ... 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();
    }
//---------------------------------------------------------------------------

вот как это выглядит:

cube example

И анимация GIF с отбраковкой лица назад:

animation

[notes]

Если у вас есть еще вопросы,то прокомментируйте меня...

[Edit2] часто нужны базовые трехмерные векторные операции

Если вы не знаете,как вычислять векторные операции,такие как кросс/точечные продукты или абсолютное значение,см:

// 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] локальные повороты для управления камерой и объектом с клавиатуры

Так как в последнее время об этом много спрашивали,то здесь есть несколько примеров моих ответов с демо-версиями: