三维空间里的简单的长方体透视变换

    这次的内容是接着yogurt上一篇《二维空间里的简单矩形变换》(http://www.cnblogs.com/to-sunshine/p/6496697.html)继续来讲图形的变化问题。其实现在有很多现成的库可以用于画图,比较牛的就有opencv、opengl等,实在感兴趣的人可以去仔细研究一下。当然和这些现成库比起来,yogurt用C语言码的三维透视变化就弱爆啦,不过没关系,主要是为了弄懂其中的变换原理嘛~~

    好啦,啰嗦的话yogurt就不多说了,最近在W3school上面自学Java和CSS有点儿心累,不过还是要给大家良心推荐这个学习网站,真心不错哦!

// 3Dchange.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include"Graph.h"
#include"math.h"
#define PI 3.1415926

typedef double matrix[4][4];

typedef struct
{
    double x;
    double y;
    double z;
}point;

//叉乘
void Cross_Product(double pa[3], double pb[3], double pc[3])
{
    pc[0] = pa[1] * pb[2] - pa[2] * pb[1];
    pc[1] = pa[2] * pb[0] - pa[0] * pb[2];
    pc[2] = pa[0] * pb[1] - pa[1] * pb[0];

    return;
}

//点乘
double Dot_Product(double pa[3], double pb[3])
{
    double p = 0;
    for (int i = 0; i < 3; i++)
    {
        p += pa[i] * pb[i];
    }

    if (p < 1e-6)//当结果太小时,取0
        return 0;
    else
        return p;
}

//单位化
void unit(double p[3])
{
    double k = sqrt(pow(p[0], 2) + pow(p[1], 2) + pow(p[2], 2));
    for (int i = 0; i < 3; i++)
        p[i] /= k;
    return;
}

//求用户坐标系到观察坐标系变换矩阵
void transform(point a, double t1[4][4])
{
    double pn[3] = { a.x - 0, a.y - 0, a.z - 0 };
    unit(pn);

    double pf[3] = { 0, 0, 1 };
    double pu[3], pv[3];

    Cross_Product(pf, pn, pu);
    unit(pu);

    Cross_Product(pn, pu, pv);
    unit(pv);

    double p[3] = { a.x, a.y, a.z };
    double m[3] = { 0, 0, 0 };
    m[0] = -Dot_Product(p, pu);
    m[1] = -Dot_Product(p, pv);
    m[2] = -Dot_Product(p, pn);

    for (int i = 0; i < 3; i++)
        t1[i][0] = pu[i];
    for (int i = 0; i < 3; i++)
        t1[i][1] = pv[i];
    for (int i = 0; i < 3; i++)
        t1[i][2] = pn[i];
    for (int i = 0; i <3; i++)
        t1[i][3] = 0;
    for (int i = 0; i < 3; i++)
        t1[3][i] = m[i];
    for (int i = 0; i < 3; i++)
        t1[i][3] = 0;
    t1[3][3] = 1;
    //得到用户坐标系到观察坐标系的转换矩阵t1[4][4]
    return;
}

//求透视投影变换矩阵
void perspective_Tran(point a, double ty[4][4])
{
    //观察窗口--------------------------------------------------------------
    double wwidth = getWindowWidth(), hheight = getWindowHeight();
    double d = wwidth / hheight;//横纵比
    double heightt = 10 * tan(PI / 6);//半个窗口高
    double height = 2 * heightt;
    double width = height*d;//窗口高和窗口宽

    //规范化变换矩阵--------------------------------------------------------
    //不需要进行投影中心和错切的变换
    double k = (double)10 / 1000;
    double a211 = (2 / width)*k;
    double a222 = (2 / height)*k;
    double a233 = 1 / 1000.0;

    double t2[4][4] = { a211 / 2.0, 0, 0, 0,
        0, a222 / 2.0, 0, 0,
        0, 0, a233, 0,
        0, 0, 0, 1 };               //比例变换  

    double f = (double)10 / 1000;
    double a333 = 1 / (1 - f);
    double a343 = -f / (1 - f);

    double t3[4][4] = { 1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, a333, 1,
        0, 0, a343, 0 };    //变为平行投影的规范化观察空间

    for (int i = 0; i < 4; i++){
        for (int j = 0; j < 4; j++)
        {
            ty[i][j] = 0;
            for (int w = 0; w < 4; w++)
                ty[i][j] += t2[i][w] * t3[w][j];
        }
    }//得到透视投影变换矩阵ty[4][4]

    int tmp = 0;
    return;
}

//得到平移等变换矩阵
void command(double(*p)[4], char order)
{
    switch (order)
    {
    case'y':
        printf("X、Y、Z平移量(形如:1,1,1):");
        scanf("%lf,%lf,%lf", p[3], p[3] + 1, p[3] + 2);
        break;
    case'f':
        printf("X、Y、Z变化比例(形如:1,1,1):");
        scanf("%lf,%lf,%lf", p[0], p[1] + 1, p[2] + 2);
        break;
    case'a':
        printf("绕X轴旋转角度(如:30°输入30):");
        double angle_a;
        scanf("%lf", &angle_a);
        angle_a /= PI;
        *(p[1] + 1) = (double)cos(angle_a);
        *(p[1] + 2) = (double)sin(angle_a);
        *(p[2] + 1) = -(double)sin(angle_a);
        *(p[2] + 2) = (double)cos(angle_a);
        break;
    case'b':
        printf("绕Y轴旋转角度:");
        double angle_b;
        scanf("%lf", &angle_b);
        angle_b /= PI;
        *(p[0] + 0) = (double)cos(angle_b);
        *(p[0] + 2) = -(double)sin(angle_b);
        *(p[2] + 0) = (double)sin(angle_b);
        *(p[2] + 2) = (double)cos(angle_b);
        break;
    case'c':
        printf("绕Z轴旋转角度:");
        double angle_c;
        scanf("%lf", &angle_c);
        angle_c /= PI;
        *(p[0] + 0) = (double)cos(angle_c);
        *(p[0] + 1) = (double)sin(angle_c);
        *(p[1] + 0) = -(double)sin(angle_c);
        *(p[1] + 1) = (double)cos(angle_c);
        break;
    }
    getchar();
    return;
}

//矩阵乘法
void change(double cuboid[8][4], double t[4][4], double new_cuboid[8][4])
{
    for (int i = 0; i < 8; i++)
        for (int j = 0; j < 4; j++)
        {
            new_cuboid[i][j] = 0;
            for (int w = 0; w < 4; w++)
                new_cuboid[i][j] += cuboid[i][w] * t[w][j];
        }
    return;
}

//三维转二维,便于在二维空间画图
void Tran3DTo2D(double(*rectangle)[4], double(*cuboid)[4])
{
    for (int i = 0; i < 8; i++)
    {
        rectangle[i][0] = cuboid[i][0] / cuboid[i][3];
        rectangle[i][1] = cuboid[i][1] / cuboid[i][3];
        rectangle[i][2] = cuboid[i][2] / cuboid[i][3];
        rectangle[i][3] = cuboid[i][3] / cuboid[i][3];
    }  //三维转二维(x,y,z,w)-->(x/w,y/w,z/w,w/w)

    return;
}

//画图
void draw(double(*tu)[4])
{
    double new_tu[8][2];
    double w = getWindowWidth();//屏幕宽
    for (int i = 0; i < 8; i++)
    {
        new_tu[i][0] = (*tu[i] + 1)*w / 2;
        new_tu[i][1] = (*(tu[i] + 1) + 1)*w / 2;
    }

    setOrig(0, 0);
    moveTo(new_tu[0][0], new_tu[0][1]);
    lineTo(new_tu[1][0], new_tu[1][1]);
    lineTo(new_tu[2][0], new_tu[2][1]);
    lineTo(new_tu[3][0], new_tu[3][1]);
    lineTo(new_tu[0][0], new_tu[0][1]);
    lineTo(new_tu[4][0], new_tu[4][1]);
    lineTo(new_tu[5][0], new_tu[5][1]);
    lineTo(new_tu[6][0], new_tu[6][1]);
    lineTo(new_tu[7][0], new_tu[7][1]);
    lineTo(new_tu[4][0], new_tu[4][1]);
    for (int i = 1; i < 4; i++)
    {
        moveTo(new_tu[i][0], new_tu[i][1]);
        lineTo(new_tu[i + 4][0], new_tu[i + 4][1]);
    }
    return;
}

int _tmain(int argc, _TCHAR* argv[])
{
    double cuboid[8][4] = { 0, 0, 0, 1,
        300, 0, 0, 1,
        300, 200,0, 1,
        0, 200, 0, 1,
        0, 0, 100, 1,
        300,0, 100, 1,
        300, 200, 100, 1,
        0, 200, 100, 1 };

    point a;//用户坐标系中的坐标

    char viewpoint;
    printf("是否开始/继续改变视点(y,n):");
    scanf("%c", &viewpoint);
    getchar();
    while (viewpoint == 'y')
    {
        printf("请输入观察参考点在用户坐标系中的坐标(x,y,z):");
        scanf("%lf,%lf,%lf", &a.x, &a.y, &a.z);
        getchar();

        matrix t1 = { 0 };//用户坐标系-->观察坐标系的转换矩阵
        matrix ty = { 0 };//在观察坐标系中的透视投影变换矩阵
        transform(a, t1);
        perspective_Tran(a, ty);

        double new1_cuboid[8][4], new2_cuboid[8][4];
        change(cuboid, t1, new1_cuboid);//用户坐标系-->观察坐标系中的坐标
        change(new1_cuboid, ty, new2_cuboid);//在观察坐标系中做透视投影后的坐标

        double rectangle[8][4];
        
        Tran3DTo2D(rectangle, new2_cuboid);

        draw(rectangle);  //未经过平移等变换的长方体

        double commandmand[4][4] = { 1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1 };

        char code, order;;
        printf("是否开始/继续变换(y,n):");
        scanf("%c", &code);
        getchar();
        while (code == 'y')
        {
            printf("请输入平移(y)、放缩比例(f)、旋转x轴y轴z轴(a、b、c):");
            scanf("%c", &order);
            getchar();

            command(commandmand, order);  //修改三维变换命令矩阵commandmand

            double new3_cuboid[8][4];
            change(cuboid, commandmand, new1_cuboid);  //用户坐标系中先按命令三维坐标变换进行变换

            change(new1_cuboid, t1, new2_cuboid);
            change(new2_cuboid, ty, new3_cuboid);

            Tran3DTo2D(rectangle, new3_cuboid);

            clearWindow();
            draw(rectangle);

            printf("是否开始/继续变换(y,n):");
            scanf("%c", &code);
        }

        printf("是否开始/继续改变视点(y,n):");
        scanf("%c", &viewpoint);
    }
    return 0;
}
View Code

    程序的稳定性还有一些小问题,在输入输出的如getchar上面还有待调试。哎呀不要在意细节,具体我们能够完成透视变换就已经棒棒哒啦!也欢迎小伙伴一起调试给出意见和建议哦!

    让我们来看一下代码的运行结果吧:

 

(1)原始长方体:

(2)缩小一倍:

(3)继续改变视点后得到的原始长方体:

(4)做平移变换:

(5)做旋转变换:

绕X旋转:

绕Y轴旋转:

绕Z轴旋转:

原文地址:https://www.cnblogs.com/to-sunshine/p/6501208.html