본문 바로가기

Programming/Architecture

Architecture my OpenGL engine core1


일단, 한바탕 울음을 쏟고 포스팅을 시작 해야겠어요. ㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ
기존에 만들어 놓은걸 살포시 갈아엎고 다시 설계하기 시작 했거든요. 기존 설계는 확장을 하려면 너무 하드코딩하는 다형성이 전-혀 들어가 있지 않은 설계를 했기에, 어느정도 공부 후에 다시 시작 하고 있습니다.ㅜ 크흙

다행스러운건 기존의 함수들을 구지 없애 면서 할 필요는 없기 때문에, 프로세스에는 그렇게 큰 영향은 없어 보이는 거? 그래서 설계 초기 부터 생각의 흐름자체를 쭉- 정리해 나가는 게 설명하기도 좋을거 같고, 나중에 이런 부분이 제 스스로도 스킬적인 역량으로 쌓인다는 희망을 가지고 시작 할게요 :)
  • 전체적인 구성과 목적

    설계에 앞서 구성을 먼저 해야 겠죠? 제가 알아본 부분과 앞으로 이렇게 만들어야 겠다~ 하는 부분은 아래와 같아요.
    • 오브젝트 에디팅 툴(애니메이션, 이펙트 포함)
    • 물리효과 툴
    • 레벨 디자인 툴
    • 스크립트 툴
    • 실제 게임 플레이

    이 중에서 가장 기본이 되는 부분은 아무래도 오브젝트 그 자체 겠죠. 뭔가 보여지는 게 있어야 플레이를 하잖아요?(물론 현재 게임엔지 만들기 포스팅은 아직까지 기술적인게 들어가 있지는 않지만요,,, :D)

    뭐니뭐니 해도 중요하게 집고 넘어가야 하는 부분은 "확장성" 이에요.

    오브젝트 에디팅 툴만 보더라도, 프로그래머나 그래픽 디자이너가 이 툴을 이용해서 만들 수도 있지만, 다른 여타 그래픽 툴을 이용해서 만든 오브젝트를 가져올 수가 있어야 편하겠죠. 저마다 편하게 다룰 수 있는 툴이 있는데, 이거만 써야 한다! 라고 고집하는 거도 웃긴거죠.
    물리효과는 오브젝트들이 역동적으로 실세계와 비슷한 움직임을 가지도록 하는 거죠. 이 역시 확장성과 관련이 있죠. 기존에 만들어진 오브젝트들에 대해서 "물리효과" 라는 새로운 개념을 넣어 주되, "오브젝트 자체의 틀을 갈아 엎지 않게" 하는 거죠.
    레벨 디자인이나 스크립트 툴과 같은 경우에는 아직까지 생각해 본적이 없어요^^;;; 일단 위의 저 두 개만 생각하더라도 박터지니까요~ :D (우헤히헬라라케카히가헤하히)

    실제 게임 플레이 까지는 아니더라도, 어느정도 잘 만들어 지고 있는지에 대해서 테스트를 해봐야 하니까 테스팅 할 수 있는 UI가 필요 해요. 물론 이전에 단일 화면 하나만 보여지는 건 이미 만들었기 때문에 거기에 부가적인 부분을 넣어 가면서 발전 시켜야 겠죠.

    첫 걸음은 오브젝트 에디팅 툴이에요. 이 툴을 만들기 위해서 기본적인 것들의 동작들을 먼저 구성 한 후에, 조합해서 툴을 만드는 거죠.
    만들면서 확인해야 하는 부분은 아래와 같이 진행하려고 해요.
    • 오브젝트
    • 디스플레이
    • 컨트롤


  • 오브젝트 설계

    유즈케이스(UseCase)는 당장은 없어요. 단일 오브젝트에 대해서만 확인 하는게 첫 번째 목표니까요. 당장은 데이터 구조에 대한 부분만 필요하므로 이 부분을 어떻게 설계 해나가는지 같이 가보아요.

    다시 한번 강조하면서 넘어가지만, 같이 공부하자라는 취지의 포스팅이므로 무조건 적인 기술부분을 언급하지는 않아 아시는 분이라면 지루 할 수 있습니다.

    • Step1. 필요한 클래스들의 정리

      전체적으로 필요한 세 개의 클래스들은 일단 만들어 놓았지만, 지금은 오브젝트에 관한 포스팅이기 때문에 그 부분에 초점을 맞춰서 진행 하도록 할게요.

      앞서 만들려는 부분에 대해서 일단 만들었어요.


      DISPLAY, CONTROL은 추후에 진행되므로 OBJECT를 먼저 볼게요.

      오브젝트라 함은, 보여지거나 보여지지 않는 게임 플레이 하는 내부에 항상 존재하는 것들이에요. 그려지는 경우에는 그에 대한 추가적인 정보가 더욱 필요하니까 기존 클래스를 상속받아서 만들어 지면 되겠죠?


      엄밀히 이야기 하자면, OBJECT는 그 자체로 객체를 생성하면 안되기 때문에 추상 클래스화 시켜야 해요. 그래야 괜시리 오브젝트로 뭔가를 만드는 실수를 방지 할 수 있으니까요.


      보이지 않는(=그려지지 않는) 오브젝트의 대표적인 것은 Camera가 있어요. 그거 외에는 보이지 않는 오브젝트는 딱히 생각이 나지 않아서, 일단은 그에 대한 상위 클래스를 만들지 않고 바로 카메라 클래스로 만들었어요.


      그려지는 오브젝트이긴 하지만, 3D 객체 아닌 것들이 있더라구요. 바로 TEXT와 ICON. 이 둘은 Render 된다는 속성보다 단순히 "보여진다"의 속성이 더 강하게 있으므로 별도로 만들었어요.


      한 단계가 거의 끝나가니까, 이제 어떤 정보들이 필요 할 것인가에 대해서 적어 봤어요.


      가독성을 위해서 지금은 한글로 작성해 보았어요.
      추후에는 영문위주로 작성하되, 아직까지 네이밍이 되어 있지 않는 부분은 한글로 작성할 예정이에요.


      같은 내용들을 가지는 것들이 있죠. 그래서 이 부분을 OBJECT로 다 넘겨 줄 거에요. 그게 바로 상속의 의미니까요:)


      이제 "위치"와 "방향"의 관점에서 생각해 본다면,
      위치가 있다면, "이동"을 한다는 의미
      방향이 있다면, "회전"을 한다는 의미
      가 성립되는 거죠.

      게임내에서 한 자리에 박혀서 움직이지 않는 오브젝트는 사실 없다고 보면되요. 이미 해당 게임이 로딩 되었을 때, 오브젝트를 그 자리에 위치시키기 위해서 프로그래머가 이동 시킨거니까요.


      그럼 이러한 기능을 별도로 빼볼게요.


      기능적으로 분리 해놓는 것이 추후에 확장할 때 매우 좋더라구요.(컴퓨터를 전공 했음에도 이제 알게 된..ㅜㅜ)

      잠시 RenderObject를 살펴 보자면, 기존의 OBJECT에 "그려지는 기능"이 추가 된거잖아요.
      아까는 변수의 정보를 통해서 MoveAble이나 RotateAble의 기능이 있다는 것으로 유추했지만, 반대로 할 수도 있어요. 생각의 전환!


      "그려진다" 라는 기능을 발휘 하려면,
      • 어떻게 그려질 것인가에 대한 정점들의 정보,
      • 빛에 대해서 어떻게 비춰질까 에 대한 재질 정보,
      • 실제 껍대기는 어떻게 보여질까에 대한 텍스쳐 정보,
      • 실제 껍대기가 없다면 무슨 색으로 보여질까 에 대한 색상 정보

      가 필요 하죠. 그럼 변수와 기능에 대해서 아래처럼 재구성 할 수 있을 거에요.


      포스팅의 모든 내용을 갈아 엎을 수가 없어서 일부 빼먹은 내용을 여기서 부터 추가 했어요.
      • TEXT의 색갈
      • ICON의 영역


      이제 다시 중첩되는 기능이 있는지 한번 더 확인해 봤어요.
      • RenderObject와 ICON의 크기(TEXT는 폰트 사이즈 이므로 이름은 같지만 의미는 달라요.)
      • RenderAble과 ICON의 텍스쳐
      • TEXT와 RenderAble의 색갈
      • TEXT와 ICON의 영역

      그리고 변수에 대해서 먼저 생각 한것이니까, 기능적인 생각을 해봐야겠죠?
      • 크기 : 사이즈를 조절 한다.
      • 텍스쳐 : 어떤 텍스쳐를 어디 부터 어디까지 입힐지를 정한다.
      • 색갈 : 색상을 변경 한다.
      • 영역 : 영역의 크기를 변경 한다.

      그리고 이것들은 "기능적 가능함"의 측면보다 "속성적 변경"의 측면이 강하니까 Has를 붙이려고 해요.(이렇게 해도 맞는가는 모르겠어요ㅎ 만드는 사람 맘이긴 한데, 대부분이 사용하는지가 사실 더 궁금하거든요ㅋㅋ)


      점점 거미줄이 만들어 지네요. 후후훗... 하지만, 이전에 한거 보다는 훨씬 간단하다는 생각이 들기는 해요.
      추가적인 내용! RenderObject 재설계 2012.08.10
      RenderObject가 가진 RenderAble는 Rendering 하는 오브젝트로 생각을 했는데, 다른 부분에 대해서 보다 보니까 일반 오브젝트에 대해서만 그런게 아니라, ICON이나 TEXT도 Rendering 하더라구요;;;
      부득이 하게 RenderAble을 분리시키면서, 그것이 가지고 있는 정점과 텍스쳐는 분리시켰어요. 기존에 가지고 있었던 일반 Object가 그려질 때 사용되는 정보를 다른 곳에서도 쓰기도 할 뿐더러, "Rendering" 그 자체는 다른 오브젝트들(ICON, TEXT)도 기능이 다르게 사용되는 부분이니까요:)

      바뀐 정보는 아래와 같아요.

    • Step2. RenderObject 심화 설계

      말은 거창하지만, 뼈대만 만들어 놓는 과정이라 사실 어려울 거도 없어요. 여기 부터는 앞서 있었던 부분은 빼고 RenderObject 중심으로 설명하려고 해요.
      설계하면서 햇갈릴 수도 있으니까, 노트를 붙여 놓는 것도 좋아요. 자기 뿐만 아니라, 다른 사람도 알아보기 쉬우면 좋죠.


      추후에 애니메이션을 하려면, 오브젝트에 특정한 정보가 들어가야 해요. 그게 바로 Bone 이라는 거죠. 애니메이션화 시킬 때를 생각해 본건 아래와 같아요.


      클래스에 대해서 정리 할 때, Bone이라는 정보를 가진 오브젝트를 BoneObject. 그리고 가졌다라는 측면이 강하기 때문에 Has를 사용한 추상클래스를 만들었어요.


      구현적인 측면에서 보았을 때, Bone이 저런 행동들을 하게 되면, 실제로 오브젝트의 정보도 변경이 되야 하는게 있으니까, BoneObject가 가지고 있는 정점정보라던지, 회전에 대한 정보라든지 가 따라가면서 바뀌어야 하죠.
      그럼 Redering()할 당시에 그에 대한 정보도 바뀌어서 구현해야 하겠죠.

    • Step3. 오브젝트 집합의 설계

      실제로 게임상에서 구현되는 부분에서 중첩되는 같은 오브젝트를 여러 개 사용 하는 경우가 있어요. 가령 자동차를 예를 들면 바퀴는 모두 같은 오브젝트들이에요. 이 부분에 대해서 오브젝트를 여러 개 만들 필요는 없는거죠.


      설명을 위해 인덱스로 접근한다는 식으로 했지만, 구현상 포인터도 상관없고 저렇게 인덱스도 상관없고, 입맛에 맞춰서 하면 되요.
      얘기의 주안점은 저렇게 같은 오브젝트 여러 개를 사용 하는 경우가 많다는 거죠.

      저러한 단일 오브젝트들을 여러 개 가지고 있는 오브젝트 집합이 있어야 겠죠? 네이밍은 StaticObject 이라고 해뒀어요. 왜 그렇게 했는지는 이따가 설명할게요.


      다형성에 의해서 RenderObject[1..*]이 그냥 단일 객체로써 RenderObject가 들어갈 수도 있지만, BoneObject가 들어갈 수도 있게 끔 해둔거죠.
      동적배열로 들어갈 수 있게 끔 하는 것 외에 자료 구조를 어떻게 할 것인가는 구현하는 사람의 취향에 따라가는 거에요:D

    • Step4. 움직임이 있는 오브젝트

      움직이는 오브젝트라 함은 임의로 세워놓은 규칙에 의해서 이동/회전/크기 변화 등을 하게 되는 오브젝트 이죠. 하지만 움직이지 않는 오브젝트도 있어야 하잖아요? 그건 그냥 방금전에 설계 했던 ObjectSet 인거죠. 가령 길거리에 있는 돌맹이나, 석상, 가로수, 등등 게임상에서 보여지기는 하지만 움직임은 별도로 존재 하지 않는 그런 오브젝트들이요.

      기존에 있기 때문에 별도로 신경은 쓰지 않아도 되요.

      하지만, 움직이는 오브젝트(Animated Object)는 별도의 정보가 더 들어가겠죠? 그리고 "움직 일 수 있다(Be Able to Animate)"라는 기능으로 분류하는 것이 알아보기도 좋구요. 단일 오브젝트 하나만 있는게 움직이는 경우는 흔치 않아요. 그만큼 오브젝트들의 디테일의 더욱 살리거나 (위 예시의 차량 처럼) 비슷한 오브젝트 여러개들이 합쳐지는 경우가 많기 때문에, 단일 오브젝트 여러 개가 뭉쳐 있는 것을 사용하게 되는거죠.

      AnimateAble의 기능을 받은 StaticObject(정적 오브젝트)는 DynamicObject(동적 오브젝트)가 될 수 있어요.


      애니메이션이 들어가게 되면, 단순히 한 가지의 행동만을 하게 되는 건 아니죠. 사람으로 치면, 걷기도 하고, 뛰기도 하고, 춤도 추고, 등등등... 이제 이건 FSM(Finite-state machine, 유한 상태 기계)이라는 기법을 사용 하게 되는 거죠. 특정 상태에 있다가, 다른 상태로 되게 되면 그에 대한 행동을 하게 되는 거에요.

      FSM은 비단 이런 것 뿐만 아니라, AI(Artificial Intelligence, 인공지능)등 다양한 부분에 대해서 응용 될 수 있어요 :D


      그럼 애니메이션 측면에서 보았을 때는,
      • 어떠한 상태들을 가지고 있는가(AnimationState datas)
      • 현재 어떤 액션을 취하고 있는가에 대한 상태(State Index)
      • 상태가 진행된 시간(단위는 정해져 있지 않음)

      에 대한 정보가 주어져야 할거에요. 그럼 그에 대해서 새로운 클래스들을 사용 해야겠죠?


      "애니메이션의 상태"는 BoneObject를 컨트롤 할 수 있어야 해요. 위에서 그렸던 Bone에 대한 정보들을 건드려야 그 한도 내에서 정해진 시간 동안 움직임을 취하게 할 수 있을 테니까요.

      그 부분이 바로 "규칙" 이에요.

      규칙은 딱히 정해져 있지 않아요. 규칙1, 규칙2, ... 이런식으로 만들어 진다면, 새로운 규칙이 생길 때 마다 그 부분을 별도로 구현하고, 그 부분을 해석하는 부분도 AnitmationState에서 처리를 해줘야 해버리니까요. 그래서 완전 추상클래스로 생성해 놓을거에요.


      몇 가지 함수들이 AnimateAble에 추가되었고, 사실 더욱 디테일하게 정의 해놔야 하는 부분이 있지만 당장은 저도 생각이 잘 나지 않아서 일단 넘어 가도록 하려구요 ^^;

  • 최소 데이터 설계

    곰곰히 생각해 보시면 아시겠지만, 큰 거에서 부터 작은거로 내려 가고 있어요. 물론, 작은거 부터 하나하나 생각하고 큰걸 만드는 방법론도 있지만, 이건 제가 편하게 느끼는 방법이니까 이렇게 내려가고 있어요:D

    3D에 대한 자료형을 구축 할 때는, 보통 벡터(vector)를 사용 해요.

    C++ STL에 있는 자료형 vector가 아닌 수학적 의미 에서의 벡터를 말하는 거에요:D


    그리고 계산 식에 대한 부분은 행렬(Matrix)를 사용 하죠. 일전에 수학적인 이론을 포스팅 하면서 살짝 언급하기는 했지만, 계산이 되는 것은 가능하면 통일이 되는 부분이 좋아요. 그래서 벡터의 경우에는 (1x4)의 크기를 가진 데이터, 행렬의 경우에는 (4x4)의 크기를 가진 데이터로 설계 하려고 해요.

    이 부분의 경우는 오히려 간단해요. 수학적 이론을 바탕으로 구현만 하면 되는 부분 이거든요^^(아이씡나~). 그리고 모든 기본 자료형은 double로 이루어 져요.
    왜 그런지는 모르겠지만, float형 자료형을 많이 사용하기도 합니다.


    내부 구현의 경우는 제가 현재 작업하고 있는 네이버의 SVN을 오픈 할 예정이므로, 그전 부분에 대해서는 동작하게끔 구현 하면 될 거 같아요:)
    • VEC4

      벡터는 기본적인 위치, 색깔, 크기 데이터에 사용 해요.
      • 데이터 형

        정확히 4개만 필요하기 때문에 정적배열로 선언 했습니다.

        double mElems[4];
        

      • 함수

        // getter & setter
        double &operator[](const size_t &_idx);
        const double &operator[](const size_t &_idx) const;
        
        // 단위 벡터(크기가 1인 벡터)화 시킨다.
        void Normalization();
        
        // 길이를 구한다.
        // 내부의 값은 변하지 않도록 한다.
        double Length() const;
        
        // 다른 벡터와 내적을 구한다.
        double DotProduct(const VEC4 &_other) const;
        
        // 다른 벡터와 외적을 구한다.
        VEC4 CrossProduct(const VEC4 &_other) const;
        void CrossProduct(const VEC4 &_other, VEC4 &_output) const;
        
        // 위치값 인가 확인한다.
        bool isPosition() const;
        
        // 벡터값 인가 확인한다.
        bool isVector() const;
        
        // 다른 벡터와 이루고 있는 각도를 구한다.
        double AngleWith(const VEC4 &_other) const;
        
        // 다른 벡터와 같은지 확인한다.
        bool operator==(const VEC4 &_other) const;
        
        // 다른 벡터와 다른지 확인한다.
        bool operator!=(const VEC4 &_other) const;
        
        // 벡터를 반대 방향(180도)으로 바꾼다.
        VEC4 &operator-(int dummy);
        
        // 벡터간 합
        VEC4 operator+(const VEC4 &_other) const;
        
        // 벡터간 차
        VEC4 operator-(const VEC4 &_other) const;
        
        // 벡터와 스칼라 곱(벡터 x 스칼라)
        VEC4 operator*(const VEC4 &_other, const double &scalar) const;
        
        // 벡터와 스칼라 곱(스칼라 x 벡터)
        VEC4 operator*(const double &scalar, const VEC4 &_other) const;
        

    • MAT4
      • 데이터 형

        4x4개로 16개가 필요 해요. 2차원으로 만들수도 있고, 1차원으로 만들 수도 있지만 저는 후자를 선택했어요.

        double mElems[16];
        

      • 함수

        사용되는 계산은 곱셈 밖에 없어요. 처음에 언급했다 시피, 계산방식 자체를 통일 시키기 위한 것이죠.
        // getter & setter
        const double &operator()(const size_t &_row, const size_t &_col) const;
        double &operator()(const size_t &_row, const size_t &_col);
        
        // setter 특정 행을 VEC4를 인자로 설정
        void setRow(const size_t &_row, const VEC4 &_vec);
        void setRows(
        	const VEC4 &_row1,
        	const VEC4 &_row2,
        	const VEC4 &_row3,
        	const VEC4 &_row4);
        
        // setter 특정 열을 VEC4를 인자로 설정
        void setCol(const size_t &_col, const VEC4 &_vec);
        void setCols(
        	const VEC4 &_col1,
        	const VEC4 &_col2,
        	const VEC4 &_col3,
        	const VEC4 &_col4);
        
        // getter 특정 열과 행을 VEC4로 가져온다.
        VEC4 Row(const size_t &_row) const;
        VEC4 Col(const size_t &_col) const;
        
        // setter 특정 값으로 모든 값 채우기
        void Fill(const double &_elem);
        
        // setter 대각선만 1의 값을 가진 단위 행렬로 값 초기화
        void Identically();
        
        // 역행렬 구하기
        // 1. 자기자신을 역행렬화 한다.
        // 역행렬을 구할 수 있으면, 자신의 값이 변하면서 true를 반환
        // 역행렬을 구할 수 없으면 false를 반환
        bool Inverse();
        
        // 2. 인자에 구해진 역행렬을 설정한다.
        // 1과 같은 형태의 반환을 한다.
        // 자신의 값이 변하면 안된다.
        bool Inverse(MAT4 &_output) const;
        
        // 행렬*행렬
        MAT4 operator*(const MAT4 &_other);
        
        // 행렬*벡터
        MAT4 operator*(const VEC4 &_vec);
        

    • DIRECTION

      방향을 데이터화 시켰어요. 기본적으로 x, y, z축을 각각 바라보고 있는 단위벡터의 집합이며, 회전 시 모든 축이 같이 회전하여, 서로 수직하는 성질이 변하지 않아요.
      • 데이터 형

        OpenGL의 경우에 우방향계를 쓰기 때문에, x축이 왼쪽이 되는 것만 주의하면 되요.
        VEC4 mForward;
        VEC4 mUp;
        VEC4 mLeft;
        

      • 함수
        // getter
        const VEC4 &getLeft() const;
        const VEC4 &getForword() const;
        const VEC4 &getUp() const;
        
        // setter(값을 직접 변경할 수는 없다.)
        
        // 회전
        // 1. 축과 회전각을 입력해서 직접 계산
        void Rotate(const VEC4 &axis, const double &_angle_degree);
        // 2. 회전 행렬자체로 직접 계산
        void Rotate(const MAT4 &_rotateMat);
        

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

Architecture my OpenGL engine core3  (0) 2012.08.12
Architecture my OpenGL engine core2  (0) 2012.08.10
06 Collaboration Diagram  (0) 2012.07.21
05 Sequence Diagram  (2) 2012.07.14
04 Statechart Diagram  (2) 2012.07.10