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

Единственное, что всегда мешало мне заниматься 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 часа, чтобы понять, при умножении матриц нужно сложить каждый шаг, а не просто продолжать умножение. Yay для объяснений.

Практический пример того, с чем я работаю / хочу работать

Например, у меня есть куб, загруженный из obj-файла wavefront, расположенного в мире по адресу

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

Способ хранения данных выглядит следующим образом:

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

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

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

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

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

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

    х = 50,5, у = 100,5, з = 200,5

  3. Некоторый пример / псевдокод, чтобы согласиться с объяснением.

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

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

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

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

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

1 ответ

Решение

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

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

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

Это представление некоторой декартовой системы координат, и оно состоит из:

  1. 3базисные векторы (по одному на каждую ось) красный, зеленый, синий

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

  2. точка начала серая

  3. проекция и однородная сторона (немаркированный нижний остаток матрицы)

    Эта часть предназначена только для одновременного вращения и перемещения, поэтому используемая точка должна быть однородной, что означает по форме (x,y,z,w=1), Если бы это было просто (x,y,z) тогда матрица будет 3x3 и этого недостаточно для перевода. Я не буду использовать какие-либо прогнозы, которые их нелегко объяснить геометрически.

Этот макет из нотации OpenGL, там также есть транспонированное представление (векторы - это строки, а не столбцы)

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

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

где:

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

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

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

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

Матрица преобразования OpenGL

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 к глобальным координатам

Или вы можете использовать https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula вместо

Чтобы преобразовать Матрицу в / из Матрицы в этом случае, просто преобразуйте оси как точки и оставьте начало координат как есть, но начало координат N должно быть (0,0,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();
    }
//---------------------------------------------------------------------------

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

пример куба

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

анимация

[заметки]

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

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

Так как в последнее время это часто спрашивают, вот несколько примеров моих ответов с демонстрациями:

Другие вопросы по тегам