从速度向量进行OpenGL旋转
这应该很简单,但我到处找都没找到一个我能理解的简单解释。我有一个物体,我想在OpenGL中把它表示成一个圆锥。这个物体有x、y、z坐标,还有一个速度向量vx、vy和vz。这个圆锥应该指向速度向量的方向。
所以,我觉得我的PyOpenGL代码应该像这样:
glPushMatrix()
glTranslate(x, y, z)
glPushMatrix()
# do some sort of rotation here #
glutSolidCone(base, height, slices, stacks)
glPopMatrix()
glPopMatrix()
这样写对吗(到目前为止)?我在“# 在这里做某种旋转 #"的位置应该放什么呢?
在我的世界里,Z轴是向上的(0, 0, 1),而且在没有任何旋转的情况下,我的圆锥也是这样指向的。
好的,Reto Koradi的回答似乎是我应该采取的方法,但我对一些实现细节不太确定,而且我的代码没有正常工作。
如果我理解正确,旋转矩阵应该是一个4x4的矩阵。Reto给我展示了如何得到一个3x3的矩阵,所以我假设这个3x3的矩阵应该是4x4单位矩阵的左上角部分。我的代码是这样的:
import numpy as np
def normalize(v):
norm = np.linalg.norm(v)
if norm > 1.0e-8: # arbitrarily small
return v/norm
else:
return v
def transform(v):
bz = normalize(v)
if (abs(v[2]) < abs(v[0])) and (abs(v[2]) < abs(v[1])):
by = normalize(np.array([v[1], -v[0], 0]))
else:
by = normalize(np.array([v[2], 0, -v[0]]))
#~ by = normalize(np.array([0, v[2], -v[1]]))
bx = np.cross(by, bz)
R = np.array([[bx[0], by[0], bz[0], 0],
[bx[1], by[1], bz[1], 0],
[bx[2], by[2], bz[2], 0],
[0, 0, 0, 1]], dtype=np.float32)
return R
而这段代码是如何插入到渲染代码中的:
glPushMatrix()
glTranslate(x, y, z)
glPushMatrix()
v = np.array([vx, vy, vz])
glMultMatrixf(transform(v))
glutSolidCone(base, height, slices, stacks)
glPopMatrix()
glPopMatrix()
不幸的是,这样做不行。我的测试用例中的圆锥指向不正确,我无法找到问题所在。如果没有“glutMultMatrixf(transform(v)”这一行,圆锥就会像预期的那样沿着z轴对齐。
现在可以工作了。Reto Koradi正确指出,旋转矩阵需要转置,以匹配OpenGL的列顺序。代码应该是这样的(在优化之前):
def transform(v):
bz = normalize(v)
if (abs(v[2]) < abs(v[0])) and (abs(v[2]) < abs(v[1])):
by = normalize(np.array([v[1], -v[0], 0]))
else:
by = normalize(np.array([v[2], 0, -v[0]]))
#~ by = normalize(np.array([0, v[2], -v[1]]))
bx = np.cross(by, bz)
R = np.array([[bx[0], by[0], bz[0], 0],
[bx[1], by[1], bz[1], 0],
[bx[2], by[2], bz[2], 0],
[0, 0, 0, 1]], dtype=np.float32)
return R.T
1 个回答
这里有一个有用的概念要记住:线性变换也可以理解为坐标系的变化。换句话说,不用想象点在坐标系内被改变,你可以想象这些点保持不动,但它们的坐标在新的坐标系中被表达。当你看到表示变换的矩阵时,这个新坐标系的基向量就是矩阵的列向量。
接下来,这个新坐标系的基向量被称为 bx
、by
和 bz
。由于旋转矩阵的列需要是正交归一的,所以 bx
、by
和 bz
必须形成一个正交归一的向量组。
在这个例子中,原来的圆锥体是沿着 z 轴方向的。因为你想让圆锥体沿着 (vx, vy, vz)
的方向,所以我们用这个向量作为新坐标系的 z 轴。为了得到一个正交归一的坐标系,接下来要做的就是将这个向量归一化,也就是调整它的长度为 1:
[vx]
bz = normalize([vy])
[vz]
由于圆锥体是旋转对称的,剩下的两个基向量的选择其实并不重要,只要它们都与 bz
正交,并且彼此之间也正交。找到一个与给定向量正交的简单方法是保持一个坐标为 0
,交换另外两个坐标,并改变其中一个坐标的符号。再次强调,这个向量也需要归一化。我们可以用这种方法选择的向量包括:
[ vy] [ vz] [ 0 ]
by = normalize([-vx]) by = normalize([ 0 ]) by = normalize([ vz])
[ 0 ] [-vx] [-vy]
这些向量与 (vx, vy, vz)
的点积为零,这意味着这些向量是正交的。
虽然在这些向量(或其他变体)之间的选择大多是随意的,但要小心不要选择到一个退化向量。例如,如果 vx
和 vy
都为零,使用第一个向量就不太合适。为了避免选择到一个(接近)退化的向量,一个简单的策略是:如果 vz
小于 vx
和 vy
,就使用这三个向量中的第一个,否则使用其他两个中的一个。
有了两个新的基向量,第三个向量就是这两个向量的叉积:
bx = by x bz
剩下的就是用列向量 bx
、by
和 bz
填充旋转矩阵,这样旋转矩阵就完成了:
[ bx.x by.x bz.x ]
R = [ bx.y by.y bz.y ]
[ bx.z by.z bz.z ]
如果你需要一个 4x4 的矩阵,比如因为你在使用旧版的固定功能 OpenGL 管道,你可以将其扩展为 4x4 矩阵:
[ bx.x by.x bz.x 0 ]
R = [ bx.y by.y bz.y 0 ]
[ bx.z by.z bz.z 0 ]
[ 0 0 0 1 ]