如何按给定方向旋转矢量

 中国有程序猿 发布于 2023-02-06 16:03

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

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 - 像矢量一样(1,0,0).这应该将"圆顶"旋转到x方向,如下所示:

在此输入图像描述

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

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

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

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

    同样,在这种情况下的归一化并不是必需的,但是如果y或者z不是单位向量,那么它就是.

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

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

    2023-02-06 16:04 回答
  • 您需要创建一个旋转矩阵.因此,您需要一个身份矩阵.用这样创建它

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

    现在你可以旋转vectorspacec了

    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扩展(实验)<glm/gtx/rotate_vector.hpp>

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

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

    2023-02-06 16:04 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有