3장 변환

Posted by 쏘엔
2019. 3. 24. 20:46 DirectX 12/기초 수학

이 포스팅의 내용은 DirectX 12를 이용한 3D 게임 프로그래밍 입문 (프랭크 D. 루나 저) 책과 인터넷을 참고하여 간단히 정리하였다.


3차원 그래픽에 쓰이는 주된 기하학적 변환은 이동변환, 회전변환, 비례변환이다. 이번 장에서는 3차원 공간의 점과 벡터를 변환하는 데 사용할 수 있는 행렬 방정식을 살펴본다.


  • 선형변환

수학 함수  는 3차원 벡터 하나를 입력받아 3차원 벡터 하나를 출력한다. 만일 함수

에 대해 다음과 같은 성질이 성립하면 를 가리켜 선형변환(linear transformation)이라고 부른다.

여기서 와 는 임의의 3차원 벡터이고 는 스칼라이다.


예)

를 생각해보자 예를들어 이다. 그런데 이 함수는 선형변환이 아니다.

에 대해

이지만 

이기 때문이다.


  • 행렬 표현
선형변환 행렬 표현(matrix representation)은 다음과 같다.



  • 비례
비례변환(scaling, 확대·축소)은 물체의 크기를 바꾸는 효과를 낸다.
비례행렬은 다음과 같이 표현한다.

 이를 비례행렬이라하고,  이를 비례행렬의 역이라 한다.


예)

최솟점 (-4, -4, 0)과 최댓점 (4, 4, 0)으로 정의된 사각형을 z축은 그대로 놔두고, x축으로 0.5단위, y축으로 2단위 비례해보자. 이를 비례행렬로 표현하면 다음과 같다.

이제 사각형을 실제로 비례(변환)하려면, 최솟점과 최댓점에 이 행렬을 곱하면 된다.

            



  • 회전
회전행렬(rotation matrix)는 다음과 같다.

 여기서 이다.

회전행렬에는 흥미로운 성질이 있는데, 행렬의 각 행벡터는 단위 길이이고, 행벡터들은 서로 직교이다. 따라서 행벡터들은 정규직교이다.

직교행렬에는 그 역행렬이 자신의 전치행렬과 같다는 성질이 있다. 즉, 회전행렬의 역은 다음과 같다.


특히 회전축이 x축이나, y축, z축이면(즉, n = (1, 0, 0)이나 n = (0, 1, 0), n = (0, 0, 1)이면) 회전행렬이 간단해진다.

다음은 각각 x, y, z축에 대한 회전행렬이다.


예)

최솟점(-1, 0, -1)과 최댓점(1, 0, 1)로 정의된 사각형을 y축에 대해 시계방향으로 -30º(반시계방향으로 30º) 회전한다고 하자. 이 경우 n=(0, 1, 0) 이므로 해당 y축 회전행렬을 쉽게 구할 수 있다.(위에 행렬을 그대로 대입)

 이제 실제로 사각형을 회전하려면, 최솟점과 최대점에 이 행렬을 곱하면 된다.



 



  • 아핀변환
아핀변환(affine transformation; 또는 어파인변환, 상관변환)은 선형변환에 이동변환(translation transformation)을 결합한 것이다.
아핀변환은 선형변환에 이동벡터 b를 더한 것이다. 이를 수식으로 표현하면 다음과 같다.



  • 동차좌표
동차좌표(homogeneous coordinate)는 3차원 벡터에 w 성분을 추가한 네값쌍의 형태인데, 주어진 동차좌표가 점을 나타내느냐 벡터를 나타내느냐는 이 w의 값이 결정한다. 즉,

1. 벡터를 나타내는 동차좌표는 

2. 점을 나타내는 동차좌표는 

이렇게 표기한다.

벡터는 위치와 무관하게 오직 방향과 크기만 서술하는 것이므로, 벡터에 대해서는 이동이 의미가 없다.(벡터의 성분이 바뀌면 벡터의 본질적인 속성인 방향과 크기가 변하므로, 벡터에 이동변환을 적용했을 때 벡터의 성분들이 바뀌어서는 안 된다)

따라서 점에만 이동변환이 제대로 수행될 수 있게끔 위와같은 동차좌표를 이용해야한다.


  • 아핀변환의 행렬 표현
w = 1인 동차좌표를 도입하면 아핀변환을 다음과 같이 표현할 수 있다.

b는 본질적으로 하나의 이동(위치 변경)을 나타낸다.

다시 한번 강조하지만 벡터는 위치가 없으므로 벡터에 대해서는 이동을 적용하지 말아야한다. 하지만 아핀변환의 선형변환 부분은 벡터에 적용해야하므로, 벡터의 동차좌표의 넷째 성분을 0으로, 즉 w = 0으로 설정하면 b에 대한 이동은 적용되지 않는다.


  • 이동
이동행렬(translation matrix)과 이동행렬의 역은 다음과 같이 표현한다.

                



예)

최솟점 (-8, 2, 0)과 최댓점 (-2, 8, 0)으로 정의되는 사각형을 x축을 따라 12단위, y축을 따라 -10.0단위를 이동한다고 하자. z축 위치는 그대로 둔다. 이러한 이동에 해당하는 이동행렬은 다음과 같다.

이제 이 행렬을 최솟점과 최댓점에 곱하면 사각형이 실제로 이동(변환)한다.

 (점 이므로 4번째 원소(w)가 1임을 명심하자)



  • 비례와 회전을 위한 아핀변환 행렬
만약 b=0이면 아핀변환이 그냥 보통의 선형변환과 같음을 기억하자. 따라서, 그 어떤 선형변환이라도 b=0인 아핀변환으로 표현할 수 있다. 이는 임의의 선형변환을 다음과 같이 4 x 4 아핀변환 행렬로 표기할 수 있다는 뜻이다. 예를 들어 비례변환과 회전변환을 각각 다음과 같은 4 x 4 행렬로 표현할 수 있다.

                

이런 방식을 이용하면 배운 모든 종류의 변환을 일관되게 4 x 4 행렬로 나타낼 수 있으며, 점과 벡터를 1 x 4 동차 행벡터로 나타낼 수 있다.



  • 변환들의 합성
