Camera

대인공·2022년 8월 12일
0

OpenGL

목록 보기
7/8
post-thumbnail

Camera / View Space

- 3D공간을 어느 시점에서 어떤 방향으로 바라볼 것인가를 결정
- 카메라를 조작하기 위한 파라미터로 부터 View transform을 유도

  • Camera 파라미터
    - camera position : 카메라의 위치
    - camera targer : 카메라가 바라보고자 하는 타겟 위치
    - camera up vector : 카메라 화면의 세로 출 방향


  • 결과 행렬
    - camera의 local - to - world transform의 inverse(역)

  • 카메라의 3축 결정 과정

구현

  • 코드 수정 , src / context.cpp / Render() 작성

    기존 코드

    auto view = glm::translate(glm::mat4(1.0f);
    	glm::vec3(0.0f, 0.0f, -0.3f);
	auto cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
	auto cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
	auto cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

	auto cameraZ = glm::normalize(cameraPos - cameraTarget);
	auto cameraX = glm::normalize(glm::cross(cameraUp, cameraZ));
	auto cameraY = glm::cross(cameraZ, cameraX);

	auto cameraMat = glm::mat4(
	  glm::vec4(cameraX, 0.0f),
	  glm::vec4(cameraY, 0.0f),
 	  glm::vec4(cameraZ, 0.0f),
	  glm::vec4(cameraPos, 1.0f));

	auto view = glm::inverse(cameraMat);

위의 '3축 결정 과정'처럼 구현되어 있다.
연산과정을 해주는 glm::lookAt함수의 활용

  • 앞 코드 수정 , src / context.cpp / Render() 작성
auto cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
auto cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
auto cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

auto view = glm::lookAt(cameraPos, cameraTarget, cameraUp);

Intercactive Camera

  • 사용자의 명령을 받아서 움직이는 카메라
    - W/S/A/D/Q/E : 전,후,좌,우,상,하 - 키보드 입력
    - 마우스 커서 : 회전 - 마우스 입력

이동

  • 파라미터 추가 , src / Context.h 작성
class Context {
public:
  	static ContextUPtr Create();
  	void Render(); // 추가된 항목
  	void ProcessInput(GLFWwindow* window); // 추가된 항목
 
private:
 
    // ...
 
 	 // camera parameter
 	 glm::vec3 m_cameraPos { glm::vec3(0.0f, 0.0f, 3.0f) }; // 추가된 항목
 	 glm::vec3 m_cameraFront { glm::vec3(0.0f, 0.0f, -1.0f) }; // 추가된 항목
 	 glm::vec3 m_cameraUp { glm::vec3(0.0f, 1.0f, 0.0f) }; // 추가된 항목
};

  • ProcessInput() 구현 , src / Context.cpp 작성
void Context::ProcessInput(GLFWwindow* window) {
  	const float cameraSpeed = 0.05f;
  	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) // 전
   		m_cameraPos += cameraSpeed * m_cameraFront;
        
  	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) // 후
    	m_cameraPos -= cameraSpeed * m_cameraFront;

  	auto cameraRight = glm::normalize(glm::cross(m_cameraUp, -m_cameraFront));
  	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) // 우
    	m_cameraPos += cameraSpeed * cameraRight;
        
  	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) // 좌
    	m_cameraPos -= cameraSpeed * cameraRight;    

 	auto cameraUp = glm::normalize(glm::cross(-m_cameraFront, cameraRight));
  	if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) // 상
    	m_cameraPos += cameraSpeed * cameraUp;
        
  	if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) // 하
    	m_cameraPos -= cameraSpeed * cameraUp;
}

  • 카메라 파라미터를 이용한 look - at행렬 계산 , src / Context.cpp / Render() 작성
	auto projection = glm::perspective(glm::radians(45.0f), (float)640 / (float)480, 0.01f, 20.0f);
      
  	auto view = glm::lookAt(m_cameraPos, m_cameraPos + m_cameraFront, m_cameraUp); // 추가된 항목
    //기존의 auto view로 변경

  • main()의 메인루프에서 Context :: ProcessEvent()호출 , src / maim.cpp 작성
while (!glfwWindowShouldClose(window)) 
{
 	glfwPollEvents();
  	context->ProcessInput(window); // 추가된 항목
  	context->Render();
  	glfwSwapBuffers(window);
}


