三维旋转球 源码+注释+简单讲解
2010-9-14 ~ 2022-2-13
(3)
前言
关于三维的东西远不止这么点内容,也不是我几句话可以讲完的,需要大家扎扎实实的看图形学。不能好高骛远,要扎实、系统的学习。还要注意相关数学知识的学习。
图形学一般是借助矩阵实现的各种转换。我在这篇文章里尽量不用图形学的术语,也不做优化、不加额外功能,只用最简单的三角函数实现,希望大家能从道理上看的清楚明白。
简单讲解
关于一个点,一定要有一个三维坐标,程序中的结构体 POINT3D 就是。
点的初始化由函数 InitPoint() 实现,该函数产生了 n 个半径为 1 的点。
点的运动,是在三维坐标内运动的,包括平移、缩放、旋转等。这个程序只涉及到了旋转,定义了三个方法:RotateX()、RotateY()、RotateZ(),分别实现绕三个轴旋转;可以使用矩阵运算来实现,也可以直接这样算出来。
最后需要将三维世界呈现出来,这里用到一个术语:投影,就是将三维的画面投影到二维上。投影有多种方法,这个球体用的单点透视,因此还需要一个“观察点”,程序中用 viewZ 定义,观察点坐标是:(0, 0, viewZ)。函数 Projection() 负责实现将三维点投影到二维平面上。
通常都会用矩阵来实现 3D 转换运算,在这里我将矩阵运算展开来写了,用最简单直接的三角函数来计算。
剩下的留待大家自己拓展吧,比如加上缩放、键盘控制、改变点的颜色、优化性能等。只要原理搞明白,这些都不难。
源码
下面是源代码:
更多有趣的形状
InitPoint() 函数负责初始化随机点。前面的程序用随机点构成了一个球体,修改 InitPoint() 函数可以实现不同的形状。注意:请将 x、y、z 的范围生成在 -0.5 ~ 0.5 之间。
以下是各种形状对应的 InitPoint 函数。
立方体
正弦曲面
圆环
期待你写出来更有趣的形状。
// 初始化三维点
void InitPoint()
{
// 产生随机种子
srand((unsigned)time(NULL));
double A_num = 200, B_num = 125, rotate = -PI / 4, beginDegree = PI / 2 - rotate;
// 产生圆环的随机点
double a, r;
for (int i = 0; i < MAXPOINT; i++)
{
bool isRight = (rand() % 2);
double angle = PI / 1000.0 * (rand() % 1000);
double ver_angle = PI / 1000.0 * (rand() % 1000) - PI / 2;
double temp_x = A_num * B_num * cos(-PI + beginDegree + angle) /
sqrt(A_num * A_num * sin(-PI + beginDegree + angle) * sin(-PI + beginDegree + angle) +
B_num * B_num * cos(-PI + beginDegree + angle) * cos(-PI + beginDegree + angle));
double temp_y = A_num * B_num * sin(-PI + beginDegree + angle) /
sqrt(A_num * A_num * sin(-PI + beginDegree + angle) * sin(-PI + beginDegree + angle) +
B_num * B_num * cos(-PI + beginDegree + angle) * cos(-PI + beginDegree + angle));
double t_x = isRight ? temp_x * cos(rotate) - temp_y * sin(rotate) : -(temp_x * cos(rotate) - temp_y * sin(rotate));
double t_y = temp_x * sin(rotate) + temp_y * cos(rotate);
p3d[i].z = B_num * sin(ver_angle);
p3d[i].x = t_x * cos(ver_angle);
p3d[i].y = t_y * cos(ver_angle);
}
}
// 实现直接操作显示缓冲区的设备对象
class Device
{
private:
DWORD *m_pbuffer;
int m_width;
int m_height;
public:
Device(int w, int h) : m_width(w), m_height(h)
{
initgraph(w, h);
BeginBatchDraw();
m_pbuffer = GetImageBuffer();
}
~Device()
{
EndBatchDraw();
closegraph();
}
// 画点
void putpixel(int x, int y, COLORREF c)
{
if (x >= 0 && x < m_width && y >= 0 && y < m_height)
m_pbuffer[y * m_width + x] = BGR(c);
}
// 更新显示
void flushdevice()
{
FlushBatchDraw();
}
};