S가 비례행렬이고, R이 회전행렬, T가 이동행렬이라고 할 때, i=0, 1...~7인 여덟 정점들로 이루어진 직육면체의 각 정점에 이 세 변환을 연달아 적용하려 한다. 이를 수행하는 가장 확실한 방법은 다음처럼 행렬들을 하나씩 차례로 적용하는 것이다.

하지만 행렬 곱셈은 결합법칙을 만족하므로, 이를 다음과 같이 표기해도 된다.

이러한 합성은 성능에 영향을 미친다. 첫 번째 방법처럼 세 가지 기하 변환을 연달아 적용한다고 가정하면, 벡터 대 행렬 곱셈이 20,000 x 3회 필요하다.

하지만 두 번째 방법처럼 합성시키면 벡터 대 행렬 곱셈 20,000회에 행렬 대 행렬 곱셈 2회면 된다.



  • 좌표 변경 변환
물은 섭씨 100도에서 끓는다. 하지만 화씨에서는 물이 100도에서 끓지 않는다. 이는 체계의 기준 차이때문이다. 물은 언제나 동일한 온도에서 끓는다.
위 예처럼 좌표계마다 좌표를 표현하는 방법이 다르다. 하지만 기하구조 자체가 변하는 것은 아니다. 한 좌표계의 좌표를 다른 좌표계의 좌표로 변환하는 것을 좌표 변경 변환(change of coordinate transformation)이라고 부른다.
점에 대한 좌표계 변환과 벡터에 대한 좌표계 변환은 서로 다르다.
하지만 동차좌표를 이용하면 서로 다른 두 공식을 하나로 표현할 수 있다. 이전에도 말했다시피 w의 성분만 적절히 설정하면 된다.(w = 0이면 벡터에 대한 좌표 변경 변환 공식이되고, w = 1이면  점에 대한 좌표 변경 변환 공식이 된다.)

이에대한 좌표 변경 행렬(change of coordinate matrix)는 다음과 같다.


'DirectX 12 > 기초 수학' 카테고리의 다른 글

2장 행렬 대수  (0) 2019.03.13
1장 벡터 대수  (0) 2019.03.13

행렬 예제 프로그램

Posted by 쏘엔
2019. 3. 24. 03:36 DirectX 12/실습

행렬 예제 프로그램(소스).zip

이번에는 DirectXMath에 포함되어있는 행렬 함수들을 라이브러리를 사용하지 않고 직접 코딩해서 작성해보았다.

2개의 클래스가 있다.

첫 번째 MATRIX 클래스는 1개의 행렬을 내부적으로 double **형으로 저장해서 관리하는 클래스이고,

Matrix.h)

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
/*
    1개의 행렬을 double **형으로 관리하는 행렬 클래스이다.
    Matrix.h
*/
#pragma once
#include <iostream>
#include <stdarg.h>
using namespace std;
class MATRIX
{
private:
    int row, col;    // 이 행렬의 행과 열이다.
    double **matrix;    // 이 행렬의 원소를 가지게 되는 행렬 자체이다.
 
public:
    // 아무 인자없이 생성자로 생성하면 row와 col이 1로 초기화되어 할당된다.
    MATRIX()
    {
        row = col = 1;
        Allocator();
    }
 
    // 행과 열을 입력하여 생성하면 그에 맞는 크기로 할당된다.
    MATRIX(int _row, int _col) : row(_row), col(_col) { Allocator(); }
 
    // double ** 형을 입력하면 그것을 행렬로 간주하고 저장한다.
    MATRIX(double ** _matrix)
    {
        if (_matrix != nullptr)
            matrix = _matrix;
 
        else
            cout << "유효하지 않은 행렬을 입력했습니다. 행렬 생성 실패" << endl;
    }
 
    // double ** 형 + 행 갯수 + 열 갯수 생성자
    MATRIX(double ** _matrix, int _row, int _col)
    {
        if (_matrix != nullptr)
        {
            matrix = _matrix;
            row = _row;
            col = _col;
        }
 
        else
            cout << "유효하지 않은 행렬을 입력했습니다. 행렬 생성 실패" << endl;
    }
 
    void SetDemension(int _row, int _col);                // 행렬의 차원을 설정하는 메소드
    void Allocator();                                    // m x n의 행렬과 전치행렬을 동적할당하는 메소드
    void Deallocator();                                    // 동적할당을 해제하는 메소드(사용 후 반드시 호출해야함)
    void PrintMatrix();                                    // 행렬의 원소를 출력하는 메소드
    inline int GetRow(){return row;}                    // 행을 리턴하는 메소드
    inline int GetCol(){return col;};                    // 열을 리턴하는 메소드
    inline void SetRow(int _row){row = _row;}            // 행을 셋팅하는 메소드
    inline void SetCol(int _col){col = _col;}            // 열을 셋팅하는 메소드
    bool SetElements(int _args, ...);                    // 원소를 셋팅하는 메소드(총 갯수로 따짐)
    double ** GetMatrix();                                // 행렬을 얻음
    bool SetMatrix(double ** _matrix);                    // 행렬을 셋팅함
};
cs

Matrix.cpp)

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
/*
Matrix 클래스 정의
Matrix.cpp
*/
#include "Matrix.h"
 
void MATRIX::Allocator()
{
    if (matrix == nullptr)
    {
        // m x n의 배열을 메모리 동적할당
        matrix = new double*[row];
 
        for (int i = 0; i < row; i++)
        {
            matrix[i] = new double[col];
            memset(matrix[i], 0sizeof(double* col);
        }
    }
}
 
void MATRIX::Deallocator()
{
    if (matrix != nullptr)
    {
        // 메모리 동적할당 해제
        for (int i = 0; i < row; i++)
            delete[]matrix[i];
 
        delete[]matrix;
 
        matrix = nullptr;
    }
}
 
void MATRIX::PrintMatrix()
{
    // 할당이 되어있어야만 출력이 가능
    if (matrix != nullptr)
    {
        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                cout << matrix[i][j] << "\t";
            }
            cout << endl;
        }
        cout << endl;
    }
}
 
