반응형

※ 해당 글은 삼각비를 이해해야 적용이 가능하니 혹시 삼각비를 모르시는 분들은 검색해서 알아보시거나, 아래 링크를 참고해주세요~!

링크 : [유니티] 삼각비를 이용해 각도로 좌표구하기

 

[유니티] 삼각비를 이용해 각도로 좌표구하기.

삼각비 삼각비는 직각 삼각형에서 각 A에 대해 세 변의 길이가 이루는비가 일정한걸 말한다. 위와 같은 삼각형에서 빗변(r), 대변(y), 이웃변(x)로 확인해보자. 여기서 각 A가 동일하면 다음과 같은

srdeveloper.tistory.com


 

 

위의 글에서 우리는 삼각비를 통해서 극축과의 각도(θ)와 반지름(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 값들을 설정해두면 된다.

반응형

+ Recent posts