|
就是把4d空间的图形投影到3d中观察。
完整源代码 http://upload.gameres.com/20107/sf_1603621_1843.zip
首先我们要建立一个4D的“超立方体”。
我们先看一下3D立方体的顶点定义。

先看我们熟悉的d3d左手坐标系,
沿着Z轴正方向是前方。
3D立方体中,(设棱长==2a)
各个(共8个)顶点坐标为:
1 {-a, a, a }
2 { a, a, a }
3 { a, -a, a }
4 {-a, -a, a }
5 {-a, a, -a }
6 { a, a, -a }
7 { a, -a, -a }
8 {-a, -a, -a }
有什么规律呢?。。。。。。
==============================================================================
先打断一下,我有必要说明一下3维坐标,4维坐标的问题。非澄清不可!
在3维空间中,普通的坐标是我们熟知的3维坐标,比如,点A(x1,y1,z1).点B(x2,y2,z2)
d3d中为了更好的变换,引进了4维齐次坐标的。(具体原因在此就不说了)
但是,到底什么是4维齐次坐标?
一个4维齐次坐标,由4个数字组成,比如 点P(x,y,z, w )。
虽然有“4维”,但是,它所表示的,却只不过是3维空间中的一个点。
反过来,一个3维空间的点,用齐次坐标表示,需要4个数。
d3d在绘制的时候,最终,要把4维齐次坐标转换成3维齐次坐标。
具体的转换方式很简单,如果点P的4维齐次坐标是(x,y,z,w),
那么,点P的3维普通坐标是(x/w, y/w, z/w).
反过来,把一个点的3维普通坐标转换成4维齐次坐标,由无数种结果,
不过一般,我们把w设为1。比如点Q(x0,y0,z0),则4维齐次坐标为(x0,y0,z0,1)
当然,比如说设为(2x0,2y0,2z0,2)等等也可以。
------------------------------------------------------------------------------
在4维空间中,有4个方向,一个点的普通坐标,需要用4个数来表示,
比如,点M(x,y,z,u).这叫做4维普通坐标,表示4维空间的点。
习惯上用u来表示第4维。(u应该是ultra“超,超级”的首字母)
4维的点,类似地,也有齐次坐标。4维点的齐次坐标,显然,需要5个数字。
比如,点N(x,y,z,u, w ).那么,点N的普通坐标就是(x/w, y/w, z/w, u/w).
注意,很多朋友包括在回帖的时候,混淆4维齐次坐标和4维普通坐标!
4维齐次坐标表示3维空间的点。
4维普通坐标表示4维空间的点。
***这样说会比较好理解一点***
一个平面上的点,用普通坐标表示,需要2个数字;用齐次坐标表示,需要3个数字;
一个3D空间的点,用普通坐标表示,需要3个数字;用齐次坐标表示,需要4个数字;
一个4D空间的点,用普通坐标表示,需要4个数字;用齐次坐标表示,需要5个数字.
===============================================================================
另外说一下数据类型和变量名的问题,在我的4D引擎中,有4中向量(点,坐标)类型:
这些typedef都是大写,但为了大家能看得清楚一些,我用大小写交替!
i3DxVec3---3维普通坐标。i3DxVec4---4维齐次坐标。
它们都表示3维空间中的点或向量。
typedef union
{
struct{
float x,y,z;
};
struct{
float x1,x2,x3;
};
}I3DXVEC3;
i3DxVec3的成员,可以这样用:x,y,z; 也可以这样用x1,x2,x3.
i3DxVec4的成员,可以这样用:x,y,z,w; 也可以是 x1,x2,x3,x4.
................................................................................
i4DxVec4---4维普通坐标。i4DxVec5---5维齐次坐标。
它们都表示4维空间中的点或向量。
i4DxVec4可以这样用:x,y,z,u; 也可以:x1,x2,x3,x4.
i4DxVec5可以这样用:x,y,z,u,w; 也可以:x1,x2,x3,x4,x5.看你喜好了~
================================================================================
********************************************************************************
***@齐次坐标一般在引擎内部使用,我们研究几何,或定义顶点数据时,都用普通坐标@***
********************************************************************************
================================================================================
继续上面的立方体的话题。
想象有一个4维“超立方体”,棱长==2a, 你怎么写出它的顶点?有几个顶点?
我们很容易发现,3维立方体的各顶点坐标,实际上就是a和-a的排列组合!
类似,在4维空间中,运用排列组合,可以写出各个顶点坐标,排列数就是顶点数目:
列举这些顶点,有许多方式,我们并不是在学习排列组合课程,就不多说了。
方便起见,我这样排列,用Ctrl+C/Ctrl+V把刚才的3维立方体坐标粘贴过来:
1{-a, a, a }
2{ a, a, a }
3{ a, -a, a }
4{-a, -a, a }
5{-a, a, -a }
6{ a, a, -a }
7{ a, -a, -a }
8{-a, -a, -a }
然后,在x,y,z坐标的基础上,加上一维(u坐标):
1{-a, a, a, a}
2{ a, a, a, a}
3{ a, -a, a, a}
4{-a, -a, a, a}
5{-a, a, -a, a}
6{ a, a, -a, a}
7{ a, -a, -a, a}
8{-a, -a, -a, a}
但是,这当然还没有列举完所有顶点。
Ctrl+C/Ctrl+V,再把最后一维坐标改一下,
1{-a, a, a, -a}
2{ a, a, a, -a}
3{ a, -a, a, -a}
4{-a, -a, a, -a}
5{-a, a, -a, -a}
6{ a, a, -a, -a}
7{ a, -a, -a, -a}
8{-a, -a, -a, -a}
这样得到了2*8==16个顶点的坐标,这就是4维超立方体的顶点坐标。
然后,为了追求完美,还要做一个小的修饰,把顶点编号改一下:
1_1 {-a, a, a, a}
1_2 { a, a, a, a}
1_3 { a, -a, a, a}
1_4 {-a, -a, a, a}
1_5 {-a, a, -a, a}
1_6 { a, a, -a, a}
1_7 { a, -a, -a, a}
1_8 {-a, -a, -a, a}
2_1 {-a, a, a, -a}
2_2 { a, a, a, -a}
2_3 { a, -a, a, -a}
2_4 {-a, -a, a, -a}
2_5 {-a, a, -a, -a}
2_6 { a, a, -a, -a}
2_7 { a, -a, -a, -a}
2_8 {-a, -a, -a, -a}
================================================================================
现在,顶点有了,很简单,但关键是,一个4维超立方体,它的各个顶点之间是怎么连接的?
***我将从两个角度来帮助大家理解,首先是第一个角度***
我们不妨先看看3维立方体中,顶点的连接情况,我把3d立方体的顶点数据再拷贝一份:
1{-a, a, a }
2{ a, a, a }
3{ a, -a, a }
4{-a, -a, a }
5{-a, a, -a }
6{ a, a, -a }
7{ a, -a, -a }
8{-a, -a, -a }

