Clolent

▶ 2D 좌표계 ( = 데카르트 좌표계 )

X축과 Y축 / 사분면


▶  3D 좌표계

X축 Y축 Z축 / 왼손 좌표계


빨간 X 

파란 Z

초록 Y


기억하자. 


▶ 로컬 좌표계와 월드 좌표계

우리가 Inspector 상에서 보는 Position 값은 Local 이다 !

transform.position == transform.localPosition ??? 맞을수도 아닐수도 ! 

( 만약 해당 오브젝트가 루트라면 같겠지만 누군가의 자식이라면 다를 것이다 ) 


Local 공간 이란 무었인가 = 자신을 기준으로한 새로운 공간 


ex) 내가 컵을 손을 쭉뻗어서 들고있고 이는 내기준으로 ( 1, 0, 0 ) 위치라고하자         내가 

아무리 이 컵을 쭉 뻗어서 든상태로 뛰고 점프하고 기어다닌다 한들 컵의위치는

여전히 내 기준으로는 ( 1, 0, 0 ) 이다. 이것이 로컬좌표


물론 월드 기준으로 봤을때 컵의 위치는 바뀌었겠지만.



▶ Center / Pivot 

이는 회전 등을할때 기준을 어떻게 할것이냐에 관한 것이다. ( B가 A의자식일때 )

이제 이를 Center 로 회전시킨다면 각각이 회전축을 가지고 회전을 한다.

이런식으로 말이다. 그렇다면 Pivot 으로 하고 화전을 하면 어떻게 될까.


Center 때와의 차이가 보이는가 그렇다. A를 기준으로해서 회전을 하게되서 B는 저렇게 위로 가버리게된다.

농구를 할때 피벗을 생각하면 좀더 이해가 쉬울것 같다. 

이는 굉장히 중요한성질이므로 꼭 잘 기억하자.



▶ Local / Global 

로컬과 글로벌은 축을 어떻게 표시할것이냐에 관한 설정이다. 기본적으로 위의 큐브그림과 같이 x, y, z축이 각각 저렇게 되어있지만 만약 물체가 회전 등을 했을때의 축을 어떻게 표현할 것인가에 관한 물음이다.



이런 큐브가 공간상에 있을때 이놈을 조금 회전을 시켜보겠다

Local 로 두고서 회전을 시키게 된다면 ! 

그 축도 같이 회전하게 된다.



그런데 이렇게 오른쪽으로 90 도 회전 하는것을 Local 이 아닌 Global 로 하게된다면 ?

물체는 회전을 한다. 하지만 그 축은 월드 좌표계로 유지된다.



그렇다. 그렇다고한다. 

Global 과 Local 에 대한 설명은 이것으로 끝. 

물체의 위치를 조정하거나 할때 상당히 유용한 기능이다. 



▶ 스크린좌표

또다른 좌표계로 스크린 공간 ( Screen Space ) 가 있다.

우리가 게임을 할때 카메라를 통해서 보게되는 그 스크린이다.


Camera 클래스의 WorldToScreenPoint 매서드를 호출하여 월드좌표에서 스크린좌표로 

변환이 가능하다. 그런데 기억해야 할것은 이것이 절대적인 값이 아니라는것이다.


어느 한 물체가 있는데 이를 어느 카메라가 이 물체를 바라보느냐에 따라서 스크린 좌표는 얼마든지 달라질수있다.

 내가 책상을 가만히 냅두고 가까이서 보면 커보이고 멀리서 보면 작아보이고 내가 움직이면 책상은 실제론 가만히 있지만 내 기준에선 그 위치가 달라지는 것처럼 말이다.


또한 유니티에는 스크린 좌표를 정규화( Normalize ) 한 좌표로 뷰포트 좌표가 있다.

뷰표트 좌표는 최하좌단 ( 0,0 ) 최상우단 ( 1,1 ) 로 정규화 한것이다.


유니티는 이러한 여러 좌표계를 용도에 맞게 사용한다고한다. 어렵다 



▶ 극좌표계

이 사진이 극 좌표계를 잘 설명하고있다.

2D 에 있어서긔 극좌표계는 위 처럼 r 과 θ 로 나타낸다.

반지름 r 과 각도 θ 이때 x,y 값은 각각 ( r Cosθ , r Sinθ ) 이 된다. 


게임상에서 극좌표는 평면상에서 원의 원주 위를 이동시키고자 할때 사용할수 있다고한다. 또는 화면중심으로부터의 거리에 따라 화면의 특정 이미지 처리를 가하고자 할때 처리가 쉬워진다고 한다. 그렇다고한다 난 잘 모르겠다.


