这次的内容是接着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; }
程序的稳定性还有一些小问题,在输入输出的如getchar上面还有待调试。哎呀不要在意细节,具体我们能够完成透视变换就已经棒棒哒啦!也欢迎小伙伴一起调试给出意见和建议哦!
让我们来看一下代码的运行结果吧:
(1)原始长方体:
(2)缩小一倍:
(3)继续改变视点后得到的原始长方体:
(4)做平移变换:
(5)做旋转变换:
绕X旋转:
绕Y轴旋转:
绕Z轴旋转: