如何按给定方向旋转矢量

时间:2014-01-04 16:02:23

标签: opengl matrix linear-algebra glm-math

我在循环中创建一些随机向量/方向,如下所示:

void generateDome(glm::vec3 direction)
{
    for(int i=0;i<1000;++i)
    {
        float xDir = randomByRange(-1.0f, 1.0f);
        float yDir = randomByRange(0.0f, 1.0f);
        float zDir = randomByRange(-1.0f, 1.0f);

        auto vec = glm::vec3(xDir, yDir, zDir);
        vec = glm::normalize(vec);

        ...
        //some transformation with direction-vector
     }
     ...
}

这会将矢量创建为+y方向(0,1,0)方向的圆顶形状:

enter image description here

现在我要旋转vec - 矢量按给定方向 - 像(1,0,0)这样的矢量。 这应该将“圆顶”旋转到x方向,如下所示:

enter image description here

我怎样才能做到这一点? (最好用glm)

2 个答案:

答案 0 :(得分:12)

旋转通常使用从起始位置开始的某种偏移(轴角,四元数,欧拉角等)来定义。您正在寻找的内容将更准确地描述(在我看来)作为重新定位。幸运的是,这并不难。你需要的是一个基础变更矩阵。

首先,让我们在代码中定义我们正在使用的内容:

using glm::vec3;
using glm::mat3;

vec3 direction;  // points in the direction of the new Y axis
vec3 vec;        // This is a randomly generated point that we will
                 // eventually transform using our base-change matrix

要计算矩阵,您需要为每个新轴创建单位矢量。从上面的例子可以看出,您希望提供的矢量成为新的Y轴:

vec3 new_y = glm::normalize(direction);

现在,计算X轴和Z轴会更复杂一些。我们知道它们必须彼此正交并且与上面计算的Y轴正交。构造Z轴的最合理的方法是假设旋转发生在由旧Y轴和新Y轴定义的平面中。通过使用交叉积,我们可以计算出该平面的法向量,并将其用于Z轴:

vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));

从技术上讲,这里不需要归一化,因为两个输入向量都已经归一化,但为了清楚起见,我已经离开了它。另请注意,当输入向量与Y轴共线时有一种特殊情况,在这种情况下,上面的叉积是未定义的。解决这个问题的最简单方法是将其视为一种特殊情况。而不是我们到目前为止,我们使用:

if (direction.x == 0 && direction.z == 0)
{
    if (direction.y < 0) // rotate 180 degrees
       vec = vec3(-vec.x, -vec.y, vec.z);

    // else if direction.y >= 0, leave `vec` as it is.
}
else
{
    vec3 new_y = glm::normalize(direction);

    vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));

    // code below will go here.
}

对于X轴,我们可以使用新的Z轴穿过新的Y轴。这产生垂直于其他轴的矢量:

vec3 new_x = glm::normalize(glm::cross(new_y, new_z));

同样,这种情况下的规范化并不是必需的,但如果yz不是单位向量,那么它就是。

最后,我们将新轴向量组合成基础变化矩阵:

mat3 transform = mat3(new_x, new_y, new_z);

将点矢量(vec3 vec)乘以此会在同一位置产生一个新点,但相对于新的基矢量(轴):

vec = transform * vec;

为每个随机生成的点做最后一步,然后就完成了!无需计算旋转角度或类似的东西。

作为旁注,您生成随机单位矢量的方法将偏向远离轴的方向。这是因为选择特定方向的概率与到给定方向上可能的最远点的距离成比例。对于轴,这是1.0。对于例如的方向。 (1, 1, 1),此距离为sqrt(3)。这可以通过丢弃位于单位球体之外的任何向量来解决:

glm::vec3 vec;
do
{
    float xDir = randomByRange(-1.0f, 1.0f);
    float yDir = randomByRange(0.0f, 1.0f);
    float zDir = randomByRange(-1.0f, 1.0f);

    vec = glm::vec3(xDir, yDir, zDir);
} while (glm::length(vec) > 1.0f);  // you could also use glm::length2 instead, and avoid a costly sqrt().

vec = glm::normalize(vec);

这将确保所有方向具有相同的概率,代价是如果您非常不幸,所拾取的点可能一次又一次地位于单位范围之外,并且可能需要很长时间才能生成一个内部。如果这是一个问题,可以修改它以限制迭代:while (++i < 4 && ...)或通过增加每次迭代接受点的半径。当它是&gt; = sqrt(3)时,所有可能的点都将被视为有效,因此循环将结束。这两种方法都会导致远离轴的轻微偏差,但在几乎任何实际情况下都无法检测到。

将上面的所有代码放在一起,结合您的代码,我们得到:

void generateDome(glm::vec3 direction)
{
    // Calculate change-of-basis matrix
    glm::mat3 transform;

    if (direction.x == 0 && direction.z == 0)
    {
        if (direction.y < 0) // rotate 180 degrees
            transform = glm::mat3(glm::vec3(-1.0f, 0.0f  0.0f),
                                  glm::vec3( 0.0f, -1.0f, 0.0f),
                                  glm::vec3( 0.0f,  0.0f, 1.0f));

        // else if direction.y >= 0, leave transform as the identity matrix.
    }
    else
    {
        vec3 new_y = glm::normalize(direction);
        vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
        vec3 new_x = glm::normalize(glm::cross(new_y, new_z));

        transform = mat3(new_x, new_y, new_z);
    }


    // Use the matrix to transform random direction vectors
    vec3 point;
    for(int i=0;i<1000;++i)
    {
        int k = 4; // maximum number of direction vectors to guess when looking for one inside the unit sphere.
        do
        {
            point.x = randomByRange(-1.0f, 1.0f);
            point.y = randomByRange(0.0f, 1.0f);
            point.z = randomByRange(-1.0f, 1.0f);
        } while (--k > 0 && glm::length2(point) > 1.0f);

        point = glm::normalize(point);

        point = transform * point;
        // ...
    }
    // ...
}

答案 1 :(得分:6)

您需要创建一个旋转矩阵。因此,您需要一个身份矩阵。使用

创建它
glm::mat4 rotationMat(1); // Creates a identity matrix

现在您可以使用

旋转矢量空间c
rotationMat = glm::rotate(rotationMat, 45.0f, glm::vec3(0.0, 0.0, 1.0));

这将围绕z轴旋转矢量空间45.0度(如屏幕截图所示)。现在你快完成了。要旋转vec,您可以写

vec = glm::vec3(rotationMat * glm::vec4(vec, 1.0));

注意:因为你有一个4x4矩阵,你需要一个vec4来将它与矩阵相乘。通常,在使用OpenGL时总是使用vec4是一个好主意,因为较小维度的矢量无论如何都将转换为齐次顶点坐标。

编辑:您还可以尝试使用<glm/gtx/rotate_vector.hpp>

GTX Extensions (Experimental)

编辑2:当您想要将圆顶“朝向”给定方向旋转时,您可以使用方向与圆顶“向上”矢量之间的交叉积来获得您的移动轴。假设您想要将圆顶“朝向”(1.0,1.0,1.0)旋转,“向上”方向是(0.0,1.0,0.0)使用:

glm::vec3 cross = glm::cross(up, direction);
glm::rotate(rotationMat, 45.0f, cross);

获取旋转矩阵。叉积返回一个与“向上”和“方向”正交的向量,这是您想要旋转的向量。希望这会有所帮助。