我重新画了图,对于立方体的一些棱,使用了不同的颜色。
顶点之间的连接是:
1~2 , 2~3 , 3~4 , 4~1
实际上,以上的1-4顶点,组成了立方体的"前面"(它们的最后一维z坐标== +a)
5~6 , 6~7 , 7~8 , 8~1
实际上,以上的5-8顶点,组成了立方体的"后面"(它们的最后一维z坐标== -a)
然后,前面和后面,对应顶点,相连在一起:
1~5
2~6
3~7
4~8
这样,就构成了立方体。
上面写的太散了,我们整理整理:
1~2 , 2~3 , 3~4 , 4~1
5~6 , 6~7 , 7~8 , 8~1
1~5 , 2~6 , 3~7 , 4~8
................................................................................
类似地,推广到4维超立方体中,
顶点1_1 到 1_8 的最后一维u坐标== +a.我又拷贝了一份:
1_1 {-a, a, a, a}
1_2 { a, a, a, a}
1_3 { a, -a, a, a}
1_4 {-a, -a, a, a}
1_5 {-a, a, -a, a}
1_6 { a, a, -a, a}
1_7 { a, -a, -a, a}
1_8 {-a, -a, -a, a}
它们的最后一维u坐标相同,(在同一个3维超平面中),组成了一个3维立方体,
顶点的连接情况自然就是:
1_1 ~ 1_2 , 1_2 ~ 1_3 , 1_3 ~ 1_4 , 1_4 ~ 1_1
1_5 ~ 1_6 , 1_6 ~ 1_7 , 1_7 ~ 1_8 , 1_8 ~ 1_1
1_1 ~ 1_5 , 1_2 ~ 1_6 , 1_3 ~ 1_7 , 1_4 ~ 1_8
顶点2_1 到 2_8 的最后一维u坐标== -a。
它们也组成了3维立方体,顶点连接情况显然是:
2_1 ~ 2_2 , 2_2 ~ 2_3 , 2_3 ~ 2_4 , 2_4 ~ 2_1
2_5 ~ 2_6 , 2_6 ~ 2_7 , 2_7 ~ 2_8 , 2_8 ~ 2_1
2_1 ~ 2_5 , 2_2 ~ 2_6 , 2_3 ~ 2_7 , 2_4 ~ 2_8
(本文中复制粘贴比较多^ ^ 希望大家找到规律)
然后,在这个4维超立方体的顶点中,
u==+a 和 u==-a 的两个3维立方体之间的对应顶点,相互连接即可。也就是:
(这是不是类似于3维立方体前后两个面的对应顶点连接呢?)
1_1 ~ 2_1
1_2 ~ 2_2
1_3 ~ 2_3
1_4 ~ 2_3
1_5 ~ 2_8
1_6 ~ 2_8
1_7 ~ 2_8
1_8 ~ 2_8
------------------------------------------------------------------------------
如果您觉得以上内容还是有些抽象,那么,----
***我们再从另一个角度理解各个顶点之间的连接***
注意:各个顶点的坐标我们早就求出来了,我们现在只需要关心顶点之间的连接!
我们想象用橡皮筋制作一个3维立方体,并为各个顶点标号,观察各个顶点连接情况,
然后把立方体压扁,平放到一张纸上,成为一个2维图形,再来观察各个顶点连接情况,
傻瓜也知道,各个顶点的连接情况,没有发生任何改变。
(说的故弄玄虚一点,这是图形的拓扑不变性,变化前后的两个图形是“同胚”的)。
把一个橡皮筋做的4维立方体压扁到一张白纸上,那么,
各个顶点之间的连接情况也不会改变!
换言之,我们可以在一张大白纸上(2D平面),研究4维立方体的顶点连接情况。
..............................................................................
我们看看2维正方形的顶点连接情况。

