Post Lists

2018년 9월 24일 월요일

FPS Quaternion Camera Implementation (쿼터니언 카메라 구현)

Reference Link:
http://graphics.stanford.edu/courses/cs348a-17-winter/Papers/quaternion.pdf
https://www.gamasutra.com/view/feature/131686/rotating_objects_using_quaternions.php
https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation

Result GIF:


Camera Header:
/*
 This code is originally based on a tutorial code of learnopengl.com.
 I converted this euler camera to quaternion camera
*/

#ifndef __CHAN_CAMERA_H__
#define __CHAN_CAMERA_H__

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

enum class Camera_Movement
{
 FORWARD,
 BACKWARD,
 LEFT,
 RIGHT
};

// Default camera values
const float SPEED = 10.0f;
const float SENSITIVITY = 0.01f;
const float ZOOM = 45.0f;

class chanCamera
{
public:
 glm::vec3 Position;
 glm::quat Orientation;
 float RightAngle;
 float UpAngle;

 // Camera options
 float MovementSpeed;
 float MouseSensitivity;
 float Zoom;

 chanCamera
 (
  glm::vec3 position = glm::vec3(0.f, 0.f, 0.f),
  glm::vec3 up = glm::vec3(0.f, 1.0f, 0.f)
 );
 chanCamera(float posX, float posY, float posZ);

 glm::mat4 GetViewMatrix();
 void ProcessKeyboard(Camera_Movement direction, float deltaTime);
 void ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch = true);
 void ProcessMouseScroll(float yoffset);

private:
 void updateCameraVectors();
};

#endif


Camera Source:
#include "chanCamera.h"
#include <iostream>

chanCamera::chanCamera(glm::vec3 position)
 :
 MovementSpeed(SPEED),
 MouseSensitivity(SENSITIVITY),
 Zoom(ZOOM)
{
 Position = position;
 Orientation = glm::quat(0, 0, 0, -1);
 RightAngle = 0.f;
 UpAngle = 0.f;
 updateCameraVectors();
}

chanCamera::chanCamera(float posX, float posY, float posZ)
 : MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{
 Position = glm::vec3(posX, posY, posZ);
 Orientation = glm::quat(0, 0, 0, -1);
 RightAngle = 0.f;
 UpAngle = 0.f;
 updateCameraVectors();
}

glm::mat4 chanCamera::GetViewMatrix()
{
 // You should know the camera move reversely relative to the user input.
 // That's the point of Graphics Camera
glm::quat reverseOrient = glm::conjugate(Orientation);
 glm::mat4 rot = glm::mat4_cast(reverseOrient);
 glm::mat4 translation = glm::translate(glm::mat4(1.0), -Position);

 return rot * translation;
}

void chanCamera::ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
 float velocity = MovementSpeed * deltaTime;

 glm::quat qF = Orientation * glm::quat(0, 0, 0, -1) * glm::conjugate(Orientation);
 glm::vec3 Front = { qF.x, qF.y, qF.z };
 glm::vec3 Right = glm::normalize(glm::cross(Front, glm::vec3(0, 1, 0)));

 if (direction == Camera_Movement::FORWARD)
  Position += Front * velocity;

 if (direction == Camera_Movement::BACKWARD)
  Position -= Front * velocity;

 if (direction == Camera_Movement::LEFT)
  Position -= Right * velocity;

 if (direction == Camera_Movement::RIGHT)
  Position += Right * velocity;
}

void chanCamera::ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch)
{
 xoffset *= MouseSensitivity;
 yoffset *= MouseSensitivity;

 RightAngle += xoffset;
 UpAngle += yoffset;

 updateCameraVectors();
}

void chanCamera::ProcessMouseScroll(float yoffset)
{
 if (Zoom >= 1.f && Zoom <= 45.f)
  Zoom -= yoffset;

 if (Zoom <= 1.f)
  Zoom = 1.f;

 if (Zoom >= 45.f)
  Zoom = 45.f;
}

void chanCamera::updateCameraVectors()
{
 // Yaw
 glm::quat aroundY = glm::angleAxis(glm::radians(-RightAngle), glm::vec3(0, 1, 0));

 // Pitch
 glm::quat aroundX = glm::angleAxis(glm::radians(UpAngle), glm::vec3(1, 0, 0));

 Orientation = aroundY * aroundX;
}

This Quaternion Camera is based on the Euler to Quaternion Conversion.

The code process will be these :

  1.  User Mouse Input -> ProcessMouseMovement() -> updateCameraVectors() ->   GetViewMatrix()
  2.  User Keyboard Input -> ProcessKeyboard() -> GetViewMatrix()


I will explain the details of each method. However, User Mouse Input and User keyboard Input methods are skipped. If you need it, it had better refer other materials about taking user input. Or, I studied it on learnopengl.com in the Getting Started - Camera Chapter. You can get informations on taking user input with the detailed source code.
My code is based on the code of learnopengl.com.

1. ProcessMouseMovement()
After taking your mouse input, you can know the offsets of mouse position, especially, x and y delta. the delta value x means that A user wants to look at  the left or right side. the delta value y means that A user wants to look at up or downside. In the Graphics Camera Movement, the delta value x can be an angle around y-axis, what is called Yaw. In addition, the delta value y can be an angle around x-axis, what is called Pitch.

