四维空间的二维线框投影可视化(附matlab代码)
写这篇博客的动机是来源于Matrix67大神的一篇博客
不同维度的对话:带你进入四维世界http://www.matrix67.com/blog/archives/1323
里面有非常酷炫的展示效果。很早就想实际实践一波,但是实际动手才发现要想获得可视化效果还是不容易的。
之后查找相关的资料,发现Steven Richard Hollasch的Four-Space Visualization of 4D Objects在四维可视化部分写的非常的详细。详情可以点击http://hollasch.github.io/ray4/Four-Space_Visualization_of_4D_Objects.html。页面打不开的话,在bing上搜索这篇文章,有Cached镜像。
注意它默认用到的是左手坐标系。本文由于采用的是对称的正方体进行演示,所以用的是右手坐标系,不太影响展示效果,所以没有加以修正。
还是惯例声明:本人没有相关的工程应用经验,只是纯粹对相关算法和展示效果感兴趣才写此博客。所以如果有错误,欢迎在评论区指正,不胜感激。本文主要关注于算法的实现,对于实际应用等问题本人没有任何经验,所以也不再涉及。
1 三维空间在2维屏幕上的投影
首先常见的三维空间在2维屏幕上的投影,用来验证算法的正确性。
由于眼睛结构构造的限制,视网膜只能接受二维的光线信息。我们人类直接观察3维物体时,丢失掉垂直于视网膜方向上的维度信息,也就是物体距离眼睛的距离,或者可以叫景深。这常常被应用与某些3维游戏设计,或者一些视觉错觉方面。
当然人类也可以间接的通过两只眼睛进行距离判断,或者根据参照物,或者运动时物体相对关系,或者眼球的运动等间接因素,来感知距离。当然,这不是本文的重点。
本文拟根据相机或眼球的拍摄投影原理,进行3维空间在2维屏幕上投影的算法概述。
1.1平行投影
首先我们定义观察原点From,观测方向点To,以及定义向上方向Up。如下图所示:
我们的看物体的方向是从From点到To点,投影到屏幕上后,还需要定义哪里是上方,也就是从From点到Up点。
之后,求出刚才3个观察点所确定的ABC坐标系,作为眼睛坐标系。
C = T o − F r o m ∥ T o − F r o m ∥ C=\frac{To-From}{\lVert To-From \rVert} C=∥To−From∥To−From
A = U p × C ∥ U p × C ∥ A=\frac{Up \times C}{\lVert Up \times C \rVert} A=∥Up×C∥Up×C
B = C × A B=C \times A B=C×A
其中, ∥ X ∥ \lVert X \rVert ∥X∥代表向量的2范数,或者可以叫做绝对值,它的值等于向量每个元素平方和的开根号。 C × A C \times A C×A代表向量C叉乘以向量A,得到的结果仍然是一个向量,垂直于A和C。
之后,真实空间中某个点的Px、Py、Pz坐标就可以转换为眼睛坐标系下的坐标P’:
P ′ = [ P x ′ P y ′ P z ′ ] = [ ( P x − F x ) ( P y − F y ) ( P z − F z ) ] ∗ [ A x B x C x A y B y C y A z B z C z ] P'=\left[ \begin{matrix} P'_x& P'_y& P'_z\\ \end{matrix} \right] \\ =\left[ \begin{matrix} (P_x-F_x)& (P_y-F_y)& (P_z-F_z)\\ \end{matrix} \right] *\left[ \begin{matrix} A_x& B_x& C_x\\ A_y& B_y& C_y\\ A_z& B_z& C_z\\ \end{matrix} \right] P′=[Px′Py′Pz′]=[(Px−Fx)(Py−Fy)(Pz−Fz)]∗
AxAyAzBxByBzCxCyCz
上式中,Fx、Fy、Fz对应着观察点的位置,可以视为From点的坐标。公式中Ax、Ay、Az代表之前求出的A向量三个值,其余B和C向量同理。
到此,我们得到了新的空间上一点的坐标。其中P’x和P’y是在2维上的投影坐标,P’z是距离2维平面的距离。绘图时,只需要绘制P’x和P’y即可。
所以三维投影到二维算法步骤大致为:
1定义From、To、Up点
2计算对应着ABC坐标
3将实际坐标P(x,y,z)转换到眼睛坐标P'(x,y,x)
4绘制P'(x,y)作为2维投影
matlab代码如下:
%创建3维物体的二维投影(平行)
clear
clc
close all
for k=0:1:40
%创建3维立方体
Line_AB = Create_Cube(3);
Line_AB = Line_AB*2-1;
N=size(Line_AB,1);
Line_List_1 = Line_AB(:,1:3)';
Line_List_2 = Line_AB(:,4:6)';
%旋转一定角度
Line_List_1 = Ro_Ma_3D(2,pi/80*k)*Ro_Ma_3D(1,pi/80*k)*Line_List_1;
Line_List_2 = Ro_Ma_3D(2,pi/80*k)*Ro_Ma_3D(1,pi/80*k)*Line_List_2;
%定义To From Up
To = [0,0,0];
From = [0,0,1];
Up = [0,1,0];
%3D眼睛坐标
C = (To-From)/norm(To-From);
A = cross(C,Up)/norm(cross(C,Up));
B = cross(A,C);
ABC = [A',B',C'];
%xyz坐标下的点转换
Line_AB_ABC = zeros(size(Line_AB));
F_xyz = [0,0,0];%定义原点,透视视角有用
Line_ABC_1 = (Line_List_1'-ones(N,1)*F_xyz)*ABC;
Line_ABC_2 = (Line_List_2'-ones(N,1)*F_xyz)*ABC;
%投影到2D屏幕上
figure(1)
clf
hold on
for m = 1:size(Line_AB_ABC,1)
P1 = Line_AB_ABC(m,1:2);
P2 = Line_AB_ABC(m,4:5);
plot([Line_ABC_1(m,1),Line_ABC_2(m,1)],[Line_ABC_1(m,2),Line_ABC_2(m,2)])
end
hold off
xlim([-2,2])
ylim([-2,2])
pause(0.5)
end
function Line_AB = Create_Cube(Dim)
%创建Dim维度的正方体,坐标范围0-1
%Dim = 3;
Line_AB = zeros(1,2*Dim);
Line_AB(Dim+1) = 1;
Line_AB_Old = Line_AB;
for k = 2:Dim
%向另一个维度复制
P_Old = size(Line_AB_Old,1);%上一个维度的线数
Line_AB_New1 = [Line_AB_Old;Line_AB_Old];
Line_AB_New1(P_Old+1:end,k) = 1;
Line_AB_New1(P_Old+1:end,Dim+k) = 1;
%连接相邻维度的线
P_Old2 = 2^(k-1);%上一个维度的点数
Vertex2 = Full_Choose([0;1],k-1);
Line_AB_New2 = zeros(P_Old2,2*Dim);
Line_AB_New2(:,1:k-1) = Vertex2;
Line_AB_New2(:,Dim+1:Dim+k