常见的两种Camera实现

在游戏中有两种常见的Camera。基于OpenGL,使用glm数学库,给出简单的实现。

两种Camera都继承自 BaseBamera。代码如下:

#pragma once

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class BaseCamera {
public:
    BaseCamera(const glm::vec3 & _postion, const glm::vec3 & _target, const glm::vec3 & _look, const glm::vec3 & _up);
    virtual ~BaseCamera();

    virtual void update() = 0;
    void rotate(const float _pitch, const float _yaw, const float _roll);

    glm::mat4 get_rotate_matrix() const;

    void set_projection(const float _fov, const float _aspec_ratio, const float _z_near, const float _z_far);

    void set_fov(const float f);
    float get_fov() const;
    float get_aspect_ratio() const;

    glm::mat4 get_view_matrix() const;
    glm::mat4 get_projection_matrix() const;

    //void cal_frustum_planes();
    //void get_frustum_planes(glm::vec4 planes[6]);
    //bool is_point_in_frustum(const glm::vec3 & point);
    //bool is_box_in_frustum(const glm::vec3 & min, const glm::vec3 & box);
    //bool is_sphere_in_frustum(const glm::vec3 & center, const float radius);

protected:
    float pitch, yaw, roll;     // 欧拉角,描述一个相机的方位,pitch表示绕x轴旋转角度,yaw表示绕y轴旋转角度,pitch表示绕z轴旋转角度
    float fovy, aspect_ratio, z_near, z_far;        // 相机 viewfrustum 描述
    
    // frustum 平头截体的近平面、远平面
    glm::vec3 far_pts[4];
    glm::vec3 near_pts[4];

    // 相机描述
    const glm::vec3 init_look, init_up;     // 初始化的 look 和 up 方向
    glm::vec3 position, target, up;     // camera position, lookat, up
    glm::vec3 look;                 // the direction of camera to target
    glm::vec3 right;                // the direction of camera's right 

    glm::mat4 V;        // view matrix
    glm::mat4 P;        // projection matrix
};
#include <glm/gtx/euler_angles.hpp>

#include <math.h>
#include <algorithm>


BaseCamera::BaseCamera(const glm::vec3 & _postion, const glm::vec3 & _target, const glm::vec3 & _look, const glm::vec3 & _up)
    : position(_postion), target(_target), look(_look), up(_up), init_look(_look), init_up(_up)
{
    right = glm::cross(look, up);
    pitch = yaw = roll = 0.0;
}

BaseCamera::~BaseCamera()
{

}

void BaseCamera::rotate(const float _pitch, const float _yaw, const float _roll)
{
    pitch = glm::radians(_pitch);
    yaw = glm::radians(_yaw);
    roll = glm::radians(_roll);

    update();
}

glm::mat4 BaseCamera::get_rotate_matrix() const
{
    return glm::yawPitchRoll(yaw, pitch, roll);
}

void BaseCamera::set_projection(const float _fovy, const float _aspec_ratio, const float _z_near, const float _z_far)
{
    fovy = _fovy;
    aspect_ratio = _aspec_ratio;
    z_near = _z_near;
    z_far = _z_far;
    P = glm::perspective(fovy, aspect_ratio, z_near, z_far);
}

void BaseCamera::set_fov(const float f)
{
    fovy = f;
}

float BaseCamera::get_fov() const
{
    return fovy;
}

float BaseCamera::get_aspect_ratio() const
{
    return aspect_ratio;
}

glm::mat4 BaseCamera::get_view_matrix() const
{
    return V;
}

glm::mat4 BaseCamera::get_projection_matrix() const
{
    return P;
}

Free Camera

参考 OpenGL Development Cookbook - Chapter 2

Free Camera,没有固定的 target,初始化 position、look、up;
target 可以根据 position 以及 look 计算出来,target = position + look * distance
move camera 移动相机位置;
rotate camera 会绕着相机的 position 旋转,look方向也会改变;
move camera 移动 camera,不会影响 look 方向;
zoom camera 缩短 camera 与 target 的距离,注意 camera 的位置不变,相当于看得更近/远一些;

第一人称游戏可以采用 FreeCamera。把camera与模型绑定在一起,模型移动、旋转,相机也会相应移动、旋转。

代码如下:

class FreeCamera
    : public BaseCamera {
public:
    FreeCamera(const glm::vec3 & _position, const glm::vec3 & _look, const glm::vec3 & _up, const float _dist, const float _speed);
    virtual ~FreeCamera();

    virtual void update();
    void rotate(const float _pitch, const float _yaw, const float _roll);

    void walk(const float dt);
    void strafe(const float dt);
    void lift(const float dt);
    void zoom(const float d);

    // set position,重新设置相机位置
    // 联想守望先锋观战模式,切换不同玩家,是在切换相机位置
    // 切换相机位置后,translation 和 欧拉角 重新设置为 0
    void set_position(const glm::vec3 & _position);
    glm::vec3 get_position() const;
    void set_look(const glm::vec3 & _look);
    glm::vec3 get_look() const;

    void set_speed(const float & s);
    float get_speed() const;

protected:
    glm::vec3 translation;
    float dist;     // target 与 position 的距离
    float speed;
};
/**
    Free Camera
**/

#include <glm/gtx/euler_angles.hpp>

#include <math.h>
#include <algorithm>

FreeCamera::FreeCamera(const glm::vec3 & _position, const glm::vec3 & _look, const glm::vec3 & _up, const float _dist, const float _speed)
    : BaseCamera(_position, _position + _look * _dist, _look, _up), dist(_dist), speed(_speed)
{
    translation = glm::vec3(0.0);

    update();
}

FreeCamera::~FreeCamera()
{

}