2D 상에서의 극좌표 ( r , θ ) = 2D 상에서의 직교좌표 ( r Cosθ , r Sinθ )


반대로 직교좌표를 극좌표로 나타내려면

2D 직교좌표 ( x , y ) = 2D 상에서의 극좌표 ( √(x^2 + y^2 ) , atan2( y,x ) ) 


위의 그림을 연상하며 생각하면 이해가 될것이다. 


▶ 구면좌표계 ( = 3D 극좌표계 )

간단하게 말하편 구면좌표계라는 것은 3D 극 좌표계 라고 생각할수 있다. 


이런 친구인데, 일반수학에서 아주 잘 배울수 있다.


이 친구는 ( r , θ , Φ ) 로 나타내어 질수있고 각각 해당하는 의미는 위의 그림을 보면 알수있다. 이제 x축 y축에 이어서 z축 까지 등장을 하는게 각각을 포현하면 아래와 같이된다.


( r Sinθ CosΦ , r Cos θ , r Sinθ Sin Φ ) 


이딴 것을 어디다가 사용하는지 의문이었는데 이 구면좌표계를 통해서 바로 어쌔신크리드, 사이퍼즈, 기타 등등의 캐릭터 뒤통수를 보고 하는게임 즉 3인칭 시점으로 게임을 진행하게 되는 게임들이 바로이 구면좌표계를 적용한 것으로 생각할수있다.


캐릭터 주변의 일정 원형궤도를 위성처럼 움직이는 물체를 표현 하거나 할수있는데 

이 물체가 카메라가 되면 바로 3인칭 시점이 되는것이고 캐릭터 주변을 돌아댕기는 펫 등을표현할수도 있을 것이다 


세상에 맙소사 이딴걸 왜배우는거지했는데 엄청중요했잖아


이 좌표계는 두가지 각도를 사용하게 되는데 각각을 방위각, 앙각 이라 부른다. 

어떤것이 앙각이고 방위각이냐 하면 


보이는 바와 같이 땅과 별이 이루는 각도가 앙각 

북쪽과 내가 바라보는 곳과의 각도가 방위각이다.

그림을 통해 이해하자


아직 조금 헷갈릴수 있다 그럼 앙각, 방위각 이란 이름은 조금 어려우니 우리에게 친숙한 이름을 사용해서 이해하자

바로 위도와 경도이다. 


해당 그림에서 가로줄에 해당하는 것들이 경도이고 세로줄에 해당하는 것이 위도이다.

경도 = 방위각 / 위도 = 앙각 으로 이해할수 있겠다. 이해하자


각각을 나타내는 기호는 앙각은 세타 θ

방위각은 파이 Φ 로 나타내어진다.


어쨋든 이러한 앙각, 방위각, 그리고 앞서 극좌표에서도 다뤘던 r 을 이용해서

구면좌표계 에서는 한 점의 좌표를 ( r , θ , Φ ) 로 표현한다.


위에서 한번 언급했듯 구면좌표계 자체는 극좌표계에서 축을 하나 더 했을 뿐이다.

즉 기존의 식에서 추가된 축에 대한 계산 한번만 더 해주면 된다는 뜻이다. 

기본의 ( r Cosθ , r Sinθ ) 에서 추가된 z 축에 대한 계산이 추가될 뿐이다.


( r Sinθ CosΦ , r Cos θ , r Sinθ Sin Φ ) 


생각해보자 어떠한 좌표들이 계속 움직이는데 좌표가 위의 식에 의해서 결정이 된다면

어쨋든 이러한 원의 위를 돌아다니게 될것이다. 


대표적인 예로 

병크 어쌔신크리드 시리즈에서 뷰포인트에서 뷰를하면 카메라가 한바퀴 회전하면서

주변 광경을 찍어주는데 이 때 이러한 구면좌표계가 사용됬다고 이해하면 되겠다


※ 클래스 내부에서의 중첩 형식

클래스 내부에 클래스를 선언하는 것 또한 가능하지만 이러면 중첩클래스의 값들을 외부에서 ( 유니티 상에서 ) 수정하기에 제한이 있다. 이때는 중첩 클래스 위에 

[System.Serializable] 을 추가해주면 유니티 상에서 이 내부 중첩클래스에 접근이 가능해진다.


※ Mathf 의 Clamp 와 Repeat