可以归纳为:用橡皮筋先构建两条边,平铺在纸上,再把两条边的对应顶点连接。
然后看看3维立方体的顶点连接情况。

可以归纳为:用橡皮筋先构建两个正方形,平铺在纸上,再把它们对应顶点连接。
我们可以类推出,4维超立方体的顶点连接。

可以归纳为:用橡皮筋先构建两个立方体,平铺在纸上,再把它们对应顶点连接。
..............................................................................
我们甚至可以推导出5维超立方体的顶点连接。
就是用橡皮筋先构建两个4维超立方体,平铺在纸上,再把它们的对应顶点连接。

6维超立方体就免了^ ^
------------------------------------------------------------------------------
我们现在直观地理解了高维超立方体,那么,实际上,我们还可以为5维超立方体定义顶点,
大家可以先自己做一下,以加深对高维超立方体的理解.
1_0 {-a, a, a, a, a}
1_1 { a, a, a, a, a}
1_2 { a, -a, a, a, a}
1_3 {-a, -a, a, a, a}
1_4 {-a, a, -a, a, a}
1_5 { a, a, -a, a, a}
1_6 { a, -a, -a, a, a}
1_7 {-a, -a, -a, a, a}
1_8 {-a, a, a, -a, a}
1_9 { a, a, a, -a, a}
1_A { a, -a, a, -a, a}
1_B {-a, -a, a, -a, a}
1_C {-a, a, -a, -a, a}
1_D { a, a, -a, -a, a}
1_E { a, -a, -a, -a, a}
1_F {-a, -a, -a, -a, a}
2_0 {-a, a, a, a, -a}
2_1 { a, a, a, a, -a}
2_2 { a, -a, a, a, -a}
2_3 {-a, -a, a, a, -a}
2_4 {-a, a, -a, a, -a}
2_5 { a, a, -a, a, -a}
2_6 { a, -a, -a, a, -a}
2_7 {-a, -a, -a, a, -a}
2_8 {-a, a, a, -a, -a}
2_9 { a, a, a, -a, -a}
2_A { a, -a, a, -a, -a}
2_B {-a, -a, a, -a, -a}
2_C {-a, a, -a, -a, -a}
2_D { a, a, -a, -a, -a}
2_E { a, -a, -a, -a, -a}
2_F {-a, -a, -a, -a, -a}
至于这32个定点之间的连接,就免了,写在这里太浪费harddisk.也可以编程完成.
==============================================================================
超立方体建立完毕后,我们就需要在4D空间中观察它。
4维世界顶点变换处理的流程是:(使用CI4D::SetTransform)
4维世界变换 --> 4维观察变换 --> 4维投影变换 >>> 3维空间(3D输出)
然后,在3维投影空间中,进行3D输出变换:(使用CI4D::Set3DOutputTransform)
3维投影空间 --> 3维观察变换 --> 3维投影变换 >>> 视口缩放 >>> 计算机屏幕
先对3维变换做个说明:

