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向量不变
相关推荐
博图光电1 小时前
当常规视觉 “束手无策”,BTIR 短波红外相机开启 “穿透式” 成像革命
数码相机
Tel1992530800410 小时前
CCD相机同步外触发拍照抓拍识别高速脉冲计数器信号采集模块
单片机·数码相机·物联网·自动化·工业自动化·仪器仪表
TTGGGFF13 小时前
具身智能:零基础入门睿尔曼机械臂(六)——手眼标定代码库详解,从原理到实践
数码相机·机械臂·具身智能
SPFFC1893803305313 小时前
抗干扰磁环排线 | Magnetic Ring Flat Cable - 高磁导率EMI抑制解决方案
服务器·数码相机·车载系统·机器人·音频·智能音箱·智能电视
carver w13 小时前
轮询和回调 --SDK
数码相机
字节数据平台14 小时前
6000字技术向拆解 “大晓机器人”携手火山引擎多模态数据湖探索视频处理新路径
人工智能·数码相机
码界奇点1 天前
Python与OpenCV集成海康威视工业相机从基础配置到高级应用的全方位指南
python·数码相机·opencv·相机·python3.11
broad-sky1 天前
Ubuntu上查看USB相机连接的是哪个口,如何查看
linux·数码相机·ubuntu
TNTLWT1 天前
机器视觉:工业镜头
数码相机
PHOSKEY1 天前
3D工业相机量化管控耳机充电弹针关键尺寸
数码相机·3d