void MATRIX::SetDemension(int _row, int _col)
{
    // 할당된게 이미 있다면 할당을 해제한다.
    if (matrix != nullptr)
        Deallocator();
 
    // m, n 변수를 새로운 값으로 대체하고
    row = _row;
    col = _col;
 
    // 새롭게 할당한다.
    Allocator();
}
 
bool MATRIX::SetElements(int _args, ...)
{
    double arg;
    // 원소의 갯수와 행렬의 차원이 같아야함
    if (row * col != _args)
    {
        cout << "원소 입력 실패" << endl;
        return false;
    }
 
    va_list ap;    // 가변 인자용 변수 ap
    va_start(ap, _args);    // 첫 번째 가변인수를 가리키도록 셋팅
 
    for (int k = 0, i = 0, j = 0; k < _args; k++, j++)
    {
        // 해당 행의 열을 다 채우면 다음 행을 채워야함
        if (j == col)
        {
            j = 0;
            i++;
        }
 
        // 실제로 동적할당 배열에 저장
        arg = va_arg(ap, double);
        matrix[i][j] = arg;
    }
 
    va_end(ap);    // 뒷정리
 
    cout << "원소 입력 성공" << endl;
    return true;
}
 
double ** MATRIX::GetMatrix()
{
    return matrix;
}
 
bool MATRIX::SetMatrix(double ** _matrix)
{
    if (_matrix == nullptr)
        return false;
 
    else
    {
        matrix = _matrix;
        return true;
    }
}
 
cs


두 번째 MATRIX_MANAGER 클래스는 static 메소드의 집합 클래스로, 행렬 연산에 필요한 메소드를 제공하는 클래스이다.

MatrixManager.h)

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
/*
행렬에 관련된 메소드를 제공하는 클래스이다.(선언)
MatrixManager.h
*/
#pragma once
#include <iostream>
#include "Matrix.h"
#define FIX 0                // 행렬식 공식에서 고정되는 인덱스
#define ERROR_CODE -99999    // 행렬식 에러코드
 
// 2x2 소행렬의 행렬식을 구하는 매크로 함수
#define TwoByTwo(matrix) ( ( (matrix[0][0]) * (matrix[1][1]) ) - ( (matrix[0][1]) * (matrix[1][0]) ) );
 
using namespace std;
class MATRIX_MANGER
{
public:
    static double GetDeterminant(MATRIX _matrix);    // 4x4 이하의 행렬식을 구하는 메소드
    static MATRIX GetInverseMatrix(MATRIX _matrix);            // 역행렬을 구하는 메소드
    static MATRIX GetTransposeMatrix(MATRIX _matrix);            // 전치행렬을 만들고 리턴하는 메소드(MATRIX 클래스 버전)
    static double ** GetTransposeMatrix(double ** _matrix, int _row, int _col);            // 전치행렬을 만들고 리턴하는 메소드(double ** 버전) 사용 후에는 메모리 누수 방지를 위해 꼭 CustomDeallocator()를 사용해야한다.
    
    ////// 1번째인자:저장시킬 소행렬, 2번째:기준이 되는 원래 행렬식, 3번째:정방행렬 차원, 4번째:소거할 열
    static void GetMinorMatix(double **_minor, double **_matrix, int _n, int _removeRow, int _removeCol);    // 소행렬을 구하는 메소드
    static MATRIX GetMultiplyMatrix(MATRIX _m1, MATRIX _m2);        // 두 행렬의 곱을 리턴하는 메소드
    static MATRIX GetIdentity(int _demension);                    // 인자로 넘어온 차원의 단위행렬을 리턴하는 메소드
    static bool IsIdentity(MATRIX _matrix);                        // 단위 행렬인지
    static double ** CustomAllocator(int _row, int _col);        // 원하는 크기대로 동적할당시킬 메소드
    static void CustomDeallocator(double **_matrix, int _demension);    // 커스텀 동적할당 해제시킬 메소드
};
cs

 MatrixManager.cpp)

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*
MatrixManager의 정의 (행렬에 필요한 메소드를 정의)
MatrixManager.cpp
*/
#include "MatrixManager.h"
 
MATRIX MATRIX_MANGER::GetTransposeMatrix(MATRIX _matrix)
{
    // 전치행렬은 원래 행렬의 행과 열의 갯수가 다르다.
    double ** transpose = MATRIX_MANGER::CustomAllocator(_matrix.GetCol(), _matrix.GetRow());
    double ** matrix = _matrix.GetMatrix();
 
    for (int i = 0; i < _matrix.GetRow(); i++)
    {
        for (int j = 0; j < _matrix.GetCol(); j++)
        {
            transpose[j][i] = matrix[i][j];    // 전치행렬은 원래행렬의 행과 열을 바꾼것과 같다.
        }
    }
 
    // 전치행렬은 행과 열이 바뀜에 주의하자
    MATRIX ret(transpose, _matrix.GetCol(), _matrix.GetRow());
    return ret;
}
 
double ** MATRIX_MANGER::GetTransposeMatrix(double ** _matrix, int _row, int _col)
{
    // 전치행렬은 원래 행렬의 행과 열의 갯수가 다르다.
    double ** transpose = MATRIX_MANGER::CustomAllocator(_col, _row);
 
    for (int i = 0; i < _row; i++)
    {
        for (int j = 0; j < _col; j++)
        {
            transpose[j][i] = _matrix[i][j];    // 전치행렬은 원래행렬의 행과 열을 바꾼것과 같다.
        }
    }
 
    return transpose;    // 사용 후에는 반드시 CustomDeallocator()를 사용해야함!
}
 
