태그 : Nebula3

Nebula3 2009년 4월 버전

공개되었습니다.

http://flohofwoe.blogspot.com/2009/04/n3-sdk-apr-2009-download.html


변경 사항 및 Anatomy는 이후 차차...

by kimsama | 2009/04/24 18:02 | Nebula3 | 트랙백 | 덧글(0)

Anatomy of Nebula3 - Loading Custom Model

개요


Nebula3에서는 현재 .n2의 Nebula2 리소스 파일 형태만을 지원하고 있는데, 이 글에서는 Nebula3를 확장하기 위해서 새로운사용자 정의 리소스 파일(메쉬, 애니메이션, 등을 위한 모델 파일)을 추가하는 방법에 대해서 간단하게 살펴 본다.

StreamModelLoader


StreamReader는 ResourceLoader 클래스의 서브 클래스이며 리소스 파일을 스트리밍을 통해서 읽어 들이는 처리를 한다. 읽어 들일때에는 비동기적인 방법으로 읽어 들인다. 아래 소스에서 보듯이StreamModelLoader::SetupModelFromStream 함수에서 리소스 파일의 확장자에 따라 적절한ModelReader를 생성하고 생성한 ModelReader에서는 리소스 파일을 파싱해서 실제로 읽어 들이는 처리를 한다.


