※ 해당 글은 삼각비를 이해해야 적용이 가능하니 혹시 삼각비를 모르시는 분들은 검색해서 알아보시거나, 아래 링크를 참고해주세요~!
링크 : [유니티] 삼각비를 이용해 각도로 좌표구하기
위의 글에서 우리는 삼각비를 통해서 극축과의 각도(θ)와 반지름(r)을 알면 좌표를 구할수 있다는것을 알았다.
그럼 이제 이 원리를 이용해서 유니티로 3D 카메라의 좌표를 구해보도록 하자.
- 구면좌표계
구면좌표계는 2D 극좌표계에서 축이 하나 더 생긴 3D 극좌표계를 말한다. 그림을 보면서 확인해보자.
위 그림에서와 같이 극으로 부터 떨어진 점 P의 좌표를 (r, θ, Φ)라고 한다.
방위각(Azimuth)는 말 그대로 방위의 각이고, 앙각(Elevation)은 고도를 말한다. 유니티 좌표계를 기준으로 XZ축이 방위가 되고, Y축이 고도가 된다.
그럼 삼각비를 이용해서 점 P의 직교좌표를 구해보자.
위 그림을 기준으로 우리는 2개의 삼각형을 얻을 수 있다.
왼쪽 삼각형은 앙각을 끼고있는 삼각형이고 오른쪽은 방위각을 끼고있는 삼각형이다.
여기서 우리가 제일 먼저 알아야할 값은 빗변(t)의 길이이다. 일단 r값과 Φ을 이용하여 t를 구하게되면,
sinΦ = t / r → t = r sinΦ
가 되는것을 확인할 수 있다. 이제 t의 값을 구했으니 t와 θ값을 이용해서 x와 z값을 구해보자.
x값 : cosθ = x / t → x = t cosθ → x = r sinΦ cosθ
z값 : sinθ = z / t → z = t sinθ → z = r sinΦ sinθ
나머지 y값은 y = r cosΦ로 확인할 수 있다.
삼각비를 제대로 이해했고 여기까지 설명을 봤으면 다 이해했다고 믿는다!
- 구면좌표계를 이용해서 캐릭터를 중심으로 움직이는 3D카메라 좌표 계산하기.
이젠 코드와 함께 캐릭터를 중심으로 움직이는 카메라의 3D 좌표를 구해볼 차례이다. 코드를 작성하기 전에 코드에 참고한 예시는 다음 그림과 같다.
위 그림을 기준으로 새로 식을 작성해 보면
x = r cosΦ cosθ
y = r sinΦ
z = r cosΦ sinθ
으로 확인할 수 있다. (여기까지 이해했다면 당신은 좀 더 진화된 수포자다.)
위에서의 설명과 다른점이 하나 있는데, 우리는 카메라의 시작 좌표와 반지름(r)을 지정하여 각도를 먼저 구할것이다.
즉, (x, y, z)값을 지정해서 각 축의 값으로 역함수를 이용하여 θ와 Φ의 각도를 구할것이다.
※ 역함수로 직각삼각형의 두 변의 길이를 이용하여 극축과의 각도를 구할 수 있다.
우리는 (x, y, z)값과 r값을 알고있기 때문에, 방위각(θ)는 역함수 atan으로 구할것이고, 앙각(Φ)은 역함수 asin으로 구한다.
float Azimuth = Mathf.Atan2(_camCoordinate.z, _camCoordinate.x);
float Elevation = Mathf.Asin(_camCoordinate.y / radius);
위와 같이 작성하게되면 우리는 방위각(Azimuth)과 앙각(Elevation)을 얻을 수 있다. 이렇게 각까지 알아냈다면 나머지는 우리가 가진 값들로 직교좌표를 구하면 되는것이다.
- 코드
[System.Serializable]
public class SphericalCoordinates
{
private float radius, azimuth, elevation;
public float Azimuth
{
get { return azimuth; }
private set
{
azimuth = Mathf.Repeat(value, maxAzimuth_Rad - minAzimuth_Rad);
}
}
public float Elevation
{
get { return elevation; }
private set
{
elevation = Mathf.Clamp(value, minElevation_Rad, maxElevation_Rad);
}
}
//Azimuth range
public float minAzimuth_Deg = 0f;
private float minAzimuth_Rad;
public float maxAzimuth_Deg = 360f;
private float maxAzimuth_Rad;
//Elevation rages
public float minElevation_Deg = -20f;
private float minElevation_Rad;
public float maxElevation_Deg = 40f;
private float maxElevation_Rad;
public SphericalCoordinates(Vector3 _camCoordinate, float _radius)
{
//방위각 라디안 값(최대, 최소)을 구한다.
minAzimuth_Rad = Mathf.Deg2Rad * minAzimuth_Deg;
maxAzimuth_Rad = Mathf.Deg2Rad * maxAzimuth_Deg;
//앙각 라디안 값(최대, 최소)을 구한다.
minElevation_Rad = Mathf.Deg2Rad * minElevation_Deg;
maxElevation_Rad = Mathf.Deg2Rad * maxElevation_Deg;
radius = _radius;
//역함수로 방위각과 앙각을 구한다.
Azimuth = Mathf.Atan2(_camCoordinate.z, _camCoordinate.x);
Elevation = Mathf.Asin(_camCoordinate.y / radius);
}
public Vector3 toCartesian
{
get
{
//camera position = (r cosΦ cosθ, r sinΦ, r cosΦ sinθ)
float t = radius * Mathf.Cos(Elevation);
return new Vector3(t * Mathf.Cos(Azimuth),
radius * Mathf.Sin(Elevation), t * Mathf.Sin(Azimuth));
}
}
public SphericalCoordinates Rotate(float newAzimuth, float newElevation)
{
Azimuth += newAzimuth;
Elevation += newElevation;
return this;
}
}
public class CamCtrl : MonoBehaviour
{
private Vector3 lookPosition;
private Vector3 targetCamPos = new Vector3(0, 1.5f, -4);
public Transform PlayerTr;
public SphericalCoordinates sphericalCoordinates;
void Start()
{
//카메라 위치 계산을 위해 x, y, z좌표와 반지름 r값을 넘겨준다.
sphericalCoordinates = new SphericalCoordinates(targetCamPos, Mathf.Abs(targetCamPos.z));
transform.position = sphericalCoordinates.toCartesian + PlayerTr.position;
}
void Update()
{
float horizontal = Input.GetAxis("Mouse X") * -1;
float vertical = Input.GetAxis("Mouse Y") * -1;
//플레이어 위치에서 조금더 위쪽으로 자리잡게 만든다.
lookPosition = new Vector3(PlayerTr.position.x,
PlayerTr.position.y + targetCamPos.y, PlayerTr.position.z);
//플레이어 중심으로 구한 구면좌표를 카메라 위치에 적용
transform.position = sphericalCoordinates.Rotate
(horizontal * Time.deltaTime, vertical * Time.deltaTime).toCartesian + lookPosition;
//목표지점으로 카메라를 보게함
transform.LookAt(lookPosition);
}
}
※※ 여기서 반지름(radius)값을 조절하면 마우스 스크롤을 통한 카메라 줌인 줌아웃 기능도 추가가 가능하다.
※※ 방위각(Azimuth)와 앙각(Elevation)에 제한을 설정하고 싶다면 minAzimuth_Deg, maxAzimuth_Deg, minElevation_Deg, maxElevation_Deg 값들을 설정해두면 된다.
'Develoment > Math' 카테고리의 다른 글
[유니티] 벡터의 내적 (0) | 2022.04.26 |
---|---|
[유니티] 삼각함수를 이용해 진자운동 구현하기 (2) | 2021.10.02 |
[유니티] 삼각비를 이용해 각도로 좌표구하기. (0) | 2021.04.15 |