一、三维空间基转换
1.绕原点旋转
二维坐标旋转可以用矩阵表示:
设有向量,那么当向量绕原点逆时针旋转度,那么旋转矩阵为。
推导略。
注:向量是列向量,因此旋转后的向量为:
旋转矩阵可以推广至更高维度的空间,以三维空间为例:
设有向量,那么当向量绕轴逆时针旋转度时,相当于在向量在一个平行于平面的的二维空间中,绕原点旋转,旋转后坐标保持不变,那么旋转矩阵可以改为:
向量绕轴和轴逆时针旋转度的旋转矩阵为:
因此向量在三维空间中,绕原点旋转任意角度,可由以上三个旋转矩阵连乘得到,即:
2.绕特定点旋转
二维空间中绕特定旋转可以用以下矩阵乘法表示:
其中向量用齐次坐标表示,即,。
三个矩阵从右至左的含义分别是:
:平移向量使得空间以为原点
:绕原点旋转
:还原空间的原点
推导略。
推广至三维空间,绕特定点旋转可以表示为:
其中
3.
二、视锥体成像
1.视点空间中的视椎体成像
如下图所示,在一个视点空间中有一根棍子(线段),其中和,点为视点,平面为成像平面,为棍子在视平面上的成像。
注:视点空间是一个左手系的三维线性空间
由于点和点分别是点和点和点的线性组合,因此视线和的空间可以表示为
即
当时(为视平面在三维空间中的坐标),便能得到在成像平面上的成像。
根据以上可以得出,视点空间中一个物体的成像和两个因素有关:视线(视点和物体的连线)与视平面的相对角度(直接决定在物体在三维空间中的坐标),视平面在物体和视点间的相对位置(即)
如果将视点平移至原点,即如下图所示:
那么等式组(1)可以简化为
其中,因此可以得出:
在成像平面上,点和即平面成像。
一般情况下,,即成像屏幕介于视点和物体之间。
2.透视效果
下图是一个二维空间中的成像示意图,视点为,成像平面为,蓝色实线代表两个离视点距离不同,但是大小相同的物体,蓝色虚线为实际成像,图(a)为正射投影,图(b)为视锥体投影。
从对比图中可以看到,两个一样的物体(物体形状及物体与视平面的角度均一致)在视平面上的正射投影是一致的,但是视椎体投影会因为距离的原因而出现不同的成像,这种现象就是透视效果(近大远小)。
当等式组(2)中的时,即成像屏幕与物体的距离趋向于无穷远时,视椎体投影的比例和正射投影一致,证明略。
3.物体空间到视点空间的转换
物体空间是一个服从右手系的三维空间,蓝色平面为地面,如下图所示:
下图(b)为视点空间,图(a)是成像平面,图(c)为物体空间。
物体空间中的物体映射到视点空间主要有两部操作:
STEP1 翻转轴
STEP2 绕轴逆时针旋转
可以通过转换矩阵实现
其中,即:
三、总结
当需要计算一个三维空间中的物体的二维成像,只需要进行以下两步操作,如下图所示:
STEP1:通过基转换把物体中的物体转换至视点坐标系中,得到物体,即:
其中
STEP2:在视点坐标系中,计算物体在成像屏幕处的成像,即:
四、PYTHON实现
# -*- coding:utf-8 -*-
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def trans_base(x, y, z):
# 计算三维空间中的旋转矩阵
Txyz = np.array(
[
[np.cos(y) * np.cos(z), -np.cos(y) * np.sin(z), -np.sin(y), 0],
[np.cos(x) * np.sin(z) - np.sin(x) * np.sin(y) * np.cos(z),
np.cos(x) * np.cos(z) + np.sin(x) * np.sin(y) * np.sin(z),
-np.sin(x) * np.cos(y), 0
],
[np.sin(x) * np.sin(z) + np.cos(x) * np.sin(y) * np.cos(z),
np.sin(x) * np.cos(z) - np.cos(x) * np.sin(y) * np.sin(z),
np.cos(x) * np.cos(y), 0
],
[0, 0, 0, 1]
]
)
return Txyz
def gen_cube(a=1):
# 生成一个以原点为中心,边长为a的正方体
d = []
X = np.linspace(a/2, -a/2, 2)
for x1 in X:
for x2 in X:
for x3 in X:
d.append([x1, x2, x3])
return np.array(d)
def to_hc(a):
# 齐次坐标表示矩阵
a = np.column_stack((a,np.zeros(a.shape[0])))
r = np.zeros(a.shape[1])
r[-1] = 1
a = np.row_stack((a, r))
return a
def trans_cube(cube, x=0, y=0, z=0):
# 旋转立方体
x_ = x / 180 * np.pi
y_ = y / 180 * np.pi
z_ = z / 180 * np.pi
T = trans_base(x_, y_, z_)
cube2 = to_hc(cube)
c2 = np.dot(T, cube2.T).T
return c2[:-1, :-1]
def map_2d(cube, z0):
# 在二维平面上计算视锥体投影
cube_ = cube.copy()
z0_ = max(z0, np.abs(cube_[:, 2].min())) # 确保立方体不会被成像屏幕穿过
cube_[:, 2] = cube_[:, 2] + z0_ # 平移立方体
cube2 = np.dot(np.diag(z0_/cube_[:,2]), cube_)
l = []
for i in range(len(cube_)):
for j in range(len(cube_)):
d = cube_[i] - cube_[j]
if d.dot(d)/1-1 < 1E-10:
tc = np.array([cube2[i], cube2[j]])
l.append(tc)
return cube2, l
def plot_2d(cube, thetax, thetay, thetaz, d, color):
# 旋转并投影
cube_ = trans_cube(cube, thetax, thetay, thetaz) # 为了更加生动,对立方体做了旋转
cube_t = np.dot(trans_cube(cube_, -90, 0, 0), np.diag([1, 1, -1])) # 从物体空间映射至视点空间
cube_2d, edges = map_2d(cube_t, d)
plt.scatter(x=cube_2d[:,0], y=cube_2d[:,1], color=color)
for e in edges:
plt.plot(e[:,0], e[:,1], c='grey', alpha=0.3)
def plot_3d(cube, thetax, thetay, thetaz, color):
# 立方体矩阵
cube_ = trans_cube(cube, thetax, thetay, thetaz) # 为了更加生动,对立方体做了旋转
x, y, z = cube_[:,0], cube_[:,1], cube_[:,2]
fig=plt.figure()
ax2 = Axes3D(fig)
ax2.scatter3D(x, y, z, color=color, s=30)
for i in range(len(cube_)):
for j in range(len(cube_)):
# 绘制棱
d = cube_[i] - cube_[j]
if d.dot(d)/1-1 < 1E-10:
tc = np.array([cube_[i], cube_[j]])
tx, ty, tz = tc[:,0], tc[:,1], tc[:,2]
ax2.plot3D(tx, ty, tz, alpha=0.3, c='grey', lw=2)
fig.show()
# 定义正方体顶角的颜色
color = ['red', 'orange', 'limegreen', 'cyan', 'blue', 'royalblue', 'purple', 'deeppink']
# 生成立方体
cube = gen_cube(a=1)
# 绘图
plot_3d(cube, 45, 45, 45, color)
plot_2d(cube, 45, 45, 45, 1.5, color) # 近距离投影
plot_2d(cube, 45, 45, 45, 100, color) # 远距离投影
下图是生成的立方体在三维空间中的呈现:
plot_2d函数会生成该立方体在视点空间中的视锥体投影,如下图所示(视距参数):
如果把视距调远至100,则透视效果将会降低,视锥投影会逼近正射投影,如下图所示: