关于另一个向量旋转一个向量

5

我正在为OpenGL编写一个3D向量类。 如何将向量v1绕另一个向量v2旋转角度A?


“沿着”一个向量旋转是什么意思? - ChrisF
我是指“反对”或“绕过”,抱歉我的英语不太好。 - YAHOOOOO
我建议你读一本3D数学入门书,而不是每当你遇到问题时在这里提问。关于这个主题一个非常好的介绍可以在http://www.amazon.com/Primer-Graphics-Development-Wordware-Library/dp/1556229119找到。 - Jukka Dahlbom
5个回答

6

您可能会发现四元数是一种更优雅和高效的解决方案。


看到最近这个答案被顶起来了,我想提供一个更加全面的答案。这个答案可以在不完全理解四元数数学含义的情况下使用。我假设(鉴于C++标签)你有类似于Vector3类的东西,其中包含“显而易见”的函数,比如innercross*=标量运算符等。
#include <cfloat>
#include <cmath>

...

void make_quat (float quat[4], const Vector3 & v2, float angle)
{
    // BTW: there's no reason you can't use 'doubles' for angle, etc.
    // there's not much point in applying a rotation outside of [-PI, +PI];
    // as that covers the practical 2.PI range.

    // any time graphics / floating point overlap, we have to think hard
    // about degenerate cases that can arise quite naturally (think of
    // pathological cancellation errors that are *possible* in seemingly
    // benign operations like inner products - and other running sums).

    Vector3 axis (v2);

    float rl = sqrt(inner(axis, axis));
    if (rl < FLT_EPSILON) // we'll handle this as no rotation:
    {
        quat[0] = 0.0, quat[1] = 0.0, quat[2] = 0.0, quat[3] = 1.0;
        return; // the 'identity' unit quaternion.
    }

    float ca = cos(angle);

    // we know a maths library is never going to yield a value outside
    // of [-1.0, +1.0] right? Well, maybe we're using something else -
    // like an approximating polynomial, or a faster hack that's a little
    // rough 'around the edge' cases? let's *ensure* a clamped range:
    ca = (ca < -1.0f) ? -1.0f : ((ca > +1.0f) ? +1.0f : ca);

    // now we find cos / sin of a half-angle. we can use a faster identity
    // for this, secure in the knowledge that 'sqrt' will be valid....

    float cq = sqrt((1.0f + ca) / 2.0f); // cos(acos(ca) / 2.0);
    float sq = sqrt((1.0f - ca) / 2.0f); // sin(acos(ca) / 2.0);

    axis *= sq / rl; // i.e., scaling each element, and finally:

    quat[0] = axis[0], quat[1] = axis[1], quat[2] = axis[2], quat[3] = cq;
}

因此,float quat[4] 包含一个表示旋转轴和角度的单位四元数,给定原始参数 (, v2, A)

下面是四元数乘法的例程。SSE/SIMD 可能可以加速此过程,但大多数情况下复杂的变换和光照通常由 GPU 驱动。如果你记得 复数 乘法有点奇怪,那么 四元数 乘法就更奇怪了。复数乘法是可交换的操作:a*b = b*a。四元数甚至不能保持这个属性,即 q*p != p*q

static inline void
qmul (float r[4], const float q[4], const float p[4])
{
    // quaternion multiplication: r = q * p

    float w0 = q[3], w1 = p[3];
    float x0 = q[0], x1 = p[0];
    float y0 = q[1], y1 = p[1];
    float z0 = q[2], z1 = p[2];

    r[3] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
    r[0] = w0 * x1 + x0 * w1 + y0 * z1 - z0 * y1;
    r[1] = w0 * y1 + y0 * w1 + z0 * x1 - x0 * z1;
    r[2] = w0 * z1 + z0 * w1 + x0 * y1 - y0 * x1;
}

最后,使用四元数旋转三维“向量”v(或者如果您喜欢,用向量表示的被称为v1的“点”),公式如下:v' = q * v * conjugate(q)。四元数有共轭,类似于复数。以下是程序:

static inline void
qrot (float v[3], const float q[4])
{
    // 3D vector rotation: v = q * v * conj(q)

    float r[4], p[4];

    r[0] = + v[0], r[1] = + v[1], r[2] = + v[2], r[3] = +0.0;
    glView__qmul(r, q, r);

    p[0] = - q[0], p[1] = - q[1], p[2] = - q[2], p[3] = q[3];
    glView__qmul(r, r, p);

    v[0] = r[0], v[1] = r[1], v[2] = r[2];
}