Since i'm trying to implement Graphics Camera For FPS-like Game, I don't consider the an angle around z-axis, what is called Roll. However, If you need it, you can insert the roll into your code, after you recognize the structure and essence of this quaternion camera.

Therefore, In one sense, ProcessMouseMovement() let you know how camera should rotate by Yaw and Pitch. That's it. once again, I mean that Yaw and Pitch are angles around y-axis and x-axis.

2. updateCameraVectors()
This is kind of the center of quaternion camera. while it looks simple, you need to know lots of quaternion algebra to understand what role each line is taking. Actually, for me, It took me lots of times to understand quaternion camera and write those lines.

http://graphics.stanford.edu/courses/cs348a-17-winter/Papers/quaternion.pdf

I recommend you to read the article above. The article is so kind that I can approach the level I want to about the quaternion. According to the article, we can define quaternion like this :



the angleAxis function of glm make that kind of equation of quaternion. That's the reason why there are glm::radians function and glm::vec3(). glm::vec3 means the u in the equation, which is the rotation axis.

Since we need to implement the rotation of Yaw and Pitch, I wrote the two lines of quaternion angle axis.

And the last line is the resulting rotation quaternion and the orientation of camera in quaternion format. Actually, you can accumulate the rotation in quaternion to multiply the quaternion.

https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Using_quaternion_as_rotations

you can know the mathematics of rotation accumulation in quaternion with this link.

But There is a point you have to care for, which is the order of multiplication. Because quaternion is not commutative in multiplication, you have to consider the order. However, There are particular orders of camera when you are using Euler to Quaternion Conversion.

https://www.gamasutra.com/view/feature/131686/rotating_objects_using_quaternions.php

In this article, it requires us to use the order quaternion = Yaw Pitch Roll. It meas Y, X, Z rotation in the order. 

Once again, because quaternion algebra is not commutative, we have to consider the order of rotations. each of all combinations can generate other rotations.

3. GetViewMatrix()
Now that we get the orientation of camera, it's time to get the view matrix. we can get the rotation matrix directly from the quaternion orientation.

https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix

With this article, you can know the mathematics of converting a quaternion to the rotation matrix.

However, Before converting it, We have to conjugate the quaterion orientation. Because, as you know, the graphics camera works reversely with our intention. If we want to look at right side, all the objects in world should move left side. That's the point of why we should conjugate the quaternion orientation.

and then we get the actual rotation Matrix. And you will also need to represent the translation of camera. So, I got the translation matrix. Actually, you can decrease the computation of GetViewMatrix() like this:


glm::mat4 chanCamera::GetViewMatrix()
{
 // You should know the camera move reversely relative to the user input.
 // That's the point of Graphics Camera!
 glm::quat reverseOrient = glm::conjugate(Orientation);

 glm::mat4 rot = glm::mat4_cast(reverseOrient);
 rot[3][0] = -(rot[0][0] * Position.x + rot[1][0] * Position.y + rot[2][0] * Position.z);
 rot[3][1] = -(rot[0][1] * Position.x + rot[1][1] * Position.y + rot[2][1] * Position.z);
 rot[3][2] = -(rot[0][2] * Position.x + rot[1][2] * Position.y + rot[2][2] * Position.z);
 rot[3][3] = 1;

 // glm::mat4 translation = glm::translate(glm::mat4(1.0), -Position);
 // return rot * translation;

 return rot;
}

You can know the mathematics why the code above should be like this in my article https://chanhaeng.blogspot.com/2018/06/the-construction-of-camera-matrix.html.

4. ProcessKeyboard()
In order to translate the camera in World, We need to know two direction, which are Front and Right directions. To get the front direction, we also use the orientation quaternion. The initial direction of Front direction is glm::vec3(0, 0, -1). However, as we move and rotate the camera, the Front direction is not pointing at glm::vec3(0, 0, -1). So, we rotate the glm::vec3(0, 0, -1) with the quaternion. We know how to rotate the vector in the articles I linked above.



so that's the reason why there is a code like this:


glm::quat qF = Orientation * glm::quat(0, 0, 0, -1) * glm::conjugate(Orientation);

so we got the rotated Front vector from the orientation quaternion. and then we got the real front vector with this line.


glm::vec3 Front = { qF.x, qF.y, qF.z };

because the x, y, z is the direction in 3D world, which are the real part of three complex numbers, i, j, k.

and then we can get the Right direction more easily with the rotated Front direction vector. the cross product of Front(actually -Z-axis) and World-Up(actually Y-axis) will give you the Right direction vector. ( cross(Y, Z) = X, cross(Z, Y) = -X, cross(-Z, Y) = -cross(Z, Y) = X).

With these direction vector, you can translate the camera position now.

5. More Study....
Actually, I didn't study all of details on the links i referred. It means that there are lots of contents I have to understand. The thing is slerp, which is interpolating the accuracy of the rotation. I'm not quite used to that kind of notion, but I will study it later. Because the journey from nothing to here is quite hard and took much time for me, I need to record my understandings here. After analyzing more details of quaternion, I will update this article.


댓글 2개:

  1. 작성하신 ChanCamera 클래스 생성자의 입력 변수중 up vector에 해당하는 변수들은 사용되지 않는것으로 보이는데 오일러 각 기반 카메라 System 에서 변형하시다 남은 흔적인가요?

    답글삭제
    답글
    1. 아 네 LearnOpenGL 코드를 가지고 변형하다가 남은 것 같네요. 제외하는게 좋을 것 같습니다.

      삭제