double MATRIX_MANGER::GetDeterminant(MATRIX _matrix)
{
    // 4x4를 초과하는 행렬은 허용하지 않는다
    if (_matrix.GetRow() != _matrix.GetCol() || (_matrix.GetRow() > 4))
        return ERROR_CODE;
 
    double det = 0;    // 실제 리턴할 행렬식 값
    switch (_matrix.GetRow())
    {
        // 2x2의 행렬식은 ad-bc로 정의된다.
    case 2:
        det = TwoByTwo(_matrix.GetMatrix());
        break;
 
        // 3x3의 행렬식은 재귀적으로 2x2행렬의 집합으로 이루어진다.
    case 3:
    {
        double **matrix = _matrix.GetMatrix();
        double **minor = nullptr;    // 소행렬
        bool sign = true;    // 부호는 +,-,+,-가 반복된다.
        double val = 0;
 
        for (int i = 0; i < _matrix.GetRow(); i++)
        {
            minor = CustomAllocator(22);    // 소행렬을 위해 2x2 크기로 동적할당
            GetMinorMatix(minor, matrix, _matrix.GetRow(), FIX, i);    // 소행렬을 얻어서 minor에 저장시킴
 
            val += (sign ? 1 : -1* matrix[FIX][i] * TwoByTwo(minor);    // 부호 곱하기 2x2행렬 공식인 ad-bc * 해당원소를 넣어주면 된다.
 
            // 아까 동적할당한 자원을 해제시킴
            if (minor != nullptr)
                CustomDeallocator(minor, 2);
 
            sign = !sign;    // 부호 스왑
        }
        det = val;
    }
    break;
 
    // 4x4를 초과하는 행렬은 가우스소거법 같은 방법이 더 효율적이다.
    case 4:
    {
        double **matrix = _matrix.GetMatrix();
        double **minor1 = nullptr;    // 3x3 행렬식
        double **minor2 = nullptr;    // 2x2 행렬식
        bool sign1 = true;    // 3x3행렬식의 부호는 +,-,+,-가 반복된다.
        bool sign2 = true;    // 2x2행렬식의 부호
 
        double val[4];// 각 소행렬의 값을 저장할 배열 (4x4행렬식은 4개의 3x3행렬식으로 이루어짐)
        memset(val, 0sizeof(double* 4);    // 값 초기화
 
        for (int i = 0; i < _matrix.GetRow(); i++, sign2 = true)
        {
            minor1 = CustomAllocator(33);    // 소행렬을 위해 3x3 크기로 동적할당
            GetMinorMatix(minor1, matrix, _matrix.GetRow(), FIX, i);    // 소행렬을 얻어서 minor1에 저장시킴
 
            for (int j = 0; j < _matrix.GetRow() - 1; j++)
            {
                // 방금 위에서 얻은 행렬식의 행렬식을 구한다(재귀적인 성질)
                minor2 = CustomAllocator(22);    // 소행렬을 위해 2x2 크기로 동적할당
                GetMinorMatix(minor2, minor1, _matrix.GetRow() - 1, FIX, j);
 
                val[i] += (sign2 ? 1 : -1* minor1[FIX][j] * TwoByTwo(minor2);    // 행렬식의 공식 적용
 
                sign2 = !sign2;    // 부호 스왑
 
                // 2x2 소행렬 할당 해제
                if (minor2 != nullptr)
                    CustomDeallocator(minor2, 2);
            }
 
            val[i] *= (sign1 ? 1 : -1* matrix[FIX][i];    // 3x3행렬식의 부호*해당원소의 값을 곱해서 방금 나온 값에 더해줌
 
            // 3x3 소행렬 할당 해제
            if (minor1 != nullptr)
                CustomDeallocator(minor1, 3);
 
            sign1 = !sign1;    // 부호 스왑
        }
 
        // 나온 값을 모~두 더해줌
        for (int i = 0; i < 4; i++)
            det += val[i];
    }
    break;
    }
 
    return det;    // 계산해서 나온 행렬식 리턴
}
 
void MATRIX_MANGER::GetMinorMatix(double **_minor, double **_matrix, int _n, int _removeRow, int _removeCol)
{
    // x=minor의 행, y=minor의 열
    int x, y;
    bool checker = false;    // 조건문에 들어갔는지 검사용
    x = y = 0;
 
    for (int i = 0; i < _n; i++)
    {
        // i행을 소거해야할 때는 그냥 무시
        if (i == _removeRow)
            continue;
 
        for (int j = 0; j < _n; j++)
        {
            // j열을 소거해야할 때에도 무시
            if (j == _removeCol)
                continue;
 
            // matrix의 실직적인 소행렬 minor를 저장함
            else
            {
                checker = true;
                _minor[x][y++= _matrix[i][j];
            }
        }
 
        // 소행렬을 다음 행, 첫 열로 이동
        if (checker == true)
        {
            x++;
            y = 0;
        }
    }
}
 
double ** MATRIX_MANGER::CustomAllocator(int _row, int _col)
{
    double ** matrix = nullptr;
 
    // m x n의 배열을 메모리 동적할당
    matrix = new double*[_row];
 
    for (int i = 0; i < _row; i++)
    {
        matrix[i] = new double[_col];
        memset(matrix[i], 0sizeof(double* _col);
    }
 
    return matrix;
}
 
void MATRIX_MANGER::CustomDeallocator(double **_matrix, int _demension)
{
    if (_matrix != nullptr)
    {
        // 메모리 동적할당 해제
        for (int i = 0; i < _demension; i++)
            delete[]_matrix[i];
 
        delete[]_matrix;
 
        _matrix = nullptr;
    }
}
 
MATRIX MATRIX_MANGER::GetInverseMatrix(MATRIX _matrix)
{
    // 역행렬은 정방행렬에만 있다.
    if (_matrix.GetRow() != _matrix.GetCol())
    {
        cout << "정방행렬이 아니므로 역행렬을 구할 수 없습니다." << endl;
        return NULL;
    }
 
    // 행렬식값이 0이면 특이행렬(역행렬이 없음)
    if (GetDeterminant(_matrix) == 0)
    {
        cout << "가역행렬이 아닙니다." << endl;
        return NULL;
    }
 
    // 4x4미만의 행렬만 역행렬을 구한다.
    if (_matrix.GetRow() > 4 || _matrix.GetCol() > 4)
    {
        cout << "4x4미만의 행렬만 가능합니다." << endl;
        return NULL;
    }
 
    double **inverse = CustomAllocator(_matrix.GetRow(), _matrix.GetCol());    // 일단 역행렬을 위한 할당
    double **minor = nullptr;    // 소행렬을 위한 배열
    double val = 0;        // 행렬식 값을 임시로 저장하기 위한 변수
    bool sign = true;    // 부호를 + - + - 로 활용하기 위한 변수
 
    // 행, 열의 갯수만큼 반복문을 돌려서
    for (int i = 0; i < _matrix.GetRow(); i++)
    {
        for (int j = 0; j < _matrix.GetCol(); j++)
        {
            minor = CustomAllocator(_matrix.GetRow() - 1, _matrix.GetCol() - 1);    // 소행렬을 위해 (row-1)x(col-1) 크기로 동적할당
            GetMinorMatix(minor, _matrix.GetMatrix(), _matrix.GetRow(), i, j);    // 소행렬을 얻어서 minor에 저장시킴(소거행열은 i,j)
 
            MATRIX sudDet(minor, _matrix.GetRow() - 1, _matrix.GetCol() - 1);    // 아까 얻은 소행렬을 MATRIX 인스턴스로 만듦
 
            // (i+1)+(j+1)가 (공식으로는 i+j이지만 인덱스는 0부터 시작하므로) 짝 수 이면 부호가 양수이고, 홀 수 이면 음수가 된다.
            if (((i + 1+ (j + 1)) % 2 == 0)
                sign = true;
 
            else
                sign = false;
 
            // 부호 * 소행렬의 행렬식값을 곱해준다
            val = (sign == true ? 1 : -1* GetDeterminant(sudDet);
 
            // 0에 마이너스 부호 붙는 것 방지
            if (val == 0.0)
                val = 0;
 
            inverse[i][j] = val;    // 이를 역행렬 배열에 저장한다.
 
            // 사용한 자원은 반납해야 메모리 누수가 없다.
            if (minor != nullptr)
                CustomDeallocator(minor, _matrix.GetRow() - 1);
        }
    }
 
    // 위에서 만든 여인수행렬을 전치행렬화 하여 딸림행렬로 만든다.(리턴값이 딸림행렬이다)
    inverse = MATRIX_MANGER::GetTransposeMatrix(inverse, _matrix.GetRow(), _matrix.GetCol());
 
    // 역행렬은 원래행렬의 행렬식(det) 값을 딸림행렬의 각 요소에 나눠주면 된다.
    double det = MATRIX_MANGER::GetDeterminant(_matrix);
    for (int i = 0; i < _matrix.GetRow(); i++)
        for (int j = 0; j < _matrix.GetCol(); j++)
            inverse[i][j] /= det;
 
    // 방금 만든 역행렬(double **형)을 MATRIX 인스턴스로 만들어서 리턴한다.
    MATRIX MatInverse(inverse, _matrix.GetRow(), _matrix.GetCol());
    return MatInverse;
}
 
MATRIX MATRIX_MANGER::GetMultiplyMatrix(MATRIX _m1, MATRIX _m2)
{
    // A행렬의 열의 갯수와 B행렬의 행의 갯수가 일치하지 않으면 곱셈은 성립하지 않는다.
    if (_m1.GetCol() != _m2.GetRow())
    {
        cout << "곱셈이 정의되지 않습니다." << endl;
        return NULL;
    }
 
    // 곱셈이 성립할 때 생성되는 행렬의 행과 열의 수
    int row = _m1.GetRow();
    int col = _m2.GetCol();
 
    double **mat1 = _m1.GetMatrix();    // 행렬 A의 double **형
    double **mat2 = _m2.GetMatrix();    // 행렬 B의 double **형
    double **ret = MATRIX_MANGER::CustomAllocator(row, col);    // 곱셈으로 생성되는 행렬은 (A의 행 x B의 열) 차원이다.
 
    double val = .0;    // 곱한 결과를 저장할 변수
    int x, y;            // 결과 행렬의 인덱스
    x = y = 0;
 
    // 행렬의 곱 순서대로 곱한 결과를 ret에 저장한다.
    // A = m x n,    B = n x p 일 때, C = m x p 와 같다.
    for (int i = 0, k = 0; i < _m1.GetRow(); k++, val = 0)
    {
        for (int j = 0; j < _m2.GetRow(); j++)
        {
            val += mat1[i][j] * mat2[j][k];        // 곱한 결과를 저장해둔다.
        }
 
        // 곱한 결과를 C(결과)행렬에 저장하고, 다음 인덱스를 넘어가야하면 조건에 맞게 처리한다.
        ret[x][y++= val;
        if (y == col)
        {
            x++;        // 다음 행
            y = 0;        // 첫 열로
 
 
            k = -1;    // B는 다시 처음처럼 곱셈을 해야하므로(바로 for문 조건으로 인해 1 증가하므로 -1이다)
            i++;    // A를 다음 행으로
        }
    }
 
    // 결과를 MATRIX로 만들어 리턴
    MATRIX matRet(ret, row, col);
    return matRet;
}
 
MATRIX MATRIX_MANGER::GetIdentity(int _demension)
{
    // 파라미터 차원의 행렬을 생성한다.
    double **ret = CustomAllocator(_demension, _demension);
 
    // 단위행렬을 만들고
    for (int i = 0; i < _demension; i++)
        ret[i][i] = 1.0;
 
    // 리턴한다.
    MATRIX matRet(ret, _demension, _demension);
    return matRet;
}
 
bool MATRIX_MANGER::IsIdentity(MATRIX _matrix)
{
    if (_matrix.GetRow() != _matrix.GetCol())
        return false;
 
    double **matrix = _matrix.GetMatrix();
 
    for (int i = 0; i < _matrix.GetRow(); i++)
    {
        for (int j = 0; j < _matrix.GetCol(); j++)
        {
            // main diagonal일 때 1이 아니면 단위행렬이 아니다.
            if (i == j)
            {
                if (matrix[i][j] != 1)
                    return false;
 
                else
                    continue;
            }
 
            // 그 외에는 모두 0이어야 한다.
            if (matrix[i][j] != 0)
                return false;
        }
    }
 
    return true;
}
cs



main 함수에서 행렬을 설정하고, 단위행렬을 얻고, 곱셈, 전치행렬, 행렬식, 역행렬 등을 구한다.

main.cpp)

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
/*
행렬 예제 프로그램
main.cpp
*/
 
#include <iostream>
#include "MatrixManager.h"
#include "Matrix.h"
using namespace std;
 
int main()
{
    MATRIX A(44);        // 4x4 크기의 행렬 A를 생성한다.
 
    // 원소를 채운다.
    A.SetElements(16,
        1.00.00.00.0,
        0.02.00.00.0,
        0.00.04.00.0,
        1.02.03.01.0);
 
    MATRIX B = MATRIX_MANGER::GetIdentity(A.GetRow());        // 단위 행렬을 얻는다.
 
    MATRIX C = MATRIX_MANGER::GetMultiplyMatrix(A, B);        // A와 B를 곱해서 나온 결과 행렬을 C에 저장한다.
 
    MATRIX D = MATRIX_MANGER::GetTransposeMatrix(A);        // A행렬의 전치행렬을 얻어서 D에 저장한다.
 
    double det = MATRIX_MANGER::GetDeterminant(A);            // A의 행렬식을 구해서 det에 저장한다.
    MATRIX E = MATRIX_MANGER::GetInverseMatrix(A);            // A의 역행렬을 구해서 E에 저장한다.
 
    MATRIX F = MATRIX_MANGER::GetMultiplyMatrix(A, E);        // A와 E를 곱한다.
 
    // 연산 결과를 출력한다.
    cout << "A = " << endl;
    A.PrintMatrix();
    cout << endl;
 
    cout << "B = " << endl;
    B.PrintMatrix();
    cout << endl;
 
    cout << "C = A*B = " << endl;
    C.PrintMatrix();
    cout << endl;
 
    cout << "D = transpose(A) = " << endl;
    D.PrintMatrix();
    cout << endl;
 
    cout << "det = determinant(A) = " << det << endl << endl;
 
    cout << "E = inverse(A) = " << endl;
    E.PrintMatrix();
    cout << endl;
 
    cout << "F = A*E = " << endl;
    F.PrintMatrix();
    cout << endl;
 
    // 메모리를 반납한다.
    A.Deallocator();
    B.Deallocator();
    C.Deallocator();
    D.Deallocator();
    E.Deallocator();
    F.Deallocator();
    return 0;
}
cs


실행화면)




원리를 생각하면서 한 번이라도 코딩해보는게 많은 도움이 되는 것 같다.


잘못된 점은 따끔히 지적해주시고, 더 좋은 의견이 있으시면 언제든지 덧글로 남겨주세요.

2장 행렬 대수

Posted by 쏘엔
2019. 3. 13. 16:33 DirectX 12/기초 수학

이 포스팅의 내용은 DirectX 12를 이용한 3D 게임 프로그래밍 입문 (프랭크 D. 루나 저) 책과 인터넷을 참고하여 간단히 정리하였다.


  • 행렬의 정의
 m x n 행렬(matrix) M은 m개의 행(row)과 n개의 열(column)로 이루어진 실수들의 정사각 배열이다. 행들의 개수와 열들 개수의 곱(이를테면 4 x 4)을 행렬의 차원이라고 부른다. 행렬을 구성하는 수들을 성분(entry) 또는 원소(element)라고 부른다.
행렬의 한 성분을 나타낼 때는  이런 형태로 표기한다. 아래 첨자 첫 번째는 행이고 두 번째는 열이다. (i가 행, j가 열)


예)


행렬 A는 4 x 4 행렬이고, 4행 3열 즉, 은 를 의미한다. 특히, 행과 열의 갯수가 같은 행렬을 정방행렬(square matrix)라고 한다.



  • 행렬의 상등, 덧셈, 스칼라 곱셈, 뺄셈의 연산
1. 두 행렬이 같은 조건은 두 행렬의 행과 열의 갯수가 같아야 하며(즉, 두 행렬의 차원이 같아야 한다.) 대응되는 성분들이 모두 같아야만 두 행렬은 같다고 한다.

2. 두 행렬을 더할 때는 대응되는 성분들을 더한다. 따라서 차원이 같은 행렬들만 더할 수 있다.

3. 행렬에 하나의 스칼라를 곱할 때는 행렬의 모든 성분에 그 스칼라를 곱한다.

4. 행렬의 뺄셈은 스칼라 곱셈과 행렬 덧셈으로 정의한다. 즉, A - B = A + (-1 · B) = A +(-B) 이다.

예)

위와 같은 행렬이 있을 때


1. A + B = 


2. A = C


3. 3D = 


4. A - B = 


행렬 덧셈과 스칼라 곱셈은 성분별로 이루어지므로, 행렬 덧셈과 스칼라 곱셈도 실수의 덧셈 및 곱셈의 다음과 같은 성질들을 만족한다.

1. A + B = B + A                    덧셈의 교환법칙

2. (A + B) + C = A + (B + C)     덧셈의 결합법칙

3. r(A + B) = rA + rB               행렬들에 대한 스칼라의 분배법칙

4. (r+s)A = rA +sA                  스칼라들에 대한 행렬의 분배법칙


  • 행렬의 곱셈
A가 m x n 행렬이고, B가 n x p 행렬이면 둘의 곱 AB가 정의된다. 곱 AB는 하나의 m x p 행렬이다. 두 행렬의 곱을 C라고 할 때, C의 ij번째 성분은 A의 i번째 행벡터와 B의 j번째 열벡터의 내적이다.

즉, 이다.

행렬 곱 AB가 정의되려면 A 행렬의 열의 개수B 행렬의 행의 개수가 일치해야 한다. 차원이 일치하지 않으면 두 곱은 정의되지 않는다.


예)

이 두 행렬이 있을 때 A의 열의 개수는 2이고, B의  행의 갯수는 3이므로 곱셈이 정의되지 않는다.


이 두 행렬이 있을 때 A의 열의 개수는 3이고, B의 행의 갯수는 3이므로 두 행렬의 곱셈이 정의된다.

주의할 점은 곱 BA는 정의되지 않는다. 왜냐하면 B의 열 개수는 3이지만, A의 행 개수는 2이기 때문이다. 이렇듯 행렬의 곱셈은 일반적으로 교환법칙이 성립하지 않는다.



  • 분배법칙과 결합법칙
행렬 곱셈에는 몇 가지 대수적 성질이 존재하는데, 우선 행렬 곱셈은 덧셈에 대한 분배법칙을 만족한다.
즉, A(B + C) = AB + AC이다.
또, 결합법칙을 만족하기 때문에 행렬을 곱하는 순서를 적절히 선택할 수 있다.
(AB)C = A(BC)


  • 전치행렬(transpose matrix)

행렬의 전치(transpose), 즉 전치행렬은 행렬의 행들과 열들을 맞바꾼 것이다. 따라서 m x n 행렬의 전치는 n x m 행렬이다. 행렬 M의 전치행렬을 로 표기한다.

예)

이 행렬의 전치행렬을 구하면

이렇게 된다.


전치행렬의 유용한 성질

1. 

2. 

3. 

4. 

5. 


  • 단위행렬(identity Matrix)
단위행렬은 정방행렬(열 개수와 행 개수가 같은 행렬)에서 좌상에서 우하로의 대각선에 있는 성분(이를 주대각(main diagonal) 성분이라 한다.)들을 1로 채운 행렬을 말한다.
즉, 단위행렬은 좌상에서 우하로의 대각선 성분들만 1이고 나머지는 0인 정방행렬이다.
예)

2 x 2, 3 x 3, 4 x 4의 단위행렬이다.


단위행렬은 곱셈의 항등원 역할을 한다.

A가 m x n 행렬이고, B가 n x p 행렬, I가 n x n의 단위행렬이면 반드시

AI = A이고 IB = B이다.

다른 말로 하면, 어떤 행렬에 단위행렬을 곱해도 그 행렬은 변하지 않는다.

특히, M이 정방행렬일 때 단위행렬과의 곱셈은 교환법칙이 성립한다.

IM = MI = M


  • 행렬식(determinant)
행렬식의 정의는 '정방행렬을 입력받아서 실숫값을 출력하는 특별한 함수'이다. 정방행렬 A의 행렬식은    det A    로 표기한다.
행렬식은 행렬의 역을 구할 때 사용된다. 또한 다음과 같은 정리가 있다.
정방행렬 A는 만일 이면 그 때에만 역행렬이 존재한다. (가역행렬이라고도 한다)

행렬식을 정의하기 전에 소행렬이라는 개념부터 알고 가는 게 좋을 것 같다.


  • 소행렬(minor matrix)

n x n 행렬 A가 주어졌을 때, 그 소행렬 는 A의 i번째 행과 j번째 열을 삭제해서 나온 (n - 1) x (n - 1) 행렬이다.


예)

이런 행렬이 있을 때, 각각의 소행렬은 다음과 같다.


  • 행렬식의 정의
행렬의 행렬식은 재귀적으로 정의된다. 예를 들어 4 x 4 행렬의 행렬식은 3 x 3 행렬의 행렬식들로 정의되고, 3 x 3 행렬의 행렬식은 2 x 2 행렬의 행렬식들로 정의된다.

공식)
A가 n x n 행렬이고, n > 1일 때, A의 행렬식은