회전

  • Euler Angle
    - 물체의 회전정보를 나타내는 대표적인 방식
    - roll(z), pitch(x), yaw(y) 3개의 회전각을 가지고 있다.


  • 카메라 회전각
    - 카메라 회전에 roll은 보통 사용하지 않는다. 대신 up vector를 기준으로 roll을 설정한다.
    - yaw, pitch만 가지고 camera front방향을 결정한다.

  • 회전관련 파라미터 추가 , src / context.h 작성
class Context {
public:
  static ContextUPtr Create();
  void Render();
  void ProcessInput(GLFWwindow* window);
  void Reshape(int width, int height);
  void MouseMove(double x, double y); // 추가된 항목
 
  //...
 
  // camera parameter
  float m_cameraPitch { 0.0f }; // 추가된 항목
  float m_cameraYaw { 0.0f }; // 추가된 항목
  glm::vec3 m_cameraFront { glm::vec3(0.0f, -1.0f, 0.0f) };
  glm::vec3 m_cameraPos { glm::vec3(0.0f, 0.0f, 3.0f) };
  glm::vec3 m_cameraUp { glm::vec3(0.0f, 1.0f, 0.0f) };
};
  • MouseMove() 구현 , src / context.cpp 작성
void Context::MouseMove(double x, double y) {
  	static glm::vec2 prevPos = glm::vec2((float)x, (float)y);
  	auto pos = glm::vec2((float)x, (float)y);
  	auto deltaPos = pos - prevPos;

  	const float cameraRotSpeed = 0.8f;
 	m_cameraYaw -= deltaPos.x * cameraRotSpeed;
  	m_cameraPitch -= deltaPos.y * cameraRotSpeed;

  	if (m_cameraYaw < 0.0f)   m_cameraYaw += 360.0f;
  	if (m_cameraYaw > 360.0f) m_cameraYaw -= 360.0f;

  	if (m_cameraPitch > 89.0f)  m_cameraPitch = 89.0f;
  	if (m_cameraPitch < -89.0f) m_cameraPitch = -89.0f;

  	prevPos = pos;    
}

  • 코드 수정 , src / context.cpp / Render() 작성
    - (0,0,-1)방향을 x축, y축에 따라 회전
	m_cameraFront =
  		glm::rotate(glm::mat4(1.0f),
    	glm::radians(m_cameraYaw), glm::vec3(0.0f, 1.0f, 0.0f)) * glm::rotate(glm::mat4(1.0f),
    	glm::radians(m_cameraPitch), glm::vec3(1.0f, 0.0f, 0.0f)) * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f);
    
    auto projectio = ...

  • 마우스 커서 콜백함수 구현 및 설정 , src / main.cpp 작성
// 콜백함수 정의
void OnCursorPos(GLFWwindow* window, double x, double y) 
{
  	auto context = (Context*)glfwGetWindowUserPointer(window);
  	context->MouseMove(x, y);
}

//...