Mathf 안에 있는 Clamp 라는 녀석은 자주 쓰이는 녀석으로 

Mathf.Clamp( value, min, max ) 를 통해 어떤 값이 최소,최대 범위를 넘어가지 않게 만들어주는데, Repeat 이란 녀석은 어떠한 역할을 하는지 다뤄보겠다. 


일단 이름에서 느껴지듯 뭔가 반복하는 녀석인데 이녀석은 인자가 2개이다.

Mathf.Repeat( t, length ) 라는데 일단 정의를 보면

루프값 t 는 legnth 보다 절대 클 수 없으며 0 보다 작을 수 없다. 그렇다. 이걸보면

정수론 시간에 했던 % 연산 ( 마듈러 modulo ) 연산과 비슷하다는 것을 느낄 것이다. 다만 이녀석은 floating 까지 커버 한다는 것이 다를 뿐이다.


즉 어떤 값 t 를 value 로써 넘기고 length 를 정해놓으면 t는 0 ~ length 사이를 반복하게 된다는 뜻이다. 


계속 커지기만 하는 값 a 가 있다고 하자. 이때 Mathf.Repeat( a , 50 ) 이라고 하면

그 반환값으로는

0, 1 , 2 , 3 , 4, .... 47, 48, 49, 0, 1, 2, 3, ..... 이런식으로 나오게 될것이다.


※ 앙각 / 방위각 / 반지름을 영어로

방위각 : Azimuthal Angle

앙각 : Elevation Angle

반지름 : Radius


※ 유니티의 삼각함수들은 라디안을 사용한다.

따라서 우리가 평소에 사용하는 90도 180도 를 사용하려면

우리가 말하는 각도 Degree 를 라디안으로 변환해주는 과정이 필요한데

이는 우리가 정한값 Degree 에 Mathf.Deg2Rad 를 곱해주는 것으로 가능하다.


그 반대의 경우는 Mathf.Rad2Deg

=======================================================

코드 예제로 넘어가자

이 코드는 어떤 한 물체를 가운데에 두고 그 물체 주변을 카메라가 구면좌표계 상을 돌아다니면서 찍는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 
    public float rotateSpeed = 1f;
    public float scrollSpeed = 200f;
    // 카메라 움직이는 속도와 휠돌렸을 때 가까워지고 멀어지는 속도
 
    public Transform pivot;
    // 목표를 포착했다.
 
    [System.Serializable] // 유니티 상에서 중첩선언된 클래스의 인자에 접근 가능하게
    public class SphericalCoordinates // 중첩 선언 클래스안의 클래스
    {
        public float _radius, _azimuth, _elevation; // 반지름, 방위각, 앙각 
 
        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; // 최대 반지름
 
        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)
        { // 벡터(목표) 를 인자로 받은 생성자
            _minAzimuth = Mathf.Deg2Rad * minAzimuth;
            _maxAzimuth = Mathf.Deg2Rad * maxAzimuth;
 
            _minElevation = Mathf.Deg2Rad * minElevation;
            _maxElevation = Mathf.Deg2Rad * maxElevation;
            // 삼각함수에서 사용하기위해 라디안으로 변환
 
            radius = cartesianCoordinate.magnitude; // 반지름의 크기
            azimuth = Mathf.Atan2(cartesianCoordinate.z, cartesianCoordinate.x);
            elevation = Mathf.Asin(cartesianCoordinate.y / radius);
            // 방위각, 앙각을 구함
        }
        
        public Vector3 toCartesian
        {
            get
            {
                float t = radius * Mathf.Cos(elevation);
                // ( r Sinθ CosΦ , r Cos θ , r Sinθ Sin Φ ) 를 기억하자
                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 SphericalCoordinates TranslateRadius(float x) {
            radius += x;
            return this;
            // 움직일때 그 값을 반영
        }
    }
    
    public SphericalCoordinates sphericalCoordinates;
    
    // Use this for initialization
    void Start () {
        sphericalCoordinates = new SphericalCoordinates(transform.position);
        transform.position = sphericalCoordinates.toCartesian + pivot.position;
    }
 
    // Update is called once per frame
    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");
        if (sw * sw > Mathf.Epsilon) {
            transform.position = sphericalCoordinates.TranslateRadius(sw * Time.deltaTime *
 scrollSpeed).toCartesian + pivot.position;
        }
 
        transform.LookAt(pivot.position);
    }
cs


댓글 로드 중…

블로그 정보

Clolent - 커피물조절달인

최근에 게시된 글