常见的两种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();
}
如果有什么问题,欢迎留言评论。
goudan-er GRAPHICS
Graphics OpenGL