예)

이 행렬의 행렬식을 구하라

2 x 2 행렬의 행렬식을 계산할 때는 ad - bc를 해주면 된다.



  • 딸림행렬(adjoint matrix)

A가 n x n 행렬이라고 할 때, 곱 를 의 여인수(cofactor)라고 부른다.

A의 각 성분에 여인수 를 계산해서 해당 번째 위치에 넣은 행렬을 여인수 행렬(cofactor matrix)이라고 부른다.

↑여인수 행렬


그리고 이 여인수 행렬의 전치행렬을 딸림행렬이라고 부르고 다음과 같이 표기한다.

딸림행렬을 이용하면 행렬의 역을 계산하는 공식을 이용할 수 있다.


  • 역행렬(inverse matrix)
행렬 대수에서 곱셈의 역원을 행렬의 역(inverse) 또는 역행렬이라고 부른다.

다음은 역행렬의 주요 정보이다.
1. 역행렬은 정방행렬에만 있다. 따라서 역행렬이라 하면 정방행렬이라는 의미가 내포되어있는 것과 같다.

2. n x n 행렬 M의 역은 n x n 행렬이며, 로 표기한다.

3. 모든 정방행렬에 역행렬이 있는 것이 아니며, 역행렬이 있는 행렬을 가역행렬(invertible matrix)이라고 부르고, 역행렬이 없는 행렬을 특이행렬(singular matrix)이라고 부른다.

