반응형

 

 

 

 

내적

- '곱한다'는 뜻을 가지고 있다.

- 한 벡터(A)를 다른 벡터(B) 위로 정사영 시킨 길이와 다른 벡터(B) 길이의 곱이다.

- 내적의 결과는 스칼라(실수) 값이 나오게된다.

- 물체의 위치, 시야각, 빛 등에 사용된다.

- 기호는 •이며 dot(inner) product라고한다.

- 좀 더 자세한 설명은 다음 영상 참고 : https://youtu.be/IOf1o72aKDc

 

 

 

내적 공식

- 다음 그림을 기준으로 내적을 구해보자

1. 두 벡터의 각 성분끼리의 곱의 합으로 정의

2. 두 벡터가 이룬 사잇각(ʘ)으로 정의

 

※ 위의 두 정의에 따라 사잇각(ʘ)을 구할 수 있다.

cosʘ의 값이 0.8이라는것을 알았으니 2번 정의에 대입을 해보면 30 * 0.8 = 24가 되므로 1번과 2번의 결과값이 동일하다는 것을 알 수 있다.

 

 

 

각도를 알고싶으면 결과값 0.8을 역함수로 계산기를 돌려보면 각도를 얻을 수 있다.

 

 

 

 

 

반응형
반응형

 

※ 해당 포스팅은 유니티 물리, 힌지 조인트를 사용하는것이 아닌 단순히 진자 운동을 보이게 하기위한 포스팅이다.

 

삼각함수를 이용하여 진자운동을 구현해볼것이다.

최종 목표는 진자운동을 구현하여 좌우로 흔들리는 철퇴 장애물을 만드는것.

 

구현하기에 앞서 삼각함수 그래프를 먼저 살펴본다.

파란선이 sin 그래프, 빨간선이 cos 그래프이다. (그림이 조악한건 이해바람..)

위 두 그래프는 1과 -1을 반복하기 떄문에 우리는 이 특성을 이용하여 진자 운동을 구현할 것이다.

진자 운동 코드를 만들기 전에 삼각함수 그래프를 이용한 움직임이 어떤지 확인해보기 위해 위, 아래로 먼저 표현을 해보자.

 

 

 

사용 예시

이번 예시에서는 Vector3.Lerp()의 마지막 인자 t의 값을 Mathf.Sin()으로 구해서 구체의 Position값을 변경하는 형태이다.

참고로 Vector3.Lerp()t인자 값은 0~1로 넣어줘야하는데 Mathf.Sin()의 값은 -1~1이다. 그렇기 때문에 약간의 추가 작업이 필요하다. t인자 값을 구하기 위한 코드는 다음과 같다.

float lerpTime += Time.deltaTime;
(Mathf.Sin(lerpTime) + 1) * 0.5f; //Lerp()의 t인자 값

그럼 본격적으로 철퇴 장애물을 만들어보자.

 

 

 

유니티 게임오브젝트 세팅

1. 빈 게임오브젝트를 만들어준다.

  ※ 이 게임오브젝트가 최상위 부모 오브젝트이다.

2. 빈 게임오브젝트의 자식으로 체인 오브젝트를 만들어준 뒤 체인 오브젝트의 위쪽을 부모 오브젝트의 중심에 맞춰준다.

3. 체인 오브젝트의 자식으로 철퇴 오브젝트를 만들어준다.

 

이것으로 게임오브젝트 세팅이 완료되었다. 설명이 따로 필요없을정도로 간단하다.

이제 최상위 부모 오브젝트를 좌우로 회전시켜주기만 하면 진자운동을 하는 철퇴 장애물을 만들 수 있는것이다!

회전 시켜줄 게임 오브젝트에 연결해줄 코드를 살펴보자

 

 

 

코드

using UnityEngine;

public class Pendulum : MonoBehaviour
{
    public float angle = 0;
    
    private float lerpTime = 0;
    private float speed = 2f;
    
    private void Update()
    {
        lerpTime += Time.deltaTime * speed;
        transform.rotation = CalculateMovementOfPendulum();
    }
	
