【问题标题】:How to rotate a vector by a given direction如何按给定方向旋转矢量
【发布时间】:2014-01-04 16:02:22
【问题描述】:

我在一个循环中创建一些随机向量/方向作为圆顶形状,如下所示:

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) 的圆顶形状:

现在我想将vec-Vector 旋转给定的方向-Vector,如(1,0,0)。 这应该像这样将“圆顶”旋转到 x 方向:

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

【问题讨论】:

    标签: opengl matrix linear-algebra glm-math


    【解决方案1】:

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

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

    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 轴,我们可以将新的 Y 轴与新的 Z 轴交叉。这会产生一个垂直于其他两个轴的向量:

    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 &lt; 4 &amp;&amp; ...) 或通过增加每次迭代接受点的半径。当它 >= 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;
            // ...
        }
        // ...
    }
    

    【讨论】:

    • 感谢您的回答。但我不完全明白在这里做什么:if (input.x == 0 &amp;&amp; input.z == 0) {...} 那里的 x、y、z 值是多少?你的意思是input.x,input.y,input.z?以及在这种特殊情况下如何计算 x,y,z 轴?
    • @Bastl 抱歉,我的变量名可能应该更具描述性。 input.x 与后面定义的 vec3 x 无关。 GLM 使用多种方法提供对vec3 组件的访问。 input.xinput.rinput[0]都指向量的第一个分量,input.yinput.ginput[1]指第二个分量,依此类推
    • @Bastl 您提到的if (input.x == 0 ...) 仅当input 的X 和Z 坐标均为零时才执行第一块代码,这意味着向量必须在完全相同的方向(或相反)方向)作为 Y 轴。
    • 对不起,我的意思是 If 中的值:input = vec3(-x, -y, z); 而不是 input.x "notation" :) 如果 y 指向 -1:input = vec3(-x, -y, z); 代码将被执行。那里的x,y,z是什么?在这种情况下如何计算 x,y,z 轴?
    • 哎呀,我刚刚注意到我有 input = vec3(-x, -y, z); 而不是 input = vec3(-input.x, -input.y, input.z); 这可能就是那个位令人困惑的原因吧?
    【解决方案2】:

    您需要创建一个旋转矩阵。因此,您需要一个身份矩阵。像这样创建它

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

    现在你可以旋转向量空间了

    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 是个好主意,因为较小维度的向量无论如何都会被转换为齐次顶点坐标。

    编辑:您也可以尝试使用GTX Extensions (Experimental),包括&lt;glm/gtx/rotate_vector.hpp&gt;

    编辑 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);
    

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

    【讨论】:

    • 嗨。感谢您的回复。问题是我不知道方向向量。我需要一种计算方法来找出旋转的正确角度。
    • @Bastl 你指的是哪个方向向量?要旋转整个球体,只需使用glUniformMatrix4fv(uniformRotMatrix, 1, GL_FALSE, glm::value_ptr(rotationMat)) 将旋转矩阵传递给您的顶点着色器。然后着色器使用该矩阵旋转每个顶点,整个球体将被旋转。
    • 我有一个函数可以生成这个圆顶形状。我想将圆顶的方向向量传递给这个函数。然后圆顶应该指向这个方向。
    • @Bastl 您可以使用glm::angle (up, direction) 获取角度并包含 GLM_GTX_vector_angle 扩展
    • 叉积向量适用于 (0,0,+-1)、(+-1,0,0) 等方向,但不适用于 (0,-1,0)。 glm::vec3 cross = glm::cross(glm::vec3(0, 1, 0), glm::vec3(0, -1, 0)); glm::mat4 rotationMat = glm::rotate(glm::mat4(1.0f), 90.0f, cross); dir = glm::vec3(rotationMat * glm::vec4(dir, 1.0f)); 生成的圆顶指向上方而不是下方
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-08
    相关资源
    最近更新 更多