void main(...)
{
//...
	glfwSetWindowUserPointer(window, context.get());

	OnFramebufferSizeChange(window, WINDOW_WIDTH, WINDOW_HEIGHT);
	glfwSetFramebufferSizeCallback(window, OnFramebufferSizeChange);
	glfwSetKeyCallback(window, OnKeyEvent);
    
	glfwSetCursorPosCallback(window, OnCursorPos); // 추가된 항목
//...

  • 결과
    - 화면안으로 커서가 들어오면 카메라 회전 시작
    - 커서가 화면 밖으로 나가면 카메라 회전종료
    - 마우스 x축 움직임으로 좌우 회전
    - 마우스 y축 움직임으로 상하 회전

마우스 우버튼을 누르고 있는 경우에만 카메라 조작하기

  • 파라미터 추가 , src / context.h 작성
	class Context {
// ...
  void MouseMove(double x, double y);
  void MouseButton(int button, int action, double x, double y); // 추가된 항목
// ...
  bool m_cameraControl { false }; // 추가된 항목
  glm::vec2 m_prevMousePos { glm::vec2(0.0f) }; // 추가된 항목
  float m_cameraPitch { 0.0f };
// ...
};

  • MouseButton() 구현 , src / context.cpp 작성
void Context::MouseButton(int button, int action, double x, double y) 
{
  	if (button == GLFW_MOUSE_BUTTON_RIGHT) 
    {
    	if (action == GLFW_PRESS) {
      	// 마우스 조작 시작 시점에 현재 마우스 커서 위치 저장
     	m_prevMousePos = glm::vec2((float)x, (float)y);
      	m_cameraControl = true;
    }
    else if (action == GLFW_RELEASE) 
    {
      	m_cameraControl = false;
    }
  }
}

  • 코드 수정 , src / context.cpp / ProcessInput() 작성
void Context::ProcessInput(GLFWwindow* window) 
{
  	if (!m_cameraControl)
    	return;
//...

- 코드 수정 , src / context.cpp / MouseMove() 작성 ```cpp void Context::MouseMove(double x, double y) { if (!m_cameraControl) return; auto pos = glm::vec2((float)x, (float)y); auto deltaPos = pos - m_prevMousePos; // ... m_prevMousePos = pos; } ``` > 기존의 static glm:: prevPos를 대신하여 사용한다.
  • 마우스 버튼감지를 위한 콜백함수 구현 및 설정 , src / main.cpp
// 콜백함수
void OnMouseButton(GLFWwindow* window, int button, int action, int modifier) 
{
  	auto context = (Context*)glfwGetWindowUserPointer(window);
  	double x, y;
  	glfwGetCursorPos(window, &x, &y);
  	context->MouseButton(button, action, x, y);
}

void Main(...)
{
	//...
	OnFramebufferSizeChange(window, WINDOW_WIDTH, WINDOW_HEIGHT);
	glfwSetFramebufferSizeCallback(window, OnFramebufferSizeChange);
	glfwSetKeyCallback(window, OnKeyEvent);
	glfwSetCursorPosCallback(window, OnCursorPos);
	glfwSetMouseButtonCallback(window, OnMouseButton); // 추가된 항목
    //...
}

  • 결과
    - 마우스 우클릭을 누르기 전까지는 아무런 입력이 인식되지 않는다.
    - 마우스 우클릭을 눌러야지만 입력이 활성화된다.


화면 크기 관련 처리 Refactoring

  • 코드 수정 , src / context.h 작성
class Context {
public:
  	static ContextUPtr Create();
  	void Render();
  	void ProcessInput(GLFWwindow* window);
  	void Reshape(int width, int height); // 추가된 항목
 
private:
  // ...
 
    int m_width {WINDOW_WIDTH};  // 추가된 항목
  	int m_height {WINDOW_HEIGHT};  // 추가된 항목
 
  // ...

  • Reshape() 구현 및 Projection행렬 계산식 수정 , src / conext.cpp / Render() 작성
void Context::Reshape(int width, int height) 
{
    m_width = width;
    m_height = height;
    glViewport(0, 0, m_width, m_height);
}
//...

void Context::Render(){

//...
auto projection = glm::perspective(glm::radians(45.0f), (float)m_width / (float)m_height, 0.01f, 20.0f);
//...

  • 창의 크기가 변경되었을때 호출되는 함수 , src / main.cpp 작성
    - user pointer기능을 이용
//...
void main(...)
{
//...
	glfwSetWindowUserPointer(window, context.get()); // 추가된 항목

	OnFramebufferSizeChange(window, WINDOW_WIDTH, WINDOW_HEIGHT);
	glfwSetFramebufferSizeCallback(window, OnFramebufferSizeChange);
	glfwSetKeyCallback(window, OnKeyEvent);
  • glfwSetWindowUserPointer(window, context.get())
    - 창 크기가 변경되면 뒤의 context.get()함수를 호출한다.

    auto pointer = (Context*)glfwSetWindowUserPointer(window);
    - 윈도우의 창이 바뀌면 Context*형 pointer로 반환된다. 이러한 기능을 사용


-코드 추가 , src / main.cpp / OnFramebufferSizeChange() 작성

//...
  	auto context = (Context*)glfwGetWindowUserPointer(window);
  	context->Reshape(width, height);

  • 결과
    - 실행도중 창의 크기가 변경되어도 오브젝트가 찌그러지거나 늘어짐없이 동일한 비율을 유지한 상태로 사이즈만 조정된다.



profile
이제 막 시작하는 유니티 클라이언트

0개의 댓글

관련 채용 정보