    Quaternion CalculateMovementOfPendulum()
    {
        return Quaternion.Lerp(Quaternion.Euler(Vector3.forward * angle), 
        	Quaternion.Euler(Vector3.back * angle), GetLerpTParam());
    }
	
    float GetLerpTParam()
    {
        return (Mathf.Sin(lerpTime) + 1) * 0.5f;
    }
}

  ※ angle값은 public으로 선언했으므로, 인스펙터 창에서 입맛대로 바꿔주면 된다.

  ※ 예시에서는 Mathf.Sin()을 사용했지만, Mathf.Cos()도 사용 가능하다. 포스팅 상단의 그림에서 봤듯이 Lerp()의 t인자값을 0.5(Sin)부터 시작할 것이냐 1(Cos)부터 시작할 것이냐의 차이다.

 

 

 

결과

 

반응형
반응형

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

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

 

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

삼각비 삼각비는 직각 삼각형에서 각 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 값들을 설정해두면 된다.

반응형
반응형

 

  • 삼각비

삼각비는 직각 삼각형에서 각 A에 대해 세 변의 길이가 이루는비가 일정한걸 말한다.

위와 같은 삼각형에서 빗변(r), 대변(y), 이웃변(x)로 확인해보자.

여기서 각 A가 동일하면 다음과 같은 값은 모두 동일하게 이루어진다.

 

1. 이웃변(x) / 빗변(r)

2. 대변(y) / 빗변(r)

3. 대변(y) / 이웃변(x)

 

여기서 1, 2, 3번과 같은 값을 각각 cos, sin, tan으로 명명한다. 다시 정리해보면..

 

1. cosA = x / r

2. sinA = y / r

3. tanA = y / x

 

이렇게 정리해 줄 수 있다. 여기까지 이해됐(외웠...)으면 다음으로 넘어가보자.

 


 

  • 극좌표계

일단, 필자와 같은 수포자들이 '각도로 좌표구하기'로 구글링해서 얻을 수 있는 공식에 대해 알아보자. 각도로 좌표구하기를 검색해서 나오는 공식을 보면

 

radian = degree * Mathf.Deg2Rad

(x, y) = (r * Mathf.cos(radian), r * Mathf.sin(radian))

 

위와 같은 공식을 쉽게 볼 수 있게 된다. 이제 이 공식도 이해를 해보자.

위 그림에 있는 B(r, θ)를 극좌표라고 하는데, 원점에서 떨어진 점 P가 있으면 P(r, θ)로 표시한다. (여기서 r은 극(원점)과 점 P를 잇는 직선의 길이, θ는 각 A를 말한다.)

※ 우리가 보통 알고있는 (x, y)좌표는 직교좌표(데카르트(cartesian) 좌표)이다.

 

우리가 알고싶은것은 점 B의 좌표이다. 점 B의 좌표는 직교좌표로 (x, y)이다.

자, 다시 삼각비를 생각해볼때이다. 삼각비에서 우리는

 

1. cosA = x / r

2. sinA = y / r

 

이것을 이용할 것이다. 뭔가 감이 오지 않는가?

 

1. cosθ = x / r ▶ r * cosθ = x

2. sinθ = y / r ▶ r * sinθ = y

 

즉, (r * Mathf.cos(radian) = x, r * Mathf.sin(radian) = y)가 되는것이다!

다시말해 극좌표 (r, θ) 직교좌표 (r * cosθ, r * sinθ)와 같다.

 

※※ 여기서 극좌표 θ는 radian을 이용한 호도법으로 표시하기 때문에, 우리가 일반적으로 쓰는 degree가 아니라 radian으로 바꿔서 사용해야한다.

 

 

결과적으로 코드를 작성해본다면, 아래와 같은 코드로 작성할 수 있다.

private Vector3 ConvertAngleToVector(float _deg)
{
	var rad = _deg * Mathf.Deg2Rad;
	return new Vector3(Mathf.Cos(rad), Mathf.Sin(rad));
}

 

※※ 필자의 경우 극좌표는 단순히 방향을 알려고할때 사용하기 때문에 r값을 굳이 넣지 않고 사용한다.

※※ 거리도 사용할 경우 cos, sin값에 거리를 곱해주면 된다.

반응형

+ Recent posts