bool
StreamModelLoader::SetupModelFromStream(const Ptr<Stream>& stream)
{
    n_assert(stream.isvalid());
    n_assert(stream->CanBeMapped());

    // first decide what ModelReader to use
    bool isLegacyFile = false;
    String fileExt = this->resource->GetResourceId().Value().GetFileExtension();
    Ptr<ModelReader> modelReader;
    if (fileExt == "n3")
    {
        // Nebula3 binary format
        modelReader = (ModelReader*) BinaryModelReader::Create();
    }
    else if (fileExt == "xml")
    {
        // Nebula3 XML format
        modelReader = (ModelReader*) XmlModelReader::Create();
    }
    else if (fileExt == "n2")
    {
        // legacy Nebula2 binary format
        modelReader = (ModelReader*) N2ModelReader::Create();
        modelReader->SetModelResId(this->resource->GetResourceId());
        isLegacyFile = true;
    }
    else
    {
        n_error("StreamModelLoader: unrecognized file extension '%s' in '%s'!",
            fileExt.AsCharPtr(), this->resource->GetResourceId().Value().AsCharPtr());
        return false;
    }


메쉬 데이터


Direct3D9::D3D9StreamMeshLoader를 상속하는 CoreGraphics::StreamMeshLoader 클래스에서는 메쉬 데이터 메쉬 데이터를 읽어 들여 정점버퍼와 인덱스 버퍼를 생성하는 일을 담당한다.D3D9StreamMeshLoader::SetupResourceFromStream() 에서는 리소스의 리소스 아이디의 파일확장자를 검사해서 해당하는 메쉬 데이터 포맷을 읽어 들이는 함수를 호출한다. 리소스 파일의 확장자가 '.nvx2'인 경우에SetupMeshFromNvx2() 함수를 호출한다.

bool
D3D9StreamMeshLoader::SetupResourceFromStream(const Ptr<Stream>& stream)
{
    n_assert(stream.isvalid());
    n_assert(this->resource.isvalid());
    #if NEBULA3_LEGACY_SUPPORT
    if (this->resource->GetResourceId().Value().GetFileExtension() == "nvx2")
    {
        return this->SetupMeshFromNvx2(stream);
    }
    else
    #endif
    if (this->resource->GetResourceId().Value().GetFileExtension() == "nvx3")
    {
        return this->SetupMeshFromNvx3(stream);
    }
    else if (this->resource->GetResourceId().Value().GetFileExtension() == "n3d3")
    {
        return this->SetupMeshFromN3d3(stream);
    }
    else
    {
       n_error("D3D9StreamMeshLoader::SetupMeshFromStream(): unrecognized fileextension in '%s'\n",this->resource->GetResourceId().Value().AsCharPtr());
        return false;
    }
}


SetupMeshFromNvx2에서는 Legacy::Nvx2StreamReader를 통해서 .nvx2 파일을 읽어 들이고 이것을 정점 버퍼와 인덱스 버퍼에 연결한다.


bool
D3D9StreamMeshLoader::SetupMeshFromNvx2(const Ptr<Stream>& stream)
{
    n_assert(stream.isvalid());    
    Ptr<Legacy::Nvx2StreamReader> nvx2Reader = Legacy::Nvx2StreamReader::Create();
    nvx2Reader->SetStream(stream);
    if (nvx2Reader->Open())
    {
        const Ptr<Mesh>& res = this->resource.downcast<Mesh>();
        n_assert(!res->IsLoaded());
        res->SetVertexBuffer(nvx2Reader->GetVertexBuffer().downcast<CoreGraphics::VertexBuffer>());
        res->SetIndexBuffer(nvx2Reader->GetIndexBuffer().downcast<CoreGraphics::IndexBuffer>());
        res->SetPrimitiveGroups(nvx2Reader->GetPrimitiveGroups());
        nvx2Reader->Close();
        return true;
    }
    return false;
}


애니메이션 데이터


애니메이션 데이터도 메쉬 데이터와 크게 다르지 않다. 차이점이라고 하면 메쉬 데이터는 하드웨어쪽의 그래픽 디바이스드라이버(win32에서는 D3D)와 연결되어야 하지만 애니메이션 데이터는 그렇지 않다는 것과 최근의 애니메이션 처리와 관련한가장 중요한 이슈는 스트리밍이라는 점이다. 다시 말하면 애니메이션 데이터의 처리는 스트리밍에 훨씬 더 민감하다는 것이다.

현재(2009.03) 이 글을 쓰는 시점에서 Nebula3의 최신 버전은 2008년. Sep. 버전(9월 버전)인데 이 버전에서는이전 Nebula2의 애니메이션 데이터 파일인 .nax2 파일 포맷 지원이 생략되어 있다. 이는 이 후 버전에서CoreAnimation 모듈에서 기능이 강화된 애니메이션 모듈을 지원하기 위해서라고 한다. 대신에 2008. Feb. (2월)버전에서는 .nax2를 지원하는 코드를 살펴 볼 수 있다. 애니메이션 데이터의 처리 부분이 궁금하면 2008. Feb. 버전을보기 바란다.

결론


이상에서 살펴 본 클래스와 이들의 멤버 함수를 이해하면 원하는 사용자 정의 파일 포맷을 Nebula3에 쉽게 포팅해서 확장할 수 있다.

한가지 팁을 덧붙이자면 Nebula3에서는 모든 리소스를 읽어 들이는 처리가 기본적으로 비동기 방식으로 처리된다. 개발 단계에서는 이 방식이 디버깅 등에 불편한 경우가 있는데 이 때에는 Resource::ResourceMapper::SetAsyncEnabled() 함수로 비동기 처리를 비활성화할 수 있다.

by kimsama | 2009/03/28 09:37 | Nebula3 | 트랙백 | 덧글(0)

Anatomy of Nebula3 - Terms


Anatomy of Nebula3 - Terms


Nebula3는 Nebula2에 비해서 훨씬 더 많은 클래스들로 구성이 되어 있다. (하지만 실제 코드의 라인수는 훨씬 적다.) 또 새로운 클래스들이 많기 때문에 다소 생소한 부분도 많다. 이들 클래스들 간의 관계를 잘 살펴서 이해하면 전체적인 구조를 쉽게 파악할 수있다. 이 글에서는 이들 클래스들 중에서 비슷한 이름으로 헷갈리기 쉽거나 파악이 모호한 것들의 관계에 대해서 설명한다.


1. Render Layer

여기에서는 Nebula3의 세가지 레이어 중에서 Render Layer에 속하는 클래스들에 대해서 설명한다.

DisplayDevice와 RenderDevice의 구분


디스플레이 디바이스는 윈도우의 창, 렌더 디바이스는 렌더링 드라이버와 관련한 것으로 win32에서는 Direct3D 디바이스를 캡슐화한 것이다. 렌더 디바이스는 자신의 백버퍼의 내요을 디스플레이 디바이스에 출력하는 처리를 한다.

Stage와 View의 구분


stage는 전체 장면을 구성하는 모든 엔티티들을 포함하는 것으로 무대의 의미로 이해할 수 있다. view는 현재 카메라로 컬링된 장면을포함한다. 즉, view는 stage에서 컬링을 통해서 현재 카메라를 통해서 보이는 엔티티들, 다시 이야기하면 렌더링해야 하는객체들을 포함한다.

Model과 Resource의 구분

Model은 Resouce 클래스를 상속하는 클래스로 Resoucre의 하위 개념이다. 즉, 리소스의 하나로 생각할 수 있는데 리소스중에서도 렌더링과 관련한 리소스가 바로 Model이다. Model은 렌더링할 객체를 위한 템플릿으로 사용된다.

namespace Models
{
class Model : public Resources::Resource
{
    ...
};
}


Model과 ModelNode의 구분


Model은 렌더링할 객체를 위한 템플릿으로 파일 등으로 리소스를 읽으면 하나 이상의 ModelNode를 가진다. 파일로부터 리소스를읽어 들였다고 바로 렌더링하는 것이 아니라 Model은 ModelInstance를 생성하고 생성한 ModelInstance를 렌더링에 사용한다.

ModelNode와 같이 -Node의 이름을 가지는 클래스들은 장면 구성을 위한 변환(Transform) 정보를 가지며, 장면 처리를 위해서 계층(Hierarchy)을 구성할 수 있다.

ModelNode과 ModelNodeIntance의 구분


Nebula에서의 Node는 장면(Scene) 트리를 구성하는 트리의 노드를 의미한다. 즉, 렌더링과 관련되는 것이다.

ModelNode - 리소스의 관점, 직접 렌더링하지 않는다. 읽어 들인 Node를 렌더링하기 위해서는 Instance를 생성해야 한다.

읽어 들인 Model을 렌더링하기 위해서는 적어도 하나 이상의 ModelInstance가 필요한데 이 ModelInstance는 Model 클래스 객체가 생성한다.


Ptr<ModelInstance> modelInstance = model->CreateInstance();


ModelNodeInstance는 렌더링 인스턴스로 매 프레임마다 갱신해야 하는 처리는 멤버 함수 Update() 함수 내에서 처리한다.

namespace Model
{
class ModelNodeInstance :  : public Core::RefCounted
{
    ...
    // 매 프레임마다 호출된다.
    virtual void Update();
    ...
};
}



(계속...)

by kimsama | 2009/03/24 16:24 | Nebula3 | 트랙백 | 덧글(0)

Nebula3 Application Layer

이 포스트는 floh님의 블로그에 포스팅된 Nebula3 관련 내용을 번역한 글입니다. Nebula3는 Nebula2의 기본적인 아키텍쳐를 계승하지만 구현에서는 이전과 비교해 보면 여러 면에서 많이 다릅니다. 특히 세 개의 레이어로 구성되어 있는 점이 가장 큰 특징 중의 하나인데 이것은 이전의 망갈로(Mangalore)가 게임 엔진에 포함되었기 때문입니다. 그래서 이 세 개의 레이어에 대한 이해를 위해서 관련된 포스트들만 먼저 번역해서 올립니다.

Nebula3 Application Layer

http://flohofwoe.blogspot.com/2007/11/nebula3s-application-layer-provides.html

애플리케이션 레이어는 애플리케이션 프로그래머에게 표준화된 하이레벨의 게임 프레임워크를 제공한다. 이것은 Nebula2 엔진의 애플리케이션 프레임워크인 망갈로의다음 버전이 Nebula3에 통합된 것으로 수년간의 리팩토링의 결과이다.(one couldsay that this started with our very first big project "Urban Assault"10 years ago) 망갈로의 디자인은 안정적인 상태로 당분간 큰 변화는 없을 듯 하다. 망갈로를 잘 알고 있는 프로그래머면 N3의 애플리케이션 레이어도 쉽게 이해할 수 있을 것이다.

망갈로가 공식적으로 시작된 2003년은 (우리가) 독립적인 개발사는 몇 개의 타이틀을 동시에 여러 퍼블리셔와 작업함으로써 리스크를 분산시키는 것이 매우 중요하다는 것을 배운 해이다.(독일 퍼블리셔들은 불행히도 자주 파산하는 경향이 있기 때문이다.) 그래서망갈로의 중요한 디자인 목표 중 하나는 (사내에서) 다수의 팀이 각기 다른 장르의 게임 개발을 하는 공통적인 게임 애플리케이션 프레임워크를 제공하는 것이다. 그리고 가장 중요한 것은 게임 프레임워크로 작지만 완벽한 게임을 매우 짧은 기간에 마칠 수 있어야 한다는 것이다.(우리의 일반적인 "가장 작은 프로젝트"는 5명의 풀타임 개발자(두 명의 프로그래머와 두 명의그래픽 아티스트 그리고 한명의 레벨 디자이너)로 구성된 3개월짜리 프로젝트이다. 여기에 두 세개의 프로젝트당 프로젝트 매니저 한 명 추가). 간단히 말하면 몇 개의 "작은 프로젝트"를 동시에 개발하는 일은 하나의 큰 프로젝트를 하는것보다 몇배는 어렵다. 왜냐하면 전체 프로젝트 크기를 간단히 줄일 수 없는 게임 프로젝트의 많은 부분이 있기 때문이다. 컨텐츠측면에 있어 우리의 다중 프로젝트 전략을 위해서는 선행 조건이 필요했는데 자체 툴킷으로 엄격히 표준화된 에셋 파이프라인이 바로 그것이다. 왜냐하면 툴킷 때문에 실제 작업에시 오버헤드 없이 프로젝트들 간에  모델러와 애니메이터를 교환할 수 있기 때문이다.(역자 주: 모델링 후 애니메이션이 이루어지기 때문에 모델러와 애니메이터의 작업은 종속 관계를 가진다. 여러 개의 프로젝트 진행시 한 프로젝트의 모델링을 마친 모렐러들은 다른 프로젝트로 투입되는데 이런 작업이 잘 이루어지기 위해서는 툴킷을 이용한 표준화된 (그것도 엄격히!) 아트 에셋 파이프라인이 있기 때문이라는 이야기) (예를 들어 작은 프로젝트라도 몇 주에 모든 그래픽 에셋들을 빌드하거나 캐랙터 애니메이션 등을 직접 프로젝트들 간에 공유한다던가 혹은 인스턴스 캐릭터 애니메이터와 같은 키맨들이 다수의 프로젝트에 참여해서 며칠씩 돕는 것은 일반적으로 힘들다.)

망갈로(이제는 N3 애플리케이션 레이어)는 기본적으로 프로그래밍 도메인에서와 같은 문제점을 해결하기 위한 시도를 한다. 이것은 응용 프로그램 프로그래머가 최소한의 셋업 오버헤드만 가자고 새로운 프로젝트를 시작할 수 있도록 하거나 이 프로젝트의 게임플레이 구현을 주어진 시간과 예산에 맞추어 구현할 수 있도록 돕는 일과 같은 일이다. 다음 항목들은 망갈로의 중요한 특징들이다.
  • 프로젝트 첫 날부터 스크린상에 무엇인가 보일 수 있도록 완전하면서 포괄적인 기능을 제공한다.
  • 우리의 표준화된 레벨 디자인 및 모델링 작업 플로어와 통합 시킨다. 그리고 여기에다 공통적인 마인드셋, 숙어 - "사회적 인터페이스"를 프로그래머와 모델러/애니메이터 그리고 레벨 디자이너에게 제공한다.
  • 응용 프로그램에서 게임에 특화된 코드를 어덯게 작성해야 하는지에 대한 컨셉을 명확하게 정의한다.(이 때문에 망갈로 프로그래머는  새로운 게임 특징 구현을 하다 "어디에" 그리고 "어떻게" 라는 질문에 마주쳤을 때 상대적으로 결정을 쉽게 할 수 있다.)
  • 게임 로직 작업을 하는 팀의 프로그래머들이 서로의 작업을 방해하지 않도록 충분히 모듈화 되어 있다.
  • "NewGame","Continue Game", "Load Game" 그리고 "Save Game" 은 표준화된 특징이다. (중요한 점:Load/Save 기능은 시작부터 바로 작동하며 대부분은 프로그래머가 load/save 관련 코드를 한라인도 작성할 필요가없다.)
  • 차후 프로젝트에서는 (이전보다) 성장하여 재사용이 가능한 모듈 집합을 제공한다. (Mangalore와 비교할 때 N3Application Layer 에서는  "GameFeatures"라고 불리는 것으로 이 부분을 더욱 정형화 시켰다.)
애플리케이션 레이어는 다음의 기본적인 컨셉들이 구현되어 있다.
  • Entity: 게임 객체, Propertie와 Attribute들을 포함한다, Message를 사용하여 조작된다.
  • Property: 게임 로직의 개별 엔티티 관점에서의 처리.
  • Manager: 게임 로직의 전역적인 관점에서의 처리.
  • Attribute: key/value 쌍, 엔티티의 영속성을 구현.
  • Message: 엔티티간의 통신에 사용.
  • GameFeature: Properties, Managers, Messages 와 Attributes를 기능별로 그룹화 한 것.
Entity는적 우주선이나 플레이어 아바타 혹은 보물 상자와 같은 하나의 게임 객체를 의미한다. An important aspectof the Application Layer에서 중요한 것은 기능 추가를 위해서 Game::Entity 클래스를 서브 클래싱하는것이 아니라 Attributes와 Properties가 엔티티의 실제 게임 로직을 정의하도록 하는 것이다. 일찍이 복잡한 게임프로젝트에서 클래스 상속 계층을 통해서 게임 객체들을 모델링하게 되면  스파게티 상속, 상위 클래스의 비대화와 중복된 코드와같은 모든 타입의 문제점들이 야기되는 것이 뻔하다는 것을 배웠다. (let's say you do a traditional realtime strategy gamewith vehicle units, so you create a Vehicle class, and derive a GroundVehicle and an AirVehicle class, which can implement navigationon the ground and in the air respectively, but now you want to addcombat functionality, hmm... create a CombatGroundVehicle class, and a CombatAirVehicle class? Or would it be better to create a CombatVehicleclass and derive GroundCombatVehicle and AirCombatVehicle? But wait,some of the vehicles should gather resources instead of fighting... you get the general idea, I think every game programmer faces this problemvery early in his career).

이것이 Properties가 등장한 배경이다. 프로퍼티는 엔티티 객체에 연결되어 엔티티에 특정한 기능을 추가하는 작은 C++ 객체이다. 예를 들어 특정한 엔티티 타입이 렌더링 되어야 한다면 GraphicsProperty 가 연결되고, 게임 세계에서 튀고 굴러야 한다면 PhysicsProperty가 필요할 것이다. 만약 엔티티가 카메라를 조작해야 한다면 CameraProperty가 추가되는 식이다. 혹은 앞에서의 전략 게임이라면 다른 유닛 타입의 기능을 만들기 위해서 (엔티티와) 결합하는 GroundMovementProperty, AirMovementProperty, CombatProperty 그리고 GatheringProperty가필요할 것이다. 이상적으로는 Ideally, Properties는 상호간에 자율적이며 독립적이므로 제약없이 결합하는 것이가능하다. (실제 프로젝트에서는 어떤 Properties는 항상 다른 Properties에 의존하는 경우도 있다. 그러나 경험에비추어 볼 때 이러한 제약은 용인할만한 수준이다.) Properties는 위에서 언급한 상속의 문제를 잘 해결한다. 새로운엔티티는 Game::Entity 클래스를 상속해서 구현하는 것이 아니라 다수의 전문화된 프로퍼티들을 결합하여 정의한다.

해결해야 할 다음 문제는 인터페이스와 커뮤니케이션 문제이다. Properties는 같은 엔티티에 연결된 프로퍼티들 간의 통신이나다른 엔티티에 연결된 프로퍼티간의 통신을 위한 커뮤니케이션을 필요로 한다. 다른 프로퍼티의 C++ 함수를 호출하는 것은 프로퍼티클래스 간의 피해야 하는 수 많은 의존 관계를 야기 시킨다. 왜냐하면 이것은 곧바로 프로퍼티를 이용해서 해결한 엔티티 클래스의 상속 문제가 야기하는 것과 같은 제약의 결과로 이어지기 때문이다. 가상함수는 몇 개의 상위 클래스에 의존성을 국한시키기 때문에 이 문제 해결에 도움이 될 것 같기도 하다. 그러나 이것은 상속의 문제를 엔티티에서 프로퍼티로 옮길 뿐이다. Messages는 이러한 딜레마를 해결하기 위해서 사용된다. 메시지를 가상 함수호출의 다음 추상화 레벨로 생각해 보자. 가상함수 호출은 호출하는 쪽에서 타겟 객체와 베이스 C++ 인터페이스를 알아야 하지만 메시지는 단지 호출하는 쪽에서 엔티티 형태의 타겟 객체만 알면 된다. 특정 인터페이스는 몰라도 된다. 엔티티는 관심있는 Properties에게 Message를 경유시키고 Proeperties에서 궁극적으로 이 메시지를 처리하게 된다.  호출하는 쪽에서는 일반적으로 어느 Property가 Message를 처리하는지에 대해서 알지 못한다. 또 메시지가 처리 되었는지에 대해서도 알 수가 없다.

C#프로그래머라면 이제 손을 들고 이것들이 바로 Interface와 Delegate 그리고 Event의 용도라고 말할지도 모르겠다. 전적으로 맞는 말이다. 그런데 안타깝게도 우리는 C++로 일하고 있다. Application Layer는 추후에 더 최적화할만한 여지가 있다. 예를 들어 메시지 발송은 위임자 같은 메카니즘으로 최적화할 수도 있을 것이다.

Attributes는 엔티티에 부속된 키(key)와 값(value)의 쌍이다. 각 엔티티는 엔티티의 완전한 영속성을 정의하는 잘 정의된 속성집합을 가진다. 속성은 엔티티의 초기 상태를 설명하고 애플리케이션 레이어의 표준 로딩/저장 메카니즘을 사용해서 엔티티의 상태를저장하고 로딩하는데 사용된다. 대개 특정 프로퍼티는 엔티티 속성의 부분 집합에 사용된다. 일반적인 규칙은 특정한 속성에 대해서모든 프로퍼티에서 이 속성을 읽을 수는 있지만 조작은 하나의 프로퍼티에서만 해야 한다.

게임 애플리케이션의 많은 특징 중에서 어떤 것들은 엔티티 내부에서 처리하는 것 보다 (애플리케이션의) 어느 중심부에서 처리하는 것이 더 나은 것이있다.(우리의 초기 프로젝트들의 총체적인 코드 디자인은 모든 것이 엔티티가 아니라는 것을 이해하지 못해서 많은 고생을 했다. 예를 들어 사용자 입력을 다루거나 카메라를 조작하는 것은 대부분의 경우 하나의 중심부에서 처리하는 것이 여러 개의 프로퍼티에서 분산해서 처리하는 것보다 낫다. 여러 개의 프로퍼티에 분산되어 있는 경우 다른 프로퍼티와의 동기화와 커뮤니케이션에 많은 노력이 필요할지도 모르기 때문이다. 이러한 게임 로직의 전역적 관점이 Manager 클래스의 서브 클래스들이 놓여지는 곳이다. Manager 클래스는 전형적인 싱글레톤 객체로 보통 게임 코드 내 어디에서든 사용이 가능해야 할 필요가 있는 서비스를 제공한다.

마지막으로 GameFeature는 새로운 컨셉으로 Mangalore에는 존재하지 않았던것이다. GameFeatures라는 것은 단지 서로 관련있는 Properties, Managers, Messages 그리고Attributes (이들 모두 임의 선택 가능)로 구성된 소스 파일 묶음을 정적 라이브러리로 컴파일한 것에 대한 재치있는이름일 뿐이다.  GameFeatures의 대부분은 프로젝트간에 코드 재사용을 촉진하고 표준화하기 위한 기반들이다. 프로젝트시작시 리더 프로그래머는 여러 GameFeature 중에서 프로젝트에 적합한  것들을 가려내고 선택한다. 그리고(이상적인경우로) 새로운 기능들은 나중에 진행할수도 있는 프로젝트를 위해서 새로운 GameFeature에다 그룹짓는다.Application Layer에는 조작 가능한 아바타와 추적 카메라 그리고 주위 환경에 대한 외적 물리 작용이 있는 일반적인3D 응용프로그램 정도는 충분히 구현할 수 있는 몇 개의 표준 GameFeature가 같이 따라온다. 그러나 대화 시스템이나 일반적인 인벤트리 시스템, 혹은 이를테면 전형적인 본격 말타기 게임과 같은 훨씬 더 복잡한 것들을 만들 작정이다. ;)

ApplicationLayer는 물론 완벽하지도 또 모든 문제를 해결하는 것도 아니다. 예를 들어 기능들의 어떤 일부들은 Property로 가야할지 아니면 Manager로 가야 할지를 결정하기가 어려울 수도 있다. 또 우리가 자주 대면하는 전형적인 문제는 기능이 너무 작고 많은부분으로 분리되는 경우로 이것은 수 많은 그리고 너무 세분화되어 매우 잦은 커뮤니케이션을 요구하는 Property들이 만들어지는 결과를 낳는다.  이런 이유로 프로젝트에는 여전히 프로젝트 시작시 코드의 기본 기반을 정할 실용적인 리더 프로그래머가 필요하다.


by kimsama | 2009/02/05 10:57 | Nebula Device | 트랙백 | 핑백(1) | 덧글(0)

Nebula3 Graphics subsystem

이 포스트는 floh님의블로그에 포스팅된 Nebula3 관련 내용을 번역한 글입니다. Nebula3는 Nebula2의 기본적인 아키텍쳐를 계승하지만구현에서는 이전과 비교해 보면 여러 면에서 많이 다릅니다. 특히 세개의 레이어로 구성되어 있는 점이 가장 큰 특징 중의 하나인데이것은 이전의 망갈로(Mangalore)가 게임 엔진에 포함되었기 때문입니다. 그래서 이 세 개의 레이어에 대한 이해를 위해서 관련된 포스트들만 먼저 번역해서 올립니다.

Nebula3 Graphics subsystem

http://flohofwoe.blogspot.com/2007/08/nebula3-render-layer-graphics.html


그래픽스(Graphics) 서브시스템은 렌더링 시스템에서 최상위 그래픽 관련 서브시스템으로 이전 망갈로의 그래픽스 시스템의 다음 버전에 해당한다. 이전 버전과 비교하면 새로운 버전은 Nebula 엔진에 통합되어 있으며 저수준(low level)의 렌더링 코드와 더욱 밀접하게 연결되어 있다.

기본적인 아이디어는 바깥 세계와 최소한의 커뮤니케이션만 하면서 자동화된 모델 엔티티, 광원 엔티티 그리고 카메라 엔티티를 포함하는 그래픽스 세계(Graphics Worlds)를 만드는 것이다. 그래픽스 세계의 주된 처리는 엔티티를 그 세계에 추가하거나 삭제하는 일과 이들의 위치 정보를 갱신하는 일이다.

Nebula3에서는 Mangalore의 그래픽스 서브시스템과 Nebula의 장면(Scene) 서브시스템 사이의 경계를 없앤 덕분에 훨씬 더 적은 양의 코드와 줄어든 커뮤니케이션 오버헤드로 동일한 컨셉의 구현이 가능해졌다.

그래픽스 서브시스템은 멀티 쓰레드를 이용한 비동기 렌더링을 지원하기 위해서 그래픽스와 모든 저수준의 렌더링 서브시스템은 별도의 팻-쓰레드로 구동된다. Nebula3 레이어 모델에서 꽤 고수준의 위치이지만 내가 이 위치를 선택한 이유는 게임 플레이와 관련된 코드와 그래픽 관련 코드 간에 가장 적은 회수의 커뮤니케이션 요구가 발생하는 곳이기 때문이다. 비록 실용적인지에 대한 여부는 실제 경험을 통해 결정해야 하는 문제이긴 하지만 그래픽 코드의 "built-in autonomy"(역주: ??  ^^;)와 함께 완전히 다른 프레임 레이트로 게임 코드를 실행하는 것이 가능해야 한다. 이것은 명확히 시도해 봐야 할 것 중의 하나이다. 왜냐하면 게임 플레이 코드를 초당 10 프레임 이상 실행할 이유가 없기 때문이다.(버츄어 파이터의 팬들은 동의하지 않겠지만 말이다)

중요한 그래픽스 서브시스템의 공용 클래스는 다음과 같다.
  • ModelEntity
  • CameraEntity
  • LightEntity
  • Stage
  • View

ModelEntity는 위치, 바운딩 볼륨, 모델 리소스가 포함된 렌더링되는 엔티티이다. 여기서 모델 리소스란 지오메트리, 재질(material), 애니메이션, 변환 계층(Transform Heirarchy)를 가지는 완전한 3차원 모델을 의미한다.

CameraEntity는 그래픽스 세계의 뷰-볼륨을 나타내는 것으로 렌더링을 위한 뷰, 프로젝션 행렬을 제공한다.

LightEntity는 동적인 광원을 나타낸다.

Stage와 View는 Nebula3에서 처음 등장한 새로운 컨셉이다. Mangalore에서 그래픽 엔티티들은 Level이라는 클래스에 포함이 되어 있었고 한번에 하나의 활성화된 카메라 엔티티와 레벨만이 존재할 수 있었다.

Nebula3에서는 StageView를 사용하여 그 문제에 대한 훨씬 더 명확한 답을 제시하고 있다. Stage는 그래픽 엔티티를 위한 컨테이너로 작은 그래픽스 세계를 표현한다. 동시에 여러 개의 Stage가 존재할 수 있지만 이들은 분리되어 있다. 엔티티는 한번에 하나의 Stage에만 연결된다.  엔티티를 그래픽스 세계로 그룹 처리하는 것 외에 Stage의 중요한 일은 엔티티들 간의 공간 관련도를 이용해서 엔티티들을 구성함으로써 이들에 대한 가시성 질의를 빠르게 처리 할 수 있도록 하는 일이다. (그리고 필요에따라)애플리케이션에서는 Stage 클래스를 상속하여 완전히 다른 가시성 질의 정책을 구현할 수도 있다.

뷰 객체는 카메라 엔티티를 통해서 뷰를 스테이지로 렌더링 한다.(역자 주: CameraEntity가 잡은 뷰는 RenderTarget에 그려지고 이것이 Stage로 보내진다는 의미) 임의의 스테이지는 여러 개의 뷰를 가질 수 있고 또 뷰가 다른 뷰에 연결되어 있을 수도 있다. 이것은 임의의 뷰를 갱신하기 위해서는 다른 연결된 뷰를 먼저 강제로 갱신해야 (this is handy when oneView's rendering process requires the content of another View's rendertarget as a texture)/ 뷰 객체는 자신의 렌더링 루프를 사용하여 처리된다. 애플리케이션에서는 View를 서브클래싱해서 원하는 대로 렌더링 처리를 하는 것이 가능하다. 예를 들어 광원당 하나의 패스만 가지거나 아니면 패스당 여러 개의 광원(multiple lights per pass)를 가지던가 혹은 큐브 맵에 렌더링(render to cubemap)하는 등의다양한 렌더링 처리가 가능하다.

요약하면 Stage는 가시성 질의에 대한 처리를 담당하고 View는 렌더링 프로세스를 처리한다.

그래픽스 서브시스템의 주된 처리 중 하나는 엔티티들 간의 가시성 판단을 통해서 이들의 실제 렌더링 여부를 판단하는 일이다. 가시성 질의를 통해서 엔티티들 간의 상호 가시성 연결(visibility link)를 확인할 수 있다. 가시성 연결은 두 가지가 있는데 바로 카메라 링크와 광원 링크이다. 카메라 링크는 자신의 뷰 볼륨내 모델에 (카메라를) 연결시킨다. 가시성 연결은 양방향이기 때문에 카메라는 자신의 뷰 볼륨 내의 모든 모델들을 알고 있으며 모델들 역시 보이는 모든 카메라를 알고 있다. 광원 연결도 카메라 연결과 같이 모델과 광원 사이의 연결을 확인한다. 광원은 자신이 영향을 미치는 모든 모델들을 알고 있으며 모델 역시자신에게 영향을 주는 모든 광원을 알고 있다.

가시성 질의의 속도를 높이기 위한 가장 중요한 클래스는 내부의 Cell 클래스로 이것은 엔티티와 자식 셀을 위한 컨테이너의 역할을 한다. Cell은 반드시 다음의 두 가지 간단한 규칙을 준수해야 한다.
  • 임의의 셀이 완전히 보이는 경우, 해당 셀의 엔티티들과 셀의 자식 셀도 모두 보인다.
  • 임의의 셀이 전혀 보이지 않는 경우, 해당 셀의 엔티티들과 셀의 자식 셀도 모두 렌더링하지 않는다.
Cell은 Stage에 부착되며 최상위 Cell로부터 시작하여 계층을 형성한다. 기본 Cell 클래스는 쿼드 트리나 옥트리와 같은 공간분할 방법을 가지는데 사용자가 포탈(portal)과 같은 다른 공간 분할 알고리즘을 사용하고자 하는 경우에는 Cell을 상속한 서브클래스를 작성해야 한다. 이 서브클래스의 가시성 판단과 관련한 제한사항은 위에서 언급한 두가지 규칙을 준수하는 것이다.

그래픽스 엔티티가 Stage에 부착될 때 이 엔티티를 수락하는(일반적으로 완전히 포함하는) 가장 낮은 레벨의 Cell에 부착된다. 그래픽스 엔티티의 변환이나 바운딩 볼륨을 갱신을 할 때 필요한 경우 Cell에서의 자신의 위치를 변경한다.

Stage는 StageBuilder 클래스에 의해서 생성된다. 애플리케이션에서는 Stage의 초기 상태를 생성할 수 있도록StageBuilder를 상속하는 클래스를 작성해야 한다. Cell과 엔티티들은 Stage의 초기 상태를 생성할 때 추가되게된다.  Nebula3는 대부분의 응용 프로그램에 적합한 표준 StageBuilder 클래스들을 제공한다.(SimpleStageBuilder와 QuadtreeStageBuilder)

이상으로 간단하게 그래픽스 서브시스템에 대해서 알아 보았다. 현재로서는 아주 간단한 구현만 된 상태이기 때문에 다음 몇 주간에 걸쳐 많은 변화가 있을 수도 있다.

by kimsama | 2009/02/05 10:56 | Nebula Device | 트랙백 | 핑백(1) | 덧글(1)

Nebula3 Resource subsystem

이 포스트는 floh님의블로그에 포스팅된 Nebula3 관련 내용을번역한 글입니다. Nebula3는 Nebula2의 기본적인 아키텍쳐를 계승하지만구현에서는 이전과 비교해 보면 여러 면에서 많이 다릅니다. 특히 세개의 레이어로 구성되어 있는 점이 가장 큰 특징 중의 하나인데이것은 이전의 망갈로(Mangalore)가 게임엔진에 포함되었기 때문입니다. 그래서 이 세 개의 레이어에 대한 이해를 위해서 관련된 포스트들만 먼저 번역해서 올립니다.

Nebula3 Resource subsystem

http://flohofwoe.blogspot.com/2007/05/nebula3-resource-subsystem.html


Nebula3의 Resource 서브시스템은 Nebula2와 비교할 때 더욱 열려 있으며 리소스의 생성과 관리에 대해서는 프로그래머에게 더 많은 권한을 위임하고 있다.

Nebula3의 리소스는 다음과 같은 속성을 가지고 있다.
  • 다른 Nebula 서브시스템에서 필요로 하는 데이터를 랩(wrap)한다.
  • ResourceId로 공유하는 것이 가능하다.
  • 아무때나 초기화하거나 로딩, 언로딩하는 것이 가능해졌다.
  • 동기 및 비동기 로딩이 가능하다.
Resource 서브시스템에서는 메쉬나 텍스쳐와 같은 전형적인 그래픽 리소스뿐만 아니라 다른 여러 타입의 리소스들도 다룬다.

저수준에서는 실제 리소스 객체와 리소스 공유, 로딩, 그리고 다소 중요도는 떨어지지만 저장과 같은 처리를 제공한다. 저수준의 리소스 클래스는 다음과 같다.
  • ResourceId
  • Resource
  • ResourceLoader
  • ResourceSaver
  • SharedResourceServer
고수준의 Resource 서브시스템은 리소스 사용자로부터의 피드백에 따라 동적으로 리소스를 로딩하거나 언로딩하는 리소스 관리 기능을 제공한다. 고수준의 Resource 서브시스템 클래스는 다음과 같다.
  • ResourceProxy (대체 클래스 이름: ManagedResource)
  • ResourceProxyServer (대체 클래스 이름: ResourceManager)
  • ResourceMapper
다음은 Resource 서브시스템들이 어떻게 작동하는지에 대한 설명이다.

ResourceId는 리소스에 대한 유일한 식별자이다. 리소스 아이디는 공유와 디스크 상에서의 리소스 데이터의 위치나 데이터 저장에 사용된다. ResourceId는 문자열 atom이다. Atom은 문자열 상수(혹은 다른 복잡한 데이터타입)에 대한 유니크한 32비트 식별자로 잦은 복사나 비교시 빠르게 처리할 수 있으며 동일한 문자열은 한번만 저장되기 때문에 메모리 풋프린트도 감소한다. 리소스 데이터를 디스크에 저장하기 위해서는 ResourceId는 일반적으로 유효한 URL로 바뀐다.예를 들어 "texture:materials/granite.dds"와 같은 리소스 아이디는 실행시"file:///C:/Programme/[AppName]/export/textures/materials/granite.dds"로 바뀐다.

Resource 객체는 리소스 데이터를 위한 실제 컨테이너이다. 텍스쳐나 메쉬와 같은 특정 리소스 타입은 특별한 클래스 인터페이스를 가지는 Resource 클래스의 서브 클래스이다. Resource 서브 클래스는 D3D9Texture와 같이 종종 플랫폼이 종속적이기도 하지만 상황에 따라서는 Texture 클래스와 같이 플랫폼에 독립적이인 인터페이스를 가지기도 한다. Nebula2와는 다르게, 리소스 객체는 자신을 어떻게 설정하고 또 로딩하거나 저장하는지에 대해서 알지 못한다. 대신에 적절한 ResourceLoaderResourceSaver 객체가 해당 Resource 객체에 부착되어야 한다. Nebula 응용 프로그램은 데이터를 쓰는 일이 드물기 때문에ResourceSavers는 완전함을 위해 존재한다.(???) 다르게 이야기하면 ResourceLoaders가 중요한데 왜냐하면사용할 Resource 객체를 설정하는 유일한 방법이기 때문이다. ResourceLoaders 클래스는 리소스 설정 프로세스에대한 완전한 컨트롤 권한을 가진다. 이 클래스는 플랫폼에 한정될 수도 있고 관련한 플랫폼 Resource 클래스에 의존적일 수도있다.  Nebula2와 비교하면 리소스의 설정에 대해서 더욱 많은 권한을 프로그래머에게 위임되었다. 예제 리소스 로더클래스들은StreamTextureLoader, StreamMeshLoader (스트림을 통해 텍스쳐와 메쉬를 설정한다.),MemoryVertexBufferLoader 그리고 MemoryIndexBufferLoader (메모리에서 정점 버퍼와 인덱스버퍼를 데이터로부터 설정한다.)이다.

Resource 클래스는 또한 동기 및 비동기 로딩에 대해서 공통된 인터페이스를 제공한다. 동기화된 로딩은 다음과 같다.
  1. res-> SetResourceId("tex:system/white.dds");
  2. res-> SetLoader(StreamTextureLoader::Create());
  3. res-> SetAsyncEnabled(false)
  4. res-> Load()
  5. if (res-> IsValid()) ... 리소스 로딩에 성공했다. 성공하지 못한 경우 LoadFailed()는 참을 리턴하게 된다.
비동기 로딩도 위와 비슷하다.
  1. res->SetResourceId("tex:system/white.dds");
  2. res->SetLoader(StreamTextureLoader::Create());
  3. res->SetAsyncEnabled(true);
  4. res->Load();
  5. 리소스는 이제 로딩 상태가 된다.
  6. aslong as IsPending() 함수가 참을 반환하는한 반복해서 Load()를 호출한다. 물론 실제 애플리케이션에서는 그 동안 의미 있는 작업을 하겠지만...
  7. Load() 함수를 호출한 후 어느 시점에서 리소스의 상태는 Valid(리소스가 사용 가능하도록 준비된 상태)나 Failed(리소스 로딩에 실패한 경우) 혹은 Cancelled(로딩 중 취소한 경우) 중 하나가 된다.
An application or even the Nebula3 render code usually doesn't have todeal with this, since the resource management layer will take care ofthis and hide the details of asynchronous resource loading behindresource proxies.

The SharedResourceServer싱글레톤 클래스는 ResourceId를 사용한 리소스의 공유 기능을 제공한다. SharedResourceServer를 통해서리소스를 생성하면 (이 리소스를 사용하는) 클라이언트의 회수에 상관없이 이 리소스는 한번만 로딩되어 메모리에 위치한다는 것이보장된다. 클라이언트의 회수가 0이 되면 해당 리소스는 자동으로 언로딩된다. (this is no substitute forproper resourcemanagement however, that's what the ResourceProxyServer cares about).리소스 공유는 Nebula3의 표준 객체 생성 메카니즘을 사용하여 직접 리소스 객체를 생성하는 방법으로 우회할 수도 있다.

A ResourceProxy (orManagedResource)클래스는 실제 리소스 객체에 대한 리소스 관리 래퍼 클래스이다. 이 아이디어가 필요한 이유는 포함된 리소스 객체가 리소스 사용피드백에 따른 리소스 매니저의 컨트롤에 의해서 변경될 수도 있기 때문이다. 예를 들어 텍스쳐 대리자는 요구한 텍스쳐가 백그라운드로딩되는 동안 placeholder 텍스쳐를 제공하거나 해당 리소스를 사용하는 모든 객체가 화면상에서 매우 작은 경우 저해상도의텍스쳐를 제공하기도 한다. 그리고 만약에 어떤 텍스쳐가 x 프레임동안 렌더링되지 않은 경우 그 텍스쳐를 언로드하는 등의 처리도텍스쳐 대리자에서 할수 있다. (역주: Gears of war 2와 같이 고해상도 텍스쳐를 사용하는 게임들은 존이 변경되거나 할 때 점진적으로 백그라운드 로딩을통해 텍스쳐를 읽어 들일 때 저해상도의 텍스쳐에서 고해상도로 부드럽게 변경하면서 읽어 들이는 것을 볼 수 있는데 이러한 처리를ResourceProxy 클래스에서 한다는 이야기)

The ResourceProxyServer (orResourceManager) 싱글레톤 클래스는 리소스 관리 시스템의 프론트엔드이다. ResourceProxy 클래스들의 팩토리 클래스이자 ResourceMappers 클래스와 리소스 타입을 결합시킨다. 다른 한편으로는 연결된  ResourceMappers에 대해서 기본적으로 모든 일을 관리한다.

The ResourceMapper클래스는 흥미를 유발하는 클래스이다. ResourceMapper는 텍스쳐나 메쉬와 같은 하나의 리소스 타입과 연관이 있는데애플리케이션은 이 ResourceMapper를 ResourceProxyServer에 연결한다.  A ResourceMapper는렌더링 코드의 리소스 사용에 대한 피드백을 근거로 리소스의 로딩 및 언로딩을 이행한다.  Subclasses ofResourceMapper 클래스의 서브 클래싱을 통해서 다른 리소스 관리 전략을 구현할 수도 있다. 그리고 특정ResourceMapper와 필요하다면 ResourceLoader의 서브 클래싱으로 완전히 커스터마이징하거나 플랫폼이나 응용프로그램에 한정된 리소스 관리 시나리오를 만드는 것도 가능하다. 이렇게 리소스 관리를 하는 것은 Nebula3의 리소스 관리목표가 필요한 것은 (미리) 모두 로딩하는 간단한 용도에서 큰 세계의 스트리밍 시나리오까지 모두 처리할 수 있는 뛰어난ResourceMappers 세트를 제공하는 것이기 때문이다.

렌더링 코드에서는 ResourceProxy 객체에 리소스 사용 피드백을 작성하는데 여기에는 리소스가 가까운 미래에도 필요한지 혹은 완전히 보이는지, 리소스를 사용하는 객체의 화면 공간의 크기에 대한 어림짐작(역주: 아마도 occlusion 계산을 위한 듯) 등의 내용을 포함해야 한다. The resource usage feedback is written by the rendering code toResourceProxy objects and should include stuff like whether theresource may be needed in the near future, whether the resource wasvisible at all, and a guesstimate of the screen space size of theobject using the resource. 그러나 ResourceProxy 서브 클래스에 의존하는 특정 피드백의 경우,  ResourceProxy에 공통된 피드백 메소드가 존재하지 않는다.(???)

리소스 사용 피드백을 기준으로 ResourceMapper 다음의 작업을 실행할 수도 있다. (실제 작업 실행의 여부는 실제 매퍼에 전적으로 달려 있다)
  • Load: 특정한 LOD(level-of-detail )에서의 비동기 로딩 (예를 들면 필요하지 않는 경우 고해상도의 밉맵을 건너뛰는 것과 같은 것)하고 로딩중인 리소스에 대한 placeholder를 제공한다.
  • Unload: 리소스를 완전히 언로드 시키고 메모리를 해제한다.
  • Upgrade: 이미 로드한 리소스의 LOD 레벨을 증가시킨다.(예를 들어 텍스쳐의 고해상도의 밉맵 레벨을 로딩하는 것)
  • Degrade: 로드한 리소스의 LOD 레벨을 감소시킨다. (예를 들어 텍스쳐의 고해상도 밉맵 레벨을 떨어뜨리는 일)

여기까지다. 아주 간단한 리소스 관리(비동기로 필요한 리소스를 로딩하지만 스크린 크기나 자동으로 리소를 언로딩하는 것은 신경 쓰지 않는다.)를 포함하는 첫번째 코드를 릴리즈할 계획이다.

by kimsama | 2009/02/05 10:54 | Nebula Device | 트랙백 | 핑백(1) | 덧글(0)

◀ 이전 페이지다음 페이지 ▶