4. 역행렬이 존재하는 경우 그 역행렬은 고유하다.

5. 행렬에 그 역행렬을 곱하면 단위행렬이 나온다. 즉, 이다. 행렬과 그 역행렬의 곱셈은 행렬의 곱셈에서 교환법칙이 성립하는 특이한 경우다.


지금까지 소행렬과 행렬식, 딸림행렬을 공부한 이유는 역행렬의 공식을 사용하기 위함인데, 역행렬의 공식은 다음과 같다.

역행렬 공식)

참고로 4 x 4 행렬 이하의 경우 딸림행렬을 이용한 계산법이 효율적이지만, 그 이상의 행렬을 처리할 때에는 가우스 소거법 같은 방법이 쓰인다.


마지막으로, A와 B가 같은 차원의 가역 정방행렬이라 할 때 다음 성질이 성립한다.


증명)


'DirectX 12 > 기초 수학' 카테고리의 다른 글

3장 변환  (0) 2019.03.24
1장 벡터 대수  (0) 2019.03.13

1장 벡터 대수

Posted by 쏘엔
2019. 3. 13. 03:30 DirectX 12/기초 수학


이 포스팅의 내용은 DirectX 12를 이용한 3D 게임 프로그래밍 입문 (프랭크 D. 루나 저) 책과 인터넷을 참고하여 간단히 정리하였다.


  • 벡터의 정의

