🧊

相机

目标:增加Scene Graph,创建相机和相机控制器
这一章我们引入类似Unity的Scene Graph,并以组件的形式提供相机和相机控制器
本章新增代码:
📦Ash ┣ 📂samples ┃ ┗ 📜hello_camera.cpp ┣ 📂src ┃ ┣ 📂device ┃ ┃ ┣ 📜input_manager.cpp ┃ ┃ ┗ 📜input_manager.h ┃ ┗ 📂world ┃ ┃ ┣ 📂components ┃ ┃ ┃ ┣ 📜camera_component.cpp ┃ ┃ ┃ ┣ 📜camera_component.h ┃ ┃ ┃ ┣ 📜camera_controller_component.cpp ┃ ┃ ┃ ┣ 📜camera_controller_component.h ┃ ┃ ┃ ┣ 📜transform_component.cpp ┃ ┃ ┃ ┗ 📜transform_component.h ┃ ┃ ┣ 📜component.h ┃ ┃ ┣ 📜game_object.cpp ┃ ┃ ┣ 📜game_object.h ┃ ┃ ┣ 📜world.cpp ┃ ┃ ┗ 📜world.h ┗ 📂tests ┗ 📜world_test.cpp
World:
class World { public: GameObjectPtr create_game_object(const std::string& name, const vec3& location, const quat& rotation = quat(1.f, 0.f, 0.f, 0.f), const vec3& scale = vec3(1.0f)); void destroy_game_object(GameObjectPtr ptr); void update(float dt); private: stdext::slot_map<GameObject> game_objects; };
这里我们使用了slot map数据结构来存储GameObject,这种数据结构在保证O(1)查询效率的同时,还可以防止野指针问题,具体介绍参见C++Now 2017: Allan Deutsch “The Slot Map Data Structure"
GameObject:
using GameObjectPtr = SlotMapPtr<GameObject>; class GameObject { public: GameObject() = default; void update(float dt); void set_parent(GameObjectPtr new_parent); [[nodiscard]] GameObjectPtr get_parent() const { return parent; } void add_child(GameObjectPtr child); void remove_child(GameObjectPtr child); [[nodiscard]] const std::vector<GameObjectPtr>& get_children() const { return children; } template <class T> T* add_component(); template <class T> void remove_components(); template <class T> bool has_component() const; template <class T> T* get_component() const; template <class T> std::vector<T*> get_components() const; [[nodiscard]] World* get_world() const { return world; } [[nodiscard]] const std::string& get_name() const { return name; } [[nodiscard]] TransformComponent* get_transform() const; private: World* world = nullptr; std::string name; GameObjectPtr self; GameObjectPtr parent; std::vector<GameObjectPtr> children; std::vector<std::shared_ptr<Component>> components; void on_destroy(); void remove_components(const std::type_info& type); friend class World; };
这里和Unity有一点差别,Unity是将parentchildren放到Transform中的,我们放到了GameObject
Component:
class Component { public: virtual ~Component() = default; virtual void on_create() { } virtual void on_destroy() { } virtual void update(float dt) { } [[nodiscard]] GameObjectPtr get_owner() const { return owner; } [[nodiscard]] World* get_world() const { assert(owner); return owner->get_world(); } protected: GameObjectPtr owner; friend GameObject; };
CameraComponent:
class CameraComponent : public Component { public: float aspect_ratio = 1.0f; float fov = std::numbers::pi * 0.5f; float near = 0.1f; float far = 512.f; void set_eye_at_up(vec3 eye, vec3 at, vec3 up); void set_look_direction(vec3 forward, vec3 up); [[nodiscard]] mat4 get_view_matrix() const; [[nodiscard]] mat4 get_projection_matrix() const { return glm::perspectiveLH(fov, aspect_ratio, near, far); } [[nodiscard]] mat4 get_view_projection_matrix() const { return get_projection_matrix() * get_view_matrix(); } };
这里只考虑透视投影(Perspective Projection),可以返回视图矩阵(View Matrix)和投影矩阵(Projection Matrix)
CameraControllerComponent:
class FlyCameraControllerComponent : public Component { public: float pitch = 0.f; float yaw = 0.f; vec2 look_sensitivity = vec2(0.001f, 0.001f); float move_speed = 10.f; void update(float dt) override; }; class OrbitCameraControllerComponent : public Component { public: float pitch = 0.f; float yaw = 0.f; vec3 pivot = vec3(0.f); float distance = 20.f; vec2 look_sensitivity = vec2(0.001f, 0.001f); float scroll_sensitivity = 1.f; float min_distance = 1.f; float max_distance = 100.f; void update(float dt) override; };
这里我们提供两种相机控制器,类似Unity/UE4场景相机的飞行相机(Fly Camera),和类似Blender默认相机的轨道相机(Orbit Camera)
这一章的示例代码位于samples/hello_camera.cpp,运行效果:
notion image
通过左上角的”Fly Camera”和”Orbit Camerea”按钮,可以在两种相机控制器间切换