OpenGL里相机的运动控制

相机的核心构造一个是glm::lookAt函数,一个是glm::perspective函数,本文相机的一切运动都在于如何构建相应的参数传入上述两个函数里。

cpp 复制代码
glm::mat4 glm::lookAt(
    glm::vec3 const &eye,//相机所在位置
    glm::vec3 const &center,//要凝视的点
    glm::vec3 const &up //相机上向量
);
cpp 复制代码
glm::mat4 perspective(
float fovy, 
float aspect, 
float near, 
float far);

首先我们通常默认相机的Front向量为(0,0,-1),位置eye为(0,0,0),则center=eye+Front=(0,0,-1)。

默认up向量为(0,1,0),通过Front向量和up向量可以叉积出right向量。

1.视角抬起与左右环视

通过修改Front向量即可。简单点可直接用欧拉角,相机引入yaw跟pitch,麻烦点就直接用向量做。可知相机自身的Front向量,right向量以及叉积出来的Up向量(注意不是up向量),这三组向量构成一组坐标基。

cpp 复制代码
    //上下抬动theta
    Front=Front*cos(theta)+Up*sin(theta)
    center=eye+Front;
    //up 不变,eye不变
    
    //上下旋转动beta
    Front=Front*cos(beta)+right*sin(beta)
    center=eye+Front;
    //up 不变,eye不变

2.视角平移

前后平移即通过Front向量平移,左右平移即通过right向量平移。将eye跟center加减Front向量和right向量即可实现平移。

cpp 复制代码
    //前后方向平移
    eye+=Front*k;//k为平移系数
    center+=Front*k;
    //up 不变
    
    //左右方向平移
    eye+=right*k;//k为平移系数
    center+=right*k;
    //up 不变

3.zoomIn/zoomOut

1)普通的视野变换

直接通过修改fov即可,fov越大则视野越宽看到的东西更多,但是屏幕上呈现的物体会变小;反之则视野越窄看到的东西更少,但是屏幕上呈现的物体会变大

2)在鼠标位置进行区域的视野放大缩小

这时候直接修改fov就不管用了,需要通过平移相机的方式来实现。

获取鼠标所在点映射的世界空间点P,然后让相机沿着有eye与P两点构成的向量前后移动即可达到zoomIn/zoomOut的效果。其本质还是利用了相似的性质,这种实现方法可以巧妙地保证鼠标所在位置对应的三维空间点永远会在鼠标所对应的屏幕像素点上。

cpp 复制代码
    glm::vec3 dir=P-eye;
    //k为视野缩放系数,正数时zoomIn视野变大,负数则相反
    eye+=dir*k;
    center+=dir*k;
    //up 不变

4.聚焦到某个物体

首先获取到这个物体的包围盒中心点P,然后用相机当前的Front向量与之加减获得到新的相机的eye。然后将相机的center设置为P即可。

cpp 复制代码
    //k为系数,可以根据包围盒的大小设置,包围盒越大可以让相机离远点    
     eye=k*(P-Front);
     center=P;
    //up 不变

5.绕某个物体旋转

可以先聚焦到这个物体。接下来获取到这个物体的包围盒中心点P,这个P可固定为相机center,然后P与eye的距离为r。然后如果向上旋转theta,相机的eye会绕着以P为球心,r为半径的面沿着相机当前Up向量方向旋转theta。如果向右旋转,相机的eye会绕着以P为球心,r为半径的面沿着相机当前right向量方向旋转beta。当然,如果相机引入yaw跟pitch的话会相对更简单一些。

cpp 复制代码
// 计算单位球面上点P绕切线n旋转theta角后的新坐标
// O: 球心
// P: 球面上的点
// n: 切线方向向量(需单位化且与OP垂直)
// theta: 旋转角度(弧度)
glm::vec3 rotatePointOnSphere(
    const glm::vec3& O, 
    const glm::vec3& P, 
    const glm::vec3& n, 
    float theta
) 
{
    // 验证输入条件
    glm::vec3 OP = P - O;
    float opLength = glm::length(OP);
    
    // 检查是否为单位球面(允许微小误差)
    if (std::abs(opLength - 1.0f) > 1e-6f) {
        std::cerr << "警告:输入点不在单位球面上,将进行归一化处理" << std::endl;
    }
    
    // 检查切线是否与半径垂直
    float dotProduct = glm::dot(OP, n);
    if (std::abs(dotProduct) > 1e-6f) {
        std::cerr << "警告:输入的切线方向不与半径垂直,将重新计算垂直分量" << std::endl;
    }
    
    // 确保OP是单位向量
    glm::vec3 unitOP = glm::normalize(OP);
    
    // 确保n是单位向量且与OP垂直
    glm::vec3 tangent = glm::normalize(n - dotProduct * unitOP);
    
    // 创建旋转四元数:绕切线方向旋转theta角
    glm::quat rotation = glm::angleAxis(theta, tangent);
    
    // 执行旋转:首先将点平移到原点,旋转后再平移回球心
    glm::vec3 P_origin = P - O;  // 点P相对于球心的坐标
    glm::vec3 P_rotated_origin = rotation * P_origin;  // 旋转
    glm::vec3 P_rotated = P_rotated_origin + O;  // 平移回球心
    
    // 由于浮点误差,可能需要重新归一化以确保在单位球面上
    return O + glm::normalize(P_rotated - O);
}

//绕P点上下转动theta
center=P;
eye=rotatePointOnSphere(P,eye,Up,theta);
//up向量不变

//绕P点左右转动beta
center=P;
eye=rotatePointOnSphere(P,eye,right,beta);
//up向量不变
相关推荐
格林威6 小时前
Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现卫星图像识别(C#代码,UI界面版)
人工智能·深度学习·数码相机·yolo·计算机视觉
安卓开发者6 小时前
Android CameraX 使用指南:简化相机开发
android·数码相机
格林威21 小时前
Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现沙滩小人检测识别(C#代码UI界面版)
人工智能·深度学习·数码相机·yolo·计算机视觉
格林威1 天前
Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现轮船检测识别(C#代码UI界面版)
人工智能·深度学习·数码相机·yolo·视觉检测
lqjun08271 天前
相机内外参矩阵:从3D世界坐标到2D像素坐标变换
数码相机·3d·矩阵
格林威2 天前
Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现持械检测(C#代码,UI界面版)
人工智能·深度学习·数码相机·yolo·计算机视觉
ZPC82102 天前
相机ROI 参数
数码相机
lingling0092 天前
精准扫描,驱动未来:迁移科技3D视觉系统在工业自动化中的革命性应用
人工智能·数码相机
Cedric11133 天前
显微科研中的关键选择:不同显微镜相机技术特性与应用适配性全面解析
数码相机