'벡터(vector)'는 크기방향을 모두 가진 수량을 가리키는 말이다.

벡터의 예로는 힘(force)이 있다. 힘은 특정한 방향과 세기로 가해지기 때문이다. 

개미가 북쪽으로 10미터 나아가는 것도 벡터라고 할 수 있다. 크기는 10미터이고 방향은 북쪽이 된다.

벡터는 위 그림과 같이 표현할 수 있다.

선분의 길이는 벡터의 크기를 나타내고, 화살표는 방향을 나타낸다.

화살표가 있는 방향을 '머리'라 하고, 화살표가 없는 반대편을 '꼬리'라고 한다.


벡터의 중요한 성질은, 위치무관하다는 것이다.

그러니까 크기와 방향이 같다면 그 위치가 달라도 두 벡터는 동일하다.


  • 왼손잡이 좌표계와 오른손잡이 좌표계

(이미지 출처 : http://www.learnopengles.com/understanding-opengls-matrices/left_right_hand/)

2D 그래픽을 표현할 때에는 그냥 2차원 좌표계(x와 y만 존재하는)를 사용하면 되지만, 3D 그래픽을 표현할 때에는 z축의 방향에 따라 좌표계가 달라진다. 
위 그림을 보면 이해가 쉬운데, 두 좌표계 모두 x축과 y축의 방향은 동일하지만, z축(가운뎃 손가락)의 방향이 다른 것을 알 수 있다. 가운뎃 손가락이 가리키는 방향이 z축이 양수(+)가 되는 방향이다. 
3차원 공간에 벡터를 표현하려면 3개의 점을 이용한다.    ex)    
프로그램 내에서는 벡터를 3개의 실수값(float이나 double)을 이용해서 표현한다.
같은 벡터라도 좌표계에 따라 표현이 달라질 수 있음에 주의해야 한다. 참고로 Direct3D는 왼손 좌표계이다.


  • 기본적인 벡터 연산들

