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向量不变
相关推荐
yunjingtianhe1 天前
EL隐裂检测仪的优势—精准捕捉细微隐裂、微小断栅等隐蔽性极强的隐患
数码相机
PHOSKEY1 天前
3D工业相机如何“读透”每一个字符?快速识别、高精度3D测量
数码相机·3d
听风吹雨yu2 天前
STM32F407-LWIP-Onvif协议控制海康相机
stm32·嵌入式硬件·数码相机
光_辉3 天前
使用代码访问海康相机
数码相机
格林威3 天前
相机的“对焦”和“变焦”,这二者有什么区别?
开发语言·人工智能·数码相机·opencv·算法·计算机视觉·视觉检测
光_辉3 天前
关于相机拍照的一些说明
数码相机
光_辉3 天前
调用海康相机实现事件监测并拍照
数码相机
JMchen1233 天前
Android相机硬件抽象层(HAL)逆向工程:定制ROM的相机优化深度指南
android·开发语言·c++·python·数码相机·移动开发·android studio
psp之魂3 天前
Unity2d Cinemachine Confine 2d失效的问题
数码相机
中达瑞和-高光谱·多光谱3 天前
中达瑞和VIX-N220推扫式高光谱相机:助力中医舌苔面诊迈向客观化、智能化新时代
数码相机