카메라에 대해 한 번 모든 것들을 정리해보려고 노력 중이다.
알아보자
1. Perspective vs Isometric(Orthographic)
**사실 뒤에 나오는 Projection Transform에서 다시 설명한다.
여기 gizmo가 있다. 바로 카메라를 보여주는 방식을 결정하는데
아래에 현 상태가 나와있다.
위 사진 상태는 Perspective 상태로 Toggle 기능을 가지고 있어서 한 번 누르면 다른 상태로 바뀐다.
Isometric 상태로 바뀐다.
** Isometric is usful in combination with clicking one of the conical axis arms to get a front, top or side elevation.
-> 2D라고 생각하면 gizmo를 이용해서 각 길이를 알아내기 쉽다.
카메라에 무슨 변화가 생기느냐?
Perspective는 정말 우리가 보는 view라고 생각하면 되고
-> 사람이 보는 방식과 동일, 가까이 있는 것이 크게 보이고 멀리 있는 것은 작게 보인다.
문제는 Isometric인데 무슨 뜻이냐?
Isometric projection is a method for visually representing three-dimensional objects in two dimensions in technical and engineering drawings.
즉, 3D임에도 2D로 보겠다는 것이다. 우리 기술-가정시간에 배우지 않았나?
앞, 위, 옆에서 본 그림들 같은 것?
++ 거리에 따라 크기의 변화는 없다. 깊이감을 삭제했다고 봐도 무방하다.
3D는 3가지를 보여주는 것만으로도 충분하다. 3-Dimension, 3차원이라는 뜻이고
각 차원마다 정보를 가지고 있다는 말이고 3D 물체라는 것은 각 차원마다 정보를 가지고 3개가 모인 것을 말하는 것이다.
Width, Height, depth 같이 3개! 결국 2가지 요소가 합친 것만 보여주겠단 뜻이다.
그게 바로 아이소메트릭이다.
사실 이 기능은 정말 2D로 본다기 보다
2D, 3D 사이의 2.5D 정도를 보여주기 위해 쓰인다.
시뮬레이션 게임에 자주 이용된다. 예를 들어서
음 2.5D라는 것이 이런 류를 말한다.
2. Spherical Coordinates 를 이용해서 3인칭 카메라 만들기
구면좌표계를 미적분학을 배웠다면 배웠을 것이다.
해당 좌표계를 이용하여 우리가 아는 3인칭 카메라의 형태를 만들 수 있다.
구면좌표계에서 3가지 성분이 있듯이
캐릭터가 구의 원점에 있다고 보았을 때
r은 카메라와 오브젝트 사이의 거리가 될 것이고
theta 는 오브젝트를 바라보는 각도 ( 수평적으로, 경도와 같이)
pi 는 오브젝트를 바라보는 각도 ( 수직적으로 위도와 같이)
가 되겠다.
일반적으로 게임개발에서는 이렇게 용어를 쓰지 않고
Radius, Azimuth, Elevation으로 바꾸어 쓴다.
순서대로 r, theta, pi 이다.
가장 유명한 게임인 GTA 5도 이러한 카메라를 채택했다.
이러한 식으로 카메라를 만들 수 있다.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class SphericalCamera : MonoBehaviour {
//확대 또는 회전 속도
public float rotateSpeed = 1f;
public float scrollSpeed = 200f;
public Transform pivot; // 구면좌표의 원점을 말하면서 바라보는 대상 ( ex 캐릭터를 의미)
//해당 구면좌표계를 카메라에 적용하기 위한 성분
[System.Serializable]
public class SphericalCoordinates
{
public float _radius, _azimuth, _elevation; // r,theta, pi 순서
//Clamp 함수의 사용으로 사잇값으로 고정시킴
public float radius
{
get { return _radius; }
private set
{
_radius = Mathf.Clamp( value, _minRadius, _maxRadius );
}
}
public float azimuth
{
get { return _azimuth; }
private set
{
_azimuth = Mathf.Repeat( value, _maxAzimuth - _minAzimuth );
}
}
public float elevation
{
get{ return _elevation; }
private set
{
_elevation = Mathf.Clamp( value, _minElevation, _maxElevation );
}
}
//너무 가까워도 너무 멀어도 안됨
public float _minRadius = 3f;
public float _maxRadius = 20f;
// 0부터 360이 가장 이상적
public float minAzimuth = 0f;
private float _minAzimuth;
public float maxAzimuth = 360f;
private float _maxAzimuth;
public float minElevation = 0f;
private float _minElevation;
public float maxElevation = 90f;
private float _maxElevation;
public SphericalCoordinates(){}
//위 클래스의 생성자 (직교좌표를 구면좌표계로 바꾸는 것임)
public SphericalCoordinates(Vector3 cartesianCoordinate)
{
//degree를 radian으로 바꿔주는 작업이 필요함.
_minAzimuth = Mathf.Deg2Rad * minAzimuth;
_maxAzimuth = Mathf.Deg2Rad * maxAzimuth;
_minElevation = Mathf.Deg2Rad * minElevation;
_maxElevation = Mathf.Deg2Rad * maxElevation;
//해당 거리로 바로 radius 구할 수 있음, 카메라와 원점의 거리, 나머지는 역삼각함수 이용함
radius = cartesianCoordinate.magnitude;
azimuth = Mathf.Atan2(cartesianCoordinate.z, cartesianCoordinate.x);
elevation = Mathf.Asin(cartesianCoordinate.y / radius);
}
//구면좌표계에서 직교좌표계로 변환하는 것임.
public Vector3 toCartesian
{
get
{
//x,z 평면에 수직으로 내렸을 때 원점과의 거리를 말함 : t
float t = radius * Mathf.Cos(elevation);
return new Vector3(t * Mathf.Cos(azimuth), radius * Mathf.Sin(elevation), t * Mathf.Sin(azimuth));
}
}
//해당 수치만큼 theta와 pi를 변환
public SphericalCoordinates Rotate(float newAzimuth, float newElevation){
azimuth += newAzimuth;
elevation += newElevation;
return this;
}
//해당 수치만큼 radius를 변환
public SphericalCoordinates TranslateRadius(float x) {
radius += x;
return this;
}
}
//객체를 담는 변수 생성
public SphericalCoordinates sphericalCoordinates;
// 객체 초기화
void Start () {
sphericalCoordinates = new SphericalCoordinates(transform.position);
transform.position = sphericalCoordinates.toCartesian + pivot.position;
}
// 업데이트로 카메라 위치바꿈.
void Update () {
float kh, kv, mh, mv, h, v;
//키보드 입력(방향키)
kh = Input.GetAxis( "Horizontal" );
kv = Input.GetAxis( "Vertical" );
//마우스 입력
bool anyMouseButton = Input.GetMouseButton(0) | Input.GetMouseButton(1) | Input.GetMouseButton(2);
mh = anyMouseButton ? Input.GetAxis( "Mouse X" ) : 0f;
mv = anyMouseButton ? Input.GetAxis( "Mouse Y" ) : 0f;
h = kh * kh > mh * mh ? kh : mh;
v = kv * kv > mv * mv ? kv : mv;
//회전에 대해
if (h * h > Mathf.Epsilon || v * v > Mathf.Epsilon) {
transform.position
= sphericalCoordinates.Rotate(h * rotateSpeed * Time.deltaTime, v * rotateSpeed * Time.deltaTime).toCartesian + pivot.position;
}
//마우스 휠의 입력 (휠을 아래로 돌리면 음수, 휠을 위로 돌리면 양수)
float sw = -Input.GetAxis("Mouse ScrollWheel");
//부동소수형 변수가 0과 다른 값중에, 가질 수 있는 가장 작은 값이다.
//실제로 증명할 때 앱실론은 작은 수라고 가정하는데 그거와 마찬가지다.
//float 은 == 연산자로 비교하면 부정확하기 떄문임.
//0은 아니면서 가장 작은값 보다 커야함.
if (sw * sw > Mathf.Epsilon) {
transform.position = sphericalCoordinates.TranslateRadius(sw * Time.deltaTime * scrollSpeed).toCartesian + pivot.position;
}
//카메라는 pivot position을 바라보게 된다. 방향벡터라고 이해하면 편함.
//해당 위치에서 입력된 위치를 바라봄.
transform.LookAt(pivot.position);
}
}
위 스크립트를 카메라에 component로 집어넣어 활용한다.
3. Projection Transform, 프로젝션 변환
여기서 Projection이란 뷰 좌표계에서 클립공간으로 바꾸는 것을 의미한다.
즉, World에 있는 모든 모델들의 좌표를 카메라의 모델좌표계로 바꾸고 그것을 다시 클립공간에 바꾸는 것을 의미한다.
아무튼 클립 공간은 뷰 좌표계의 일부를 클립한 공간이다.
**여기서 클립이란 트위치 클립딴다는 말과 같이 특정 부분만 잘라낸다는 뜻이다.
여기서는 카메라에서 보이지 않는 부분을 잘라내는 것을 의미한다.
여기서는 카메라에서 보이지 않는 부분을 삭제한다.
카메라의 뒤쪽이라거나 너무 멀어 카메라에 비치지 않거나 너무 가까워서 카메라에 비치지 않는 부분 포함이다.
이것을 view frustum이라고 하는데
피라미드 모양에서 목을 쳐낸 것을 말한다.
**Perspective Camera 에서만 가능하다.
카메라로 빨간 막대기를 찍는 것을 생각해보자.
우리는 장면단위로 보지 지점단위로 보지 않는다.
카메라도 마찬가지다.
어떠한 장면을 담는 것이지 부분을 담는 것이 아니다.
때문에 위와 같은 일이 일어난다.
완전히 이해할 필요는 없지만 당연한 것이라 받아들이기 어렵지는 않을 것이다.
다음 그림이 View Frustum 이다.
저 멀리 있는 평면을 원평면
가까이 있는 평면을 근평면이라고 한글로 번역한다.
여기서 FOV, Field Of View 는 화각이라고 하는데
화각이 넓어질수록 원평면/근평면의 비가 커진다.
즉, 원평면이 넓어져서 담아지는 것들이 많아진다고 생각하면 된다.
또한 원근법이란 개념이 적용된다.
또한 Clipping Plane을 조절할 수도 있다.
앞에서 Perspective만 가능하다고 했는데
사실
Orthogonal도 가능하다.
다만 직교투영은 근평면과 원평면의 평면의 크기가 다르지 않다.
즉, 시야각이란 개념도, 원근법이라는 개념도 아무것도 없다.
3D에서도 Orthogonal을 적용한다면 2D처럼 보인다.
'Game Development, 게임개발 > 개발' 카테고리의 다른 글
C#/Unity Event - 개념 (0) | 2022.01.08 |
---|---|
UniRx, Reactive Extension for Unity - 개념 (1) | 2022.01.07 |
게임 일시정지하기, 퍼즈, pause 만들기 [Unity] (0) | 2021.06.10 |
2진법 규칙에 맞춰서 출력하기 - C# (0) | 2021.05.29 |
주어진 글자 디스플레이에 출력하기 - Unity (0) | 2021.05.28 |