두 벡터가 있다.

1. 두 벡터가 동일하려면 각 좌표의 성분이 모두 같아야만 동일하다.
ex)

일때에만  이다.


2. 벡터의 덧셈은 각 성분별로 이루어진다.
ex)



3. 벡터에 스칼라(scalar, 크기만 있는 것)를 곱할 수 있으며 벡터에 스칼라를 곱하면 결과는 벡터이다.
ex) 스칼라 K를 벡터 V에 곱하면 


4. 벡터의 뺄셈은 벡터 덧셈과 스칼라 곱셈을 통해서 정의된다.
ex)

참고로 벡터에 부호를 바꾸면 방향이 완전 반대가 된다.    벡터에 스칼라를 곱셈하면 벡터의 크기(길이)가 늘어난다.



  • 벡터의 크기

    <- 벡터의 크기는 수식으로 이렇게 표현한다. 벡터의 크기는 피타고라스의 정리를 2번 이용하면 나오는 데 어려운 이야기는 건너뛰고 벡터 크기의 공식은 아래와 같다.



  • 단위 벡터(unit vector)

벡터의 크기는 중요하지 않고, 방향만 필요할 때가 있다. 그럴 때 단위 벡터를 사용하고, 어떤 벡터를 단위 벡터로 만드는 것을 정규화(normalization)이라고 부른다. 벡터의 각 성분을 벡터의 크기로 나누면 벡터가 정규화된다.



  • 벡터의 내적(dot product)
점곱이라고도 부른다.
두 벡터를 곱하는 벡터 곱셈이다. 주의할 점은, 결과값이 스칼라로 나온다는 것이다.

내적의 정의만 봐서는 내적의 기하학적 의미가 분명하지 않은데, 코사인 법칙을 적용해보면 다음과 같은 관계를 찾아낼 수 있다.

여기서 θ는 벡터 u와 v사이의 0보다 크거나 같고 파이보다 작거나 같은 값을 만족하는 각도 값이다.



  • 벡터의 외적(cross product)

또 다른 벡터의 곱셈으로는 외적이 있다.

내적은 결과가 스칼라가 나왔지만 외적은 벡터가 나온다. 또한 외적은 3차원 벡터에 대해서만 적용된다.(2차원 벡터는 외적이라는 개념이 없다)    그리고 외적은 교환법칙이 성립되지 않는다.

외적을 이용하면 두 벡터의 직교인 벡터가 나온다.

    두 벡터가 있을 때 이 둘을 외적한 벡터 w는 아래와 같이 구할 수 있다.

외적한 벡터 w는 벡터 u와 벡터 v에 대해서 직교인 벡터이다.

왼손을 펼쳐서 첫 벡터 u의 방향을 가리킨 상태에서 둘째 벡터 v의 방향(첫 벡터와의 각도가 90도 이하가 되는 쪽)으로 말아쥐었을 때 엄치 손가락이 가리키는 방향이 바로 w(직교 벡터) = u x v의 방향이다. 이를 왼손 엄지 법칙(left-hand-thumb rule)이라 부른다. 위 그림을 보고 직접 한번 해보면 이해가 빠르게 될 것이다.



  • 위치 벡터(점)

지금까지 살펴본 벡터는 위치를 사용하지 않는다. 하지만 3차원 그래픽 프로그램에서는 공간 안의 어떤 위치를 지정할 수 있어야 한다. 특정 좌표계를 기준으로 표준 위치에 있는(꼬리가 원점과 일치하도록 이동된 벡터) 벡터를 3차원 공간 안의 한 위치를 나타내는데 사용하는 벡터를 위치벡터라고 부른다.

위치벡터에서 중요한 것은 벡터의 머리 끝 좌표이다.  그러므로 다른 말로는 이라고 부른다.
위치벡터에서 의미 있게 적용할 수 있는 연산들이 있다. 예를 들어, 두 점(두 위치벡터)의 차 q - p를 p에서 q로 가는 벡터라고 정의할 수 있다.
또, 점 p(위치벡터) 더하기 벡터 v를 p의 위치를 v만큼 옮겼을 때 도달하는 점이라고도 정의할 수 있다.
이 연산들은 벡터의 뺄셈과 덧셈으로 적용할 수 있으므로 기존의 벡터 대수 연산을 그대로 사용하면 된다.


'DirectX 12 > 기초 수학' 카테고리의 다른 글

3장 변환  (0) 2019.03.24
2장 행렬 대수  (0) 2019.03.13