将所有内容整合在一起。显然,您可以在适当的地方使用static关键字。现代优化编译器可能会忽略inline提示,具体取决于它们自己的代码生成启发式算法。但是现在让我们专注于正确性:

如何将向量v1绕另一个向量v2旋转角度A?

假设有某种Vector3类,并且(A)以弧度为单位,我们希望得到表示绕轴v2旋转角度(A)的四元数,并将该四元数旋转应用于v1以获得结果:

float q[4]; // we want to find the unit quaternion for `v2` and `A`...

make_quat(q, v2, A);

// what about `v1`? can we access elements with `operator [] (int)` (?)
// if so, let's assume the memory: `v1[0] .. v1[2]` is contiguous.
// you can figure out how you want to store and manage your Vector3 class.

qrot(& v1[0], q);

// `v1` has been rotated by `(A)` radians about the direction vector `v2` ...

这是Beta文档网站上的人们希望看到扩展的内容吗?我对其要求、期望的严格程度等方面并不十分清楚。

我个人认为这是出于各种原因而最好的方法。可惜你回答得不够快,否则你可能会成为被选中的答案。 - andand

6
这可能会有所帮助:
double c = cos(A);
double s = sin(A);
double C = 1.0 - c;

double Q[3][3];
Q[0][0] = v2[0] * v2[0] * C + c;
Q[0][1] = v2[1] * v2[0] * C + v2[2] * s;
Q[0][2] = v2[2] * v2[0] * C - v2[1] * s;

Q[1][0] = v2[1] * v2[0] * C - v2[2] * s;
Q[1][1] = v2[1] * v2[1] * C + c;
Q[1][2] = v2[2] * v2[1] * C + v2[0] * s;

Q[2][0] = v2[0] * v2[2] * C + v2[1] * s;
Q[2][1] = v2[2] * v2[1] * C - v2[0] * s;
Q[2][2] = v2[2] * v2[2] * C + c;

v1[0] = v1[0] * Q[0][0] + v1[0] * Q[0][1] + v1[0] * Q[0][2];
v1[1] = v1[1] * Q[1][0] + v1[1] * Q[1][1] + v1[1] * Q[1][2];
v1[2] = v1[2] * Q[2][0] + v1[2] * Q[2][1] + v1[2] * Q[2][2];

我不知道这里哪里出了问题,但这个结果是错误的。 - Andry
此答案修改自 https://dev59.com/cWTWa4cB1Zd3GeqPACPq#10711058 - Drise

4

3
我在这里找到了这个链接:http://steve.hollasch.net/cgindex/math/rotvec.html
let
    [v] = [vx, vy, vz]      the vector to be rotated.
    [l] = [lx, ly, lz]      the vector about rotation
          | 1  0  0|
    [i] = | 0  1  0|           the identity matrix        
          | 0  0  1|

          |   0  lz -ly |
    [L] = | -lz   0  lx |
          |  ly -lx   0 |

    d = sqrt(lx*lx + ly*ly + lz*lz)
    a                       the angle of rotation

then

矩阵操作给出:
[v] = [v]x{[i] + sin(a)/d*[L] + ((1 - cos(a))/(d*d)*([L]x[L]))} 

我编写了自己的Matrix3类和Vector3Library,实现了这个向量旋转。它完美地工作着。我使用它来避免在摄像机视野之外绘制模型。
我想这就是“使用3D旋转矩阵”的方法。我快速浏览了四元数,但从未使用过,所以坚持使用我能理解的东西。

3
最容易理解的方法是旋转坐标轴,使向量v2与Z轴对齐,然后绕Z轴旋转A度,再将Z轴旋转回与v2对齐。
当您为这三个操作编写旋转矩阵时,您可能会注意到您需要连续应用三个矩阵。为达到相同的效果,您可以将这三个矩阵相乘。

1
我不会点踩,但这个方案过于复杂。一个更简单的解决方案,我认为实际上更容易理解(但这只是我的看法),就是使用Brett Hale建议的四元数方法。 - andand
1
@andand:四元数很好、很简单,前提是你已经理解了四元数。但看起来发帖者在普通的三维线性代数上遇到了困难,现在学习四元数可能还有些困难。 - thiton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接