我使用Direct3D做3D输出。
3维观察变换(摄影机变换)之后,顶点到了摄影机空间中,然后再做投影变换:
从摄影机空间中,朝着前方(Z 轴正方向)取一个“观察平截头体”,
然后,把观察平截头体缩放到投影空间中的一个半立方体中(图7右边),
这个半立方体的的范围是,x,y坐标范围从-1 ~ +1;z坐标范围从0 ~ +1。
不过摄影机空间中观察平截头体之外的顶点,也跟着一起变换到了投影空间里,
这些额外的顶点,在那个半立方体之外,所以需要“剪裁”掉。这个工作由3D引擎完成。
然后,3维工作到此结束,接下来,变换最终的z坐标值和(x,y)坐标值,被分开处理:
z坐标,被用于排序;
另一方面,(x,y)坐标(范围在-1 ~ +1)经过视口缩放(乘以窗口的长宽),
变成屏幕坐标,然后绘制到屏幕上。
这样就完成了到2维屏幕的投影。
投影结束后,x,y坐标的范围,是窗口的坐标范围。
***注意:我没有提供3维的世界变换,因为没有必要***
..............................................................................
然后我们对4维变换做个说明:
4维世界变换和4维观察变换之后,顶点到了摄影机空间中,然后再做投影变换:
从摄影机空间中,朝着前方(U 轴正方向!!)取一个“4维观察平截头体”,
然后,把观察平截头体缩放到投影空间中的一个“半超立方体”中,
这个半超立方体的的范围是,x,y,z坐标范围从-1 ~ +1;u坐标范围从0 ~ +1。
不过摄影机空间中观察平截头体之外的顶点,也跟着一起变换到了投影空间里,
这些额外的顶点,在那个半立方体之外,所以需要“剪裁”掉。这个工作,
暂时I4D引擎还没有实现。所以,你心里要有个数,不要让摄影机靠物体太近!
这样的话,比如说,“拍摄”一个4维超立方体,离远一点,就完全没有任何问题了。
因为我迫切要研究4维世界,我等不及,所以,先凑合着用吧^ ^。
然后呢,我没有3D引擎中的“视口缩放”这一步,没必要,所以,
按照最终的x,y,z坐标,填写ID3DVertexBuffer9就可以了。
由于不需要排序(具体原因以后再说),所以第四维u坐标,就没有任何用处了。
这样就完成了到3维世界的投影。
注意(我不需要3D引擎中的“视口缩放”这一步),投影结束后,x,y,z坐标的范围,
是 -1 到 +1。
然后,要设置3D输出变换。
...............................................................................
4维观察平截头体是什么样子的?
3维观察平截头体,可以看成是一个变形的立方体,不是吗?
(立体几何上叫做“4棱锥台”)
变形的立方体,和标准的立方体,各个顶点之间的连接情况是相同的。和图5一样。
(即拓扑结构相同,专业地说,它们是“同胚”的)
那么类似,4维观察平截头体,可以看成是一个变形的4维超立方体。
它的拓扑结构的话,和图6是一样的。
假如我要做5D引擎的话,那么,5维观察平截头体的拓扑结构,大家应该可以想象吧 ^ ^
------------------------------------------------------------------------------
另外,我写了一个CI3DXCamera和CI4DXCamera这两个摄影机类。
其中,CI3DXCamera,和我以前在gameres上贴过的一个CCamera类基本相同。
看一下app.cpp。
InitI4D()中,初始化了I4D对象和两个摄影机(3D摄影机和4D的)
InitDraw()中,初始化了一个4维超立方体的数据。
注意:我使用LineList(线列表)而不是3角形!
顶点建立了之后,需要锁定4维顶点缓冲区,然后用memcpy拷贝顶点:
I4DVERTEX *pVerticesT;
g_pVB1->Lock(&pVerticesT);
{
memcpy(pVerticesT,pMyVertices,NUM_VERTICES*sizeof(I4DVERTEX));
}
g_pVB1->Unlock();
FrameMove()中,进行4维变换(我没做世界变换),然后做3D输出的变换。
OnKeyDown(WORD KeyCode)中,处理了键盘操作。使用键盘控制摄影机。
我的键盘定义:
3维空间中的前后左右上下平移按键:(沿着摄影机自己的x,y,z轴移动!)
const WORD k3_fore='W', k3_back= 'S', k3_left= 'A', k3_right= 'D', k3_up='Q', k3_down='Z';
3维空间中的旋转按键(围绕世界坐标y和z轴来旋转摄影机!)
const WORD k3r_up= 'F', k3r_down='C', k3r_left='X', k3r_right='V';
4维空间中的4个方向上的平移按键:(沿着摄影机自己的x,y,z轴移动!)
const WORD k4_fore='Y', k4_back= 'H', k4_left= 'G', k4_right= 'J', k4_up= 'T',k4_down='B',k4_up2='U', k4_down2='N';
4维空间中的旋转按键:(围绕世界坐标的某平面旋转!)
const WORD k4r_xy1='I', k4r_xz1= 'O', k4r_xu1= 'P', k4r_yz1= 219, k4r_yu1='M',k4r_zu1=190;
const WORD k4r_xy2='K', k4r_xz2= 'L', k4r_xu2= 186, k4r_yz2= 221, k4r_yu2=188,k4r_zu2=191;
(注:186=";"--219="["--221="]"--188="<"--190=">"--191="?")
需要说的是,3维空间中的旋转,是绕着某个轴进行的,而4维的旋转,需要围绕某个平面进行。
比如:
k4r_xy1 按键:控制围绕xy平面向正方向旋转;
k4r_xy2 按键:控制围绕xy平面向负方向旋转。
最后,是Render()函数:
void Render()
{
g_pi4d->Clear(I4DCOLOR_XRGB(0,0,0));
g_pi4d->BeginScene();
{
g_pi4d->DrawPrimitive(g_pVB1);
}
g_pi4d->EndScene();
/////////////////////
g_pi4d-> resent();
} |
|