사실 앞서 Homogeneous 좌표와 Matrix를 설명한 이유는 지금 알아볼 MVP Matrix 때문입니다.
MVP Matrix를 사용안하고도 화면을 무언가 표현을 할수는 있었습니다. 하지만 MVP Matrix를 사용하지 않는 다면 오브젝트에 대한 이동, 회전, 스케일링 같은 변환 등을 통한 3D 공간감을 얻긴 힘들 것입니다.

MVP Matrix

MVP는 Model, View, Projection의 약자입니다.

 

글로는 개념을 설명하기에 모호한 점이 많아 게임엔진 툴인 Unity를 사용하여 MVP설명하고자 합니다.

Model

모델이란 스크린샷에서 정육면체(큐브)의 도형을 말합니다, 즉 화면에 나타내고자 하는 객체를 말합니다.

View

카메라 객체, 즉 Model객체를 바라보는 대상입니다. 디지털 카메라로 어떤 피사체를 찍을때 디지털카메라가 View에 해당하고 큐브가 피사체에 해당합니다. 

Projection

투영, 카메라가 모델을 볼수 있는 공간을 말합니다. 스크린샷에서 흰선의 사각뿔 모양으로 그려진 것이 바로 Frustum(절두체)라고 하는 Projection입니다. 프로젝션은 대표적으로 2가지가 있습니다.

  • Perspective Projection : 이 프로젝션은 사람의 눈이나 카메라가 세상을 바라 보는 것처럼 원근법을 적용하여 객체를 표현합니다.  굉장히 익숙한 느낌일 겁니다. 최종적으로 렌더링되는 결과물을 보면 익숙할겁니다.
  • Orthographic Projection : 이 프로젝션은 절두체 안에 있는 모든 오브젝트들을 원근과 왜곡 없이 표현합니다. 최종적으로 렌더링 되는 결과물을 보면 프로젝션내의 모든 오브젝트들을 그대로 압축해서 보여주는 느낌이 듭니다.

Projection View 예시

 

Orthographic View 예시

백문이 불여일견, 차이가 느껴지시나요?

아직 잘 와닿지 않는다면 Unity를 설치해서 저와 같이 큐브(Model)를 배치하고 카메라(View)의 Projection을 변경해보시기 바랍니다. 

MVP매트릭스 연산 방법

신기하면서 간단하게도 MVP매트릭스는 각 매트릭스를 모두 곱해주기만 하면됩니다. 단 순서는 지켜야합니다. 매우 중요합니다.

MVPMatrix = Projection * View * Model

MVP매트릭스를 이용하여 찌그러진 삼각형 바로 나타내기

왼쪽은 OpenGL의 기본적인 좌표계이고, 오른쪽은 안드로이드의 스크린 비율에 좌표계를 매핑한 모습.

먼저 삼각형을 그리기전에 가정을 하겠습니다.

  • 삼각형은 원점에 존재 합니다. 그러므로 Model 행렬은 따로 연산할 필요 없이 단위행렬로 지정합니다. 
  • 카메라도 원점에 있으면 삼각형을 보기 힘드므로 카메라는 z축으로 일정거리 만큼 떼어 두겠습니다.
  • 프로젝션 매트릭스는 Perspective로 만들겠습니다.

매트릭스 Model,View,Projection에 해당하는 행렬 3개를 만듭니다.

private final float[] projectionMatrix = new float[16];
private final float[] viewMatrix = new float[16];
private final float[] modelMatrix = new float[16];

Model을 포함한 나머지 매트릭스도 단위 행렬로 초기화 합니다.

Matrix.setIdentityM(projectionMatrix, 0);
Matrix.setIdentityM(viewMatrix, 0);
Matrix.setIdentityM(modelMatrix, 0);

View매트릭스와 Projection 매트릭스를 구해보도록 하겠습니다.

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0,0,width, height); // 뷰포트를 전체화면으로 맞춤
    float aspectRatio = (float) width/ height; // 가로 세로 비율을 구함
    Matrix.perspectiveM(projectionMatrix,0 , 60, aspectRatio, 1, 7);
    Matrix.setLookAtM(viewMatrix, 0,
            0.0f, 0.0f, -2.0f,
            0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f);
    // mvp = p * v * m (곱하는 순서 중요함)
    Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}

뷰포트 설정을 통해 전체화면을 설정하고, 스크린의 가로세로 비율을 구해둡니다.

Perspective 타입의 Projection Matrix를 구하기 위해  Matrix.perspectiveM(…) 메소드를 적용하겠습니다.
첫번째 인자는 매트릭스이고,
두번째 인자는 매트릭스 offset입니다
세번째 인자는 Field of View 이며, 사람으로 치면 볼수있는 시야폭입니다. 일반적으로 사람눈과 비슷하게 보려면 60정도가 적당합니다. 반드시 60일 필요는 없습니다.
네번째 인자는 프로젝션매트릭스의 가로세로 비율인데, 아까 구해둔 스크린의 가로세로 비율을 적용하여 찌그러진 화면을 프로젝션을 메트릭스의 비율을 통해 보완(?)합니다
다섯번째와 여섯번째 인자는 near, far인데 카메라가 볼수 있는 거리를 뜻합니다. 적당히 지정합니다.

이렇게 Projection Matrix는 구해졌습니다.

다음 View Matrix를 보겠습니다.

Matrix.setLookAtM(…)이라는 함수를 통해 View Matrix를 구할수 있습니다.

첫번째 인자는 매트릭스
두번째 인자는 매트릭스 offset입니다.
세번째부터 다섯번째까지는 eye인데요 카메라의 위치입니다.
여섯번째부터 여덟번째까지는 center입니다. 카메라의 눈이 바라보는 위치입니다.
아홉번째부터 11번째까지는 up벡터입니다. 카메라의 위쪽 방향을 뜻하며, (0,1,0) 이면 y축 방향으로 카메라를 위로 하게 됩니다. 

다음 이미지를 참고하시면 이해하기가 수월할것입니다.

안드로이드에서 두개의 매트릭스를 곱하기 위해서는 Matrix.multiplyMM(…)을 이용합니다.

구해진 View Matrix와 Projection Matrix를 순서를 잘 지켜서 곱합니다. 이렇게 곱해진 VP Matrix에 Model Matrix를 한번 더 곱하면 MVP Matrix가 되는것이지요.

Model(삼각형)은 원점 (0,0,0)에 위치하기로 정했으므로 그대로 단위행렬을 곱합니다.
(단위행렬은 곱해도 그대로 이므로 사실 MVP와 VP는 같습니다)

이렇게 완성된 MVP Matrix를 이제 정점 쉐이더에 넘겨 주면 됩니다.

uniform mat4 uMVPMatrix;
attribute vec4 vPosition;
...
void main() {
    ...
    gl_Position = uMVPMatrix * vPosition;
}

정점벡터와 MVP매트릭스를 곱하면 변환된 벡터(Transformed Vector)를 얻을수 있고 이를 gl_Position에 대입해주면 끝입니다.

직사각형의 스크린 비율 때문에 찌그러져 있던 삼각형이 매트릭스 변환으로 이쁘게 보입니다.

본 프로젝트는 github에서 확인 가능합니다.

카테고리: Graphics

2개의 댓글

ㅁㄴㅇㄹ · 2019년 10월 14일 2:55 오후

Projection 부분에서 ‘스크린샷에서 흰선의 삼각뿔 모양’ 라고 하셨는데, ‘사각뿔’이 맞지않나요?

    Charlezz · 2019년 10월 17일 11:49 오후

    수정했습니다. 감사합니다 🙂

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.