三维旋转球 源码+注释+简单讲解
2010-9-14
3D 绘图
关于三维的东西远不止这么点内容,也不是我几句话可以讲完的,需要大家扎扎实实的看图形学。不能好高骛远,要扎实、系统的学习。还要注意相关数学知识的学习。
过去我写了一些简单教程(以后还会写),但是这篇文章不算教程。我写这篇文章,是想表达我前面的看法,希望大家能认识到。
我在这篇文章里尽量不用图形学的术语,也不做优化、不加额外功能,只希望大家能从道理上看的清楚明白。
简单讲一下:
关于一个点,一定要有一个三维坐标,程序中的结构体 POINT3D 就是;
点的初始化由函数 InitPoint() 实现,该函数产生了 n 个半径为 1 的点;
点的运动,是在三维坐标内运动的,包括平移、缩放、旋转等。这个程序只涉及到了旋转,定义了三个方法:RotateX()、RotateY()、RotateZ(),分别实现绕三个轴旋转;
最后需要将三维世界呈现出来,这里用到一个术语:投影,就是将三维的画面投影到二维上。投影有多种方法,这个球体用一点透视就可以。还需要一个“观察点”,程序中用 viewZ 定义,具体的观察点坐标是:(0, 0, viewZ)。函数 Projection() 负责实现这些功能。
通常都会用矩阵来实现 3D 转换运算,在这里我将矩阵运算展开来写了,用最简单直接的三角函数来计算。
剩下的留待大家自己拓展吧,比如加上缩放、键盘控制、改变点的颜色、优化性能等。只要原理搞明白,这些都不难。
下面是源代码:
// 程序名称:三维旋转球
// 编译环境:Visual C++ 6.0,EasyX 2011惊蛰版
// 最后更新:2010-9-14
//
#include <graphics.h>
#include <time.h>
#include <math.h>
#include <conio.h>
#define MAXPOINT 2000
#define PI 3.1415926536
// 定义三维点
struct POINT3D
{
double x;
double y;
double z;
};
POINT3D p3d[MAXPOINT]; // 所有的三维点
double viewZ = 3; // 视点 z 轴坐标
// 初始化三维点
void InitPoint()
{
// 产生随机种子
srand((unsigned)time(NULL));
// 产生球体表面的随机点(根据球体面积与其外切圆柱面积的关系)
double rxy, a;
for(int i=0; i<MAXPOINT; i++)
{
p3d[i].z = 2.0 * rand() / RAND_MAX - 1; // 求随机 z 坐标
rxy = sqrt(1 - p3d[i].z * p3d[i].z); // 计算三维矢量在 xoy 平面的投影长度
a = 2 * PI * rand() / RAND_MAX; // 产生随机角度
p3d[i].x = cos(a) * rxy;
p3d[i].y = sin(a) * rxy;
}
}
// 使三维点按 x 轴旋转指定角度
void RotateX(POINT3D &p, double angle)
{
double y = p.y;
p.y = p.y * cos(angle) + p.z * sin(-angle);
p.z = y * sin(angle) + p.z * cos(angle);
}
// 使三维点按 y 轴旋转指定角度
void RotateY(POINT3D &p, double angle)
{
double x = p.x;
p.x = p.x * cos(angle) + p.z * sin(-angle);
p.z = x * sin(angle) + p.z * cos(angle);
}
// 使三维点按 z 轴旋转指定角度
void RotateZ(POINT3D &p, double angle)
{
double x = p.x;
p.x = p.x * cos(angle) + p.y * sin(-angle);
p.y = x * sin(angle) + p.y * cos(angle);
}
// 将三维点投影到二维屏幕上(单点透视)
POINT Projection(POINT3D p)
{
POINT p2d;
p2d.x = (int)(p.x * ( viewZ / (viewZ - p.z) ) * 200 + 0.5) + 320;
p2d.y = (int)(p.y * ( viewZ / (viewZ - p.z) ) * 200 + 0.5) + 240;
return p2d;
}
void main()
{
initgraph(640, 480);
InitPoint();
BeginBatchDraw();
int c;
POINT p2d;
while(!_kbhit())
{
cleardevice(); // 清除屏幕
for(int i=0; i<MAXPOINT; i++)
{
// 使该点围绕三个坐标轴做旋转运动
RotateX(p3d[i], PI/180);
RotateY(p3d[i], PI/170);
RotateZ(p3d[i], PI/160);
// 根据点的深度,产生相应灰度的颜色
c = (int)(p3d[i].z * 100) + 155;
// 投影该点到屏幕上
p2d = Projection(p3d[i]);
// 画点
putpixel(p2d.x, p2d.y, RGB(c,c,c));
}
FlushBatchDraw();
Sleep(10); // 延时 10 毫秒
}
EndBatchDraw();
closegraph();
}