본문으로 바로가기

6장 3D Graphics - ② 3D 모델 로딩하기

6장에서는 2D 환경에서 완전한 3D 게임으로 전환하는 방법을 다룬다. 3D 게임으로 전환하기 위해서는 먼저 3D회전을 포함한 엑터의 변환을 다룰 수 있어야 한다. 또한 3D 모델을 로드하고 그릴 수 있어야 한다. 마지막으로 대부분의 3D 게임 장면에서 제공하는 여러 타입의 조명을 구현해야 한다.

💡 6장의 목차

  • 3D에서 물체의 변환과 쿼터니언
  • 3D 모델 로딩하기
  • 3D 메시 그리기
  • 조명

3D 모델 로딩

2D 스프라이트 기반 게임에서는 사각형 하나로 모든 스프라이트를 그릴 수 있지만, 3D 모델은 많은 메쉬를 삼각형들이 사용된다. 그리고 아티스트들은 마야와 같은 3D 모델링 툴을 통해서 삼각형 메쉬를 만들어낸다. 때문에 3D 게임에서는 모델링 툴로 만들어진 3D 모델 메쉬를 게임으로 로딩하는 방법이 필요하다.

모델 포맷 선택

각 모델링 툴은 자신들에 특화된 모델 포맷을 가진다. 하지만 이들을 게임에 그대로 사용하기에는 다음과 같은 문제들이 있다.

  • 불필요하게 많은 정보를 담고 있다.
  • 모델링 파일 포맷의 내용이 불분명한 경우가 있다.
  • 특정 모델링 툴에 종속적이다.

그래서 우리는 익스체인지 포맷(exchange format)을 대안으로 사용할 수 있다. 익스체인지 포맷은 여러 모델링 프로그램에서 동작하는 것을 목표로한다. 대표적으로 FBXCOLLADA가 있다. 하지만 익스체인지 포맷 또한 여전히 불필요하게 많은 정보를 담고 있다. 때문에 유니티나 언리얼 같은 상용엔진에서는 익스체인지 포맷의 임포트를 지원하지만, 임포트 할 때 내부적으로 엔진에 맞는 포맷으로 변경하여서 사용한다.

교재에서는 학습을 위해서 바이너리 파일 포맷이 아닌, 단순화 한 JSON 파일 포맷을 실습에 사용한다. 그리고 이 포맷의 이름을 gpmesh로 정했다. gpmesh는 다음과 같이 구성되어 있다.

  • version: 포맷의 버전 정보
  • vertexformat: 버텍스 버퍼의 포맷 방식
  • shader: 모델을 그리는 데 사용할 셰이더 프로그램 지정
  • textures: (배열) 모델과 관련된 텍스처들의 배열
  • vertices: 버텍스 버퍼 정보 (버텍스 버퍼의 포맷 방식에 맞게 저장되어 있다.)
  • indices: 인덱스 버퍼 정보

당분간은 아래와 같은 형태의 모델 포맷을 활용해서 실습을 진행한다.

Cube.gpmesh

    {
    "version":1,
    "vertexformat":"PosNormTex",    // 위치 / 법선 / 텍스쳐 포맷
    "shader":"BasicMesh",
    "textures":[
        "Assets/Cube.png"       //Cube.png 파일을 텍스처 파일로 사용
    ],
    "specularPower":100.0,
    "vertices":[
        // (x,  y,  z) (x,y,z) (u,v)
        [-0.5,-0.5,-0.5,0,0,-1,0,0],    
        [0.5,-0.5,-0.5,0,0,-1,1,0],
        [-0.5,0.5,-0.5,0,0,-1,0,-1],
        // ... (중략)...
        [-0.5,0.5,-0.5,-1,0,0,0,-1]
    ],
    "indices":[
        [2,1,0],
        [3,9,8],
        [4,11,10],
        // ... (중략) ...
        [27,25,26]
    ]
    }

버텍스 속성 갱신

기존까지의 버텍스는 위치에 대한 float데이터 3개와(x,y,z)와 텍스처에 대한 float데이터 2개(u,v)를 가졌었다. Cube.gpmesh에 나왔는 것 처럼 앞으로는 버텍스에 대한 법선벡터(Normal vector)에 대한 float데이터 3개도 추가로 가져야 한다. 따라서 아래와 같이 VertexArray class에서 버텍스 속성을 추가해주자.

VertexArray.cpp의 VertexArray::VertexArray() 중

// 두 번째 버텍스 속성(속성1)을 활성화
// 속성1: 버텍스는 3차원 법선 벡터(x,y,z)를 가진다.
glEnableVertexAttribArray(1);
glVertexAttribPointer(
    1,          // 버텍스 속성 인덱스
    3,          // 요소의 수 법선벡터 (x,y,z) 2개의 컴포넌트 존재
    GL_FLOAT,   // 요소의 타입
    GL_FALSE,   // GL_FLOAT에서는 사용되지 않음
    sizeof(float) * 8,  // 간격(간격은 항상 버텍스의 크기다.)
    reinterpret_cast<void*> (sizeof(float) * 3) // 오프셋 포인터
    );

다른 속성들도 버텍스 포맷에 맞게 수정한다. 버텍스 속성을 수정한 다음에 새로운 버텍스 레이아웃을 참조하도록 Sprite.vert 셰이더도 수정한다.

Sprite.vert

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;

gpmesh 파일 로딩

실습용 메시 파일을 로딩하기 위해서는 JSON을 파싱을 해야한다. 이 부분을 Mesh클래스로 캡슐화한다. Mesh 클래스의 기능은 gpmesh 포맷을 파싱해서 셰이더와 텍스처, 버텍스 배열을 읽어오는 역할을 한다. 이 부분은 소스코드를 참고하자.

mesh.h 이동 / mesh.cpp 이동