void FreeCamera::update()
{
    position += translation;
    
    translation=glm::vec3(0); 

    glm::mat4 R = glm::yawPitchRoll(yaw, pitch, roll);
    look = glm::vec3(R * glm::vec4(init_look, 0.0));
    up = glm::vec3(R * glm::vec4(init_up, 0.0));
    right = glm::cross(look, up);
    target = position + look * dist;
    
    V = glm::lookAt(position, target, up);
}

void FreeCamera::rotate(const float _pitch, const float _yaw, const float _roll)
{
    BaseCamera::rotate(_pitch, _yaw, _roll);
}

void FreeCamera::walk(const float dt)
{
    translation += (look * speed * dt);

    update();
}

void FreeCamera::strafe(const float dt)
{
    translation += (right * speed * dt);

    update();
}

void FreeCamera::lift(const float dt)
{
    translation += (up * speed * dt);

    update();
}

void FreeCamera::zoom(const float d)
{
    dist += d;

    update();
}

void FreeCamera::set_position(const glm::vec3 & _position)
{
    position = _position;
    translation = glm::vec3(0.0);
    yaw = pitch = roll = 0.0;

    update();
}

glm::vec3 FreeCamera::get_position() const
{
    return position;
}

void FreeCamera::set_look(const glm::vec3 & _look)
{
    look = _look;
    yaw = pitch = roll = 0.0;

    update();
}

glm::vec3 FreeCamera::get_look() const
{
    return look;
}

void FreeCamera::set_speed(const float & s)
{
    speed = s;
}

float FreeCamera::get_speed() const
{
    return speed;
}

Target Camera

参考 OpenGL Development Cookbook - Chapter 2

Target Camera,有着固定的 target;
rotate camera 会绕着 target 旋转;
move camera 相机position以及target都会移动;
zoom camera 拉近相机与target的距离;

第三人称游戏可以采用 Target Camera。 考虑一般的RPG游戏,此处以 dota2 举例。回忆 dota2或者war3或者其他RPG游戏:
游戏中,相机视角是固定的保持不变;
移动角色,相机的位置会跟随角色移动,但是视角不变;
选中角色情况下,相机的 target 会固定到角色模型;
如果移动鼠标或者鼠标点击游戏场景其他位置,这时相机的 position、target 会变,会发现相机的视角仍然是没有变化的;
游戏中还可以滚动鼠标中键,会调整相机到场景的距离,这实际也就是 target camera 的 zoom 操作。

代码如下:

class TargetCamera
    : public BaseCamera {
public:
    TargetCamera(const glm::vec3 & _positon, const glm::vec3 & _target, const glm::vec3 & _up,
        const float _min_ry = -60.0, const float _max_ry = 60.0, const float _min_dist = 1.0, const float _max_dist = 10.0);
    virtual ~TargetCamera();

    virtual void update();
    void rotate(const float _pitch, const float _yaw, const float _roll);

    // set target,重新设置相机 target
    // 联想 dota2,鼠标点击场景或者其他英雄,重新设置了相机的 target
    // 一般情况下,重置 target,仍保持原视角
    void set_target(const glm::vec3 & _target);
    glm::vec3 get_target() const;

    void pan(const float dx, const float dy);
    void move(const float dx, const float dy, const float dz);
    void zoom(const float d);

protected:
    float min_ry, max_ry;
    float dist;
    float min_dist, max_dist;
};
/**
    Target Camera
**/

#include <glm/gtx/euler_angles.hpp>

#include <math.h>
#include <algorithm>

TargetCamera::TargetCamera(const glm::vec3 & _positon, const glm::vec3 & _target, const glm::vec3 & _up, const float _min_ry /*= -60.0*/, const float _max_ry /*= 60.0*/, const float _min_dist /*= 1.0*/, const float _max_dist /*= 10.0*/)
    : BaseCamera(_positon, _target, glm::normalize(_target - _positon), _up), min_ry(_min_ry), max_ry(_max_ry), min_dist(_min_dist), max_dist(_max_dist)
{
    dist = glm::distance(position, target);
    dist = std::max(min_dist, std::min(dist, max_dist));

    update();
}

TargetCamera::~TargetCamera()
{

}

void TargetCamera::update()
{
    glm::mat4 R = glm::yawPitchRoll(yaw, pitch, roll);
    glm::vec3 T = init_look * dist;
    T = glm::vec3(R * glm::vec4(T, 0.0f));
    
    position = target - T;
    look = glm::normalize(target - position);
    up = glm::vec3(R * glm::vec4(init_up, 0.0));
    right = glm::cross(look, up);

    V = glm::lookAt(position, target, up);
}

void TargetCamera::rotate(const float _pitch, const float _yaw, const float _roll)
{
    float p = std::min(std::max(_pitch, min_ry), max_ry);
    BaseCamera::rotate(p, _yaw, _roll);
}

void TargetCamera::set_target(const glm::vec3 & _target)
{
    target = _target;

    update();
}

glm::vec3 TargetCamera::get_target() const
{
    return target;
}

void TargetCamera::pan(const float dx, const float dy)
{
    glm::vec3 X = right * dx;
    glm::vec3 Y = up * dy;
    
    position += X + Y;
    target += X + Y;

    update();
}
void TargetCamera::move(const float dx, const float dy, const float dz)
{
    glm::vec3 X = right * dx;
    glm::vec3 Y = up * dy;
    glm::vec3 Z = look * dz;
    
    position += X + Y + Z;
    target += X + Y + Z;

    update();
}

void TargetCamera::zoom(const float d)
{
    position += look * d;
    dist = glm::distance(position, target);
    dist = std::max(min_dist, std::min(dist, max_dist));

    update();
}

如果有什么问题,欢迎留言评论。