본문 바로가기

Programming/OpenGL

OpenGL Transformations_Using Own Matrix


이번에는 자신만의 행렬을 만들어서 사용 하는 방법에 대해서 얘기할 게요. 행렬에 대한 이론과 OpenGL의 변환에 대한 이론은 아래를 참고 하시면 될 거 같아요.

행렬에 대해서 보러 가기
OpenGL 변환

행렬은 그 생김새가 아래와 같이 되어 있어요.


그럼 위의 형태를 구현하려면 보통 2차원 배열을 생각하게 되겠죠? OpenGL에서 주의해야 할 것은, 원소에 접근 할 때 일반적으로 수학에서 생각하는 것처럼 행-열의 순번으로 원소를 가져오지 않고 열-행의 순서로 접근 한다는 거에요.
말 인즉, m3의 원소를 접근하고 싶으면 m[3][0]로 접근하는게 아니라 m[0][3]이라는 얘기죠. OpenGL이 열 중심(Column major)의 정책 이라서 딱히 뭐라고 할 여지는 없어요 :)


비단 여기에서 뿐만 아니라 간간히 이런 부분들에 대해서 헷갈리게 될 경우가 종종 있는데, 위처럼 열-행, 행-렬 의 순번도 있지만 이미지에서 처럼 좌하단이 (0, 0)으로 시작하는 경우도 있고..
물론 구현되어 있는 API를 사용한다면 그 부분에 대해서 잘 숙지해야 하고, 직접 구현 하는 거라면 해당 부분에 대해서 잘 명시해 둬야 겠죠? :D

  • 행렬 만들어 보기

    실제 구현에 있어서는 이 차원, 일 차원 상관은 없어요. 하지만 개인적으로는 원래 배열 만들 때 일 차원으로 하던게 있고, 서적에서도 일차원으로 만드는걸 알려 주기에 일 차원으로 가닥을 잡을 게요. 구현은 아래와 같이 간단 해요.
    GLfloat identityMat[16] = 
    { 1.0, 0.0, 0.0, 0.0,
      0.0, 1.0, 0.0, 0.0,
      0.0, 0.0, 1.0, 0.0,
      0.0, 0.0, 0.0, 1.0 };
    		


    위의 식은 단위 행렬을 구현한 거에요. 여기서 주의할 점은 어디까지나 행렬은 열 중심(Column major)이라는 거에요. 단위 행렬의 경우에는 어떻게 보나 모양이 같기 때문에 구현에 있어서는 보통 우리가 생각하는 거처럼 행 중심(Row major) 처럼 보이게 되지만, 실제로는 column을 의미 한다는 거죠.


    저렇게 들어간다는 거죠.

    실제로 내부에서 생성되는 행렬을 확인 해 볼 때도 마찮가지라는 것을 알 수 있어요.
    // m_position[0] == -3.0
    // m_position[1] == 0.0
    // m_position[2] == 3.0
    glTranslated(m_position[0], m_position[1], m_position[2]);
    GLdouble mviewMat[16];
    glGetDoublev(GL_MODELVIEW_MATRIX, mviewMat);
    
    for(size_t idx = 0 ; idx < 16 ; ++idx)
    	printf("%l ", mviewMat[idx]);
    		



    일 차원 행렬로 그대로 가져 왔을 때, 열 중심으로 되어 있는 상태인 것을 알 수 있어요 :) 어떠한 형태로 돌아가는지 만 알면 그 다음 부터는 버릇 들이는 수밖에 없을 거 같아요;_;
  • 행렬 스택 초기화 시키기

    만들어진 행렬을 적용 시켜야 겠죠? OpenGL에서 제공하는 함수 중에 glLoadIdentity() 라는 함수가 있었죠. 이 함수가 호출 되면, 행렬 스택에 모든 데이터가 없어지고 단위행렬로 초기화 되게 되요. 그럼 우리가 만든 "임의의 행렬로 초기화" 시키는 것도 가능 하겠죠? 임의의 행렬로 행렬 스택을 초기화 시키는 함수는 아래와 같아요.
    • 함수 원형

      void glLoadMatrixf(const GLfloat *matrix);
      void glLoadMatrixd(const GLdouble *matrix);
      				

    • 인자

      matrix 16개의 값을 가진 행렬
    • 반환 값

      void
    • 처리

      스택을 입력된 인자 matrix 행렬로 초기화 시킨다.
      두 함수의 차이는 끝에 f와 d 인데, 이는 배열의 형태가 float이냐, double이냐를 판단하는 거에요.

    설명에서는 임의의 행렬이 단위 행렬이 되었지만, 애초에 입력되는 행렬의 데이터를 변경 시켜서 함수를 호출하게 되면, 적어도 단위행렬과 곱해지게 되는 하나의 스텝은 넘어가게 되겠죠? 나중엔 그림자나 재질에 대해서도 행렬로 처리를 하게 되는데 행렬자체에 대한 매커니즘만 이해해도 아름답게 구현이 가능해 져요 :D
  • 행렬 연산 하기

    모델뷰 행렬에서 신나게(??!) 행렬과 연관시켜서 설명 했다시피, 행렬은 스택에 쌓이면서 곱연산으로 이루어 진다고 얘기 했었죠. 그런데 실제로 우리가 사용 했던 것들은 이동, 확대, 회전 에 대한 함수만을 불러왔잖아요?
    이제 우리는 행렬을 입맛대로 만들 수 있는 방법을 알았으니까 그 행렬들을 적용 시켜서 원하는 위치로 정점들이 이동 시킬 수 있다는 의미가 되요. 바로 아래의 함수를 통해서 행렬 연산을 할 수 있어요.
    • 함수 원형

      void glMultMatrixf(const GLfloat *matrix);
      void glMultMatirxd(const GLdouble *matrix);
      				

    • 인자

      matrix : 16개의 값을 가진 배열
    • 반환 값

      void
    • 처리

      행렬 스택에 쌓여있는 행렬에 인자로 들어온 matrix를 곱 연산 시킨다.

    여기서 "반드시!" 알아 두어야 할 수학적 이론은 교환법칙이 성립 하지 않는게 일반적 이에요.


    물론, 같아지는 경우가 있긴 한대, 그건 어디까지나 예외적인 사항인 거고 일반적으로는 위의 식이 성립 해요.

    OpenGL에서는 곱연산을 새로 넣는 것은 뒤쪽에 하게 되어 있어요. 가장 나중에 적용시킨 연산행렬(또는 그런 행렬을 만들어서 적용 시켜주는 OpenGL 함수 호출)이 뒤쪽에서 곱해진다는 거죠. 이동, 확대, 회전에 대해서 설명해 볼게요.
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glTranslated(3.0, 0.0, 0.0); // x축으로 3.0만큼 이동
    glScaled(1.0, 1.0, 1.0); // 1.0배씩, 즉 원본 크기 그대로
    glRotated(45.0, 0.0, 0.0, 1.0); // (0, 0, 1)방향을 회전축으로 45도 회전
    
    // do some draw..
    		


    위의 각각 세가지 식을 행렬로 나타내 보면 아래와 같이 되요.


    연산은 불러온 순서대로 차례로 뒤쪽에 곱해 져서 최종적인 행렬 M이 나오게 되는 거에요.


    DX의 경우에는 이 연산 순서가 반대라고 알고 있어요. 즉, 위의 순서대로 입력하게 되면, R*S*T의 순서로 연산이 되는 거죠.
    연산 순서가 중요한 이유는 아까도 얘기했다시피 이론적인 측면에서는 교환법칙이 성립하지 않는 다는 거고, 실질적으로 구현하는 측면에서는 저 순서대로 하게 되면,
    OpenGL에서는 "정점을 회전하고, 확대 시킨 다음에, 이동 시켜"가 되는 거고
    DX에서는 "정점을 이동하고, 확대 시킨 다음에, 회전 시켜"가 되는 거죠.


    엄연히 뭐가 다른지 알겠죠?ㅎ

  • 사용자 행렬로 적용

    위의 코드를 가지고 사용자 행렬을 만들어서 모두 적용 시켜 보는 실습을 해볼게요.
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(3.0, 0.0, 0.0); // x축으로 3.0만큼 이동
    glScaled(1.0, 1.0, 1.0); // 1.0배씩, 즉 원본 크기 그대로
    glRotated(45.0, 0.0, 0.0, 1.0); // (0, 0, 1)방향을 회전축으로 45도 회전
    
    // do some draw..
    		


    행렬은 아래와 같이 되겠죠?


    그럼 각각에 대한 행렬을 구현 하면 아래와 같아요
    GLdouble identity[16] = {
    	1.0, 0.0, 0.0, 0.0,
    	0.0, 1.0, 0.0, 0.0,
    	0.0, 0.0, 1.0, 0.0,
    	0.0, 0.0, 0.0, 1.0
    	};
    
    GLdouble translate[16] = {
    	1.0, 0.0, 0.0, 0.0,
    	0.0, 1.0, 0.0, 0.0,
    	0.0, 0.0, 1.0, 0.0,
    	3.0, 0.0, 0.0, 1.0
    	};
    
    GLdouble scaling[16] = {
    	1.0, 0.0, 0.0, 0.0,
    	0.0, 1.0, 0.0, 0.0,
    	0.0, 0.0, 1.0, 0.0,
    	0.0, 0.0, 0.0, 1.0
    	};
    
    GLdouble rotate[16] = {
    	cos(0.79), sin(0.79), 0.0, 0.0,
    	-sin(0.79), cos(0.79), 0.0, 0.0,
    	0.0, 0.0, 1.0, 0.0,
    	0.0, 0.0, 0.0, 1.0
    	};
    		


    위의 행렬값 들을 OpenGL한테 직접 보내는 거죠!
    glMatrixMode(GL_MODELVIEW);
    // glLoadIdentity();
    glLoadMatrix(identity);
    
    // glTranslated(3.0, 0.0, 0.0); // x축으로 3.0만큼 이동
    glMultMatrixd(translate);
    
    // glScaled(1.0, 1.0, 1.0); // 1.0배씩, 즉 원본 크기 그대로
    glMultMatrixd(scaling);
    
    // glRotated(45.0, 0.0, 0.0, 1.0); // (0, 0, 1)방향을 회전축으로 45도 회전
    glMultMatrixd(rotate);
    
    // do some draw..
    		


    위의 예제에서는 glMultMatrixd(..)라는 함수를 사용 했지만, 들어가는 인자가 GLfloat 배열 형태라면 glMultMatrixf가 되겠죠?
    물론, 위에서는 예시를 위해서 값을 일일이 넣었지만 실제로 구현 할 때는 변수를 이용해서 만들어야 이쁠거에요. :D

참조 : OPENGL GAME PROGRAMMING(Foreword by Mark J.Kilgard)

'Programming > OpenGL' 카테고리의 다른 글

OpenGL Transformations_Projection  (0) 2012.04.03
OpenGL Transformation_Matrix  (4) 2012.03.22
OpenGL Transformations_Modeling  (0) 2012.03.17
OpenGL Transformations_Viewing(Camera)  (0) 2012.03.15
Understanding Coordiante Transformations  (0) 2012.03.13