从速度向量进行OpenGL旋转

1 投票
1 回答
1428 浏览
提问于 2025-04-18 12:59

这应该很简单,但我到处找都没找到一个我能理解的简单解释。我有一个物体,我想在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 个回答

0

这里有一个有用的概念要记住:线性变换也可以理解为坐标系的变化。换句话说,不用想象点在坐标系内被改变,你可以想象这些点保持不动,但它们的坐标在新的坐标系中被表达。当你看到表示变换的矩阵时,这个新坐标系的基向量就是矩阵的列向量。

接下来,这个新坐标系的基向量被称为 bxbybz。由于旋转矩阵的列需要是正交归一的,所以 bxbybz 必须形成一个正交归一的向量组。

在这个例子中,原来的圆锥体是沿着 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) 的点积为零,这意味着这些向量是正交的。

虽然在这些向量(或其他变体)之间的选择大多是随意的,但要小心不要选择到一个退化向量。例如,如果 vxvy 都为零,使用第一个向量就不太合适。为了避免选择到一个(接近)退化的向量,一个简单的策略是:如果 vz 小于 vxvy,就使用这三个向量中的第一个,否则使用其他两个中的一个。

有了两个新的基向量,第三个向量就是这两个向量的叉积:

bx = by x bz

剩下的就是用列向量 bxbybz 填充旋转矩阵,这样旋转矩阵就完成了:

    [ 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 ]

撰写回答