본문 바로가기

프로그래밍/MaxSDK

[MAXSDK] 기하 파이프라인 시스템

원문 : max8 sdk 도움말. Geometry Pipeline System

기하 파이프라인 시스템

개관

기하 파이프라인 시스템을 이해하는 것은 씬 내의 기하도형을 다루는 플러그인을 제작하는 개발자에게 있어 중요하다. 이 섹션은 파이프라인 시스템에 대해 설명한다. 파이프라인은 노드가 수정자 응용프로그램을 통해 씬 내의 노드가 반복적으로 수정될 수 있도록 허용해주는 맥스에 의해 사용되는 시스템이다.

파이프라인의 시작점은 Base Object 이다. 이것은 절차적 오브젝트이거나 단순한 메시이다. 파이프라인의 마지막에는 오브젝트의 월드 공간 상태가 들어 간다. 이 월드 공간 상태는 3D 뷰포트에서 그것이 어떻게 보이고 렌더링되는지를 정의한다.

시스템을 위해 파이프라인의 끝에서 오브젝트의 상태를 평가하기 위해서 그것은 시작부터 끝까지 전체에 걸쳐 각 수정(modification)을 적용해야만 한다. 예를 들어 사용자가 절차적 실린더를 씬에 생성하고, 그것에 Bend 수정자를 적용한 다음에, Taper 수정자를 적용했다고 하자. 시스템이 이 파이프라인을 평가할 때, 그것은 실린더 오브젝트의 상태를 가지고 시작할 것이다. 이 오브젝트 상태가 파이프라인을 따라 가는 동안, 그것은 Bend 수정자와 만나게 된다. 시스템은 Bend 수정자에게 오브젝트 상태에 대한 그것의 변형(deformation)을 적용할 것을 요청한다. 이 연산의 결과는 Taper 수정자에 원본(source)로서 넘겨진다. Taper는 자신의 변형을 적용한다. 이 연산의 결과는 시스템에 넘겨지며, 그 결과는 월드 공간에 배치되고 씬 내의 노드 상태가 완결된다.

시스템이 노드의 상태를 평가하는 속도를 최대화하기 위해서, 시스템은 씬 내의 각 노드를 위한 World Space Cache 를 유지한다. 이 월드 공간 캐시는 노드 파이프라인의 결과이다. 그것은 모든 것이 적용된 이후에 월드 공간의 상태를 반영한다. 캐시와 함께 시스템은 유효 인터벌을 유지한다. 이 유효 인터벌은 캐시가 노드의 상태를 정확하게 반영하는 시간 간격을 표현한다.

특정 시간에 자신을 디스플레이하는 것 처럼 노드가 연산을 수행할 필요가 있을 때마다, 시스템은 그 시간에 캐시가 유효한지를 판단하기 위해 유효 인터벌을 검사한다. 만약 유효하다면 기하도형을 위한 개별 채널이 존재한다(예를 들어 오브젝트의 정점). 만약 수정자가 정점들에만 영향을 미친다면, 시스템은 면 구조, UV 좌표, 재질 할당 등을 포함한 전체 오브젝트의 상태를 재평가해야만 하는 것보다 매우 적은 작업을 수행하게 된다. 예를 들어 Bend 수정자는 단지 기하도형 채널에만 영향을 미친다; 다른 모든 채널들은 평가를 요구하지 않는다.

기하도형(정점들), topology(면이나 폴리곤 구조), 텍스처 정점들(UV 좌표), 하위 오브젝트 선택, 선택 수준(level of selection), 디스플레이 컨트롤을 위한 개별 채널이 존재한다. 이 개별 채널들은 3ds 맥스의 캐시 시스템이 좀 더 세련될 수 있도록 해 준다. 오브젝트를 위한 하나의 전역 상태를 캐싱하는 대신에 그것은 채널에 기반해서 그것의 개별적인 일부를 캐싱할 수 있다.

파이프라인 세부사항

이 섹션은 3ds 맥스 파이프라인 아키텍처의 세부사항에 대해서 논의한다. Base Objects, Drived Objects, World Space Derived Objects 라는 개념들이 제출된다. ModContext, ModApp, ObjectState 에 대해서도 설명한다.

아래 다이어그램 [그림1] 은 씬 내의 Cylinder 에 대한 간단한 파이프라인을 보여 준다. 씬의 각 노드는 오브젝트에 대한 참조를 가진다. 이 경우 노드의 오브젝트 참조는 직접적으로 Base Object 를 가리킨다. 그것은 파이프라인을 표현하는 이 오브젝트 참조들의 흐름(flow)이다. 이 경우 그것은 노드의 오브젝트 참조로부터 생성 파라미터를 저장하는 절차적 실린더 오브젝트로의 흐름이다.

 


그림 1
 
3ds 맥스 오브젝트들은 인스턴스화될 수 있다. 이것은 하나 이상의 노드가 같은 오브젝트 참조를 가리킬 수 있다는 것이다. 그림 2는 두 개의 파이프라인을 보여 준다. 이 경우 Cylinder 와 그것의 인스턴스화된 복사본이 존재한다. 인스턴스화된 복사본의 오브젝트 참조가 원래와 같은 Base Object 를 가리키는 방식에 대해 주목하라. 만약 Cylinder 의 생성 파라미터가 변경되면, 두 노드가 씬 내에서 변경될 것이다. 왜냐하면 두 개 모두 생성 파라미터를 저장하는 같은 Base Object 를 가리키기 때문이다.

그림 2
 
3ds 맥스는 특정 방식으로 파이프라인을 수정하기 위한 하나 이상의 수정자의 응용프로그램을 지원한다. 그림 3은 Cylinder 에 Bend 오브젝트 공간 수정자를 적용한 결과인 파이프라인을 보여 준다. 수정자가 처음 적용될 때, 새로운 Derived Object 가 노드의 파이프라인으로 인스턴스화된다.
 

그림 3
 
 
이러한 정렬은 MAX 의 수정자 스택에서 아래와 같이 보일 것이다 :

    Bend
    ------------------------
    Cylinder

이것은 파이프라인의 Base Object 가 실린더임을 지정한다. ------------------ 는 Derived Object 의 시작을 지정한다. Bend 는 이 Derived Object 에 의해 참조되는 수정자이다.

Derived Object 는 자신의 뒤에 다른 오브젝트에 대한 참조를 가지고 오는 하나 이상의 수정자 응용프로그램으로 구성된다. 이 다른 오브젝트는 Base Object 이거나 또 다른 Derived Object 일 수 있다. 위의 그림 3에서 Derived Object 는 단일 수정자 응용프로그램을 가지고 있다. 이 수정자 응용프로그램은 ModApp 라고 불린다. ModeApp 는 주로 수정자에 대한 참조로 구성된다 -- 이 경우 Bend 이다. 수정자는 파이프라인 내에 있는 것이 아니라, 파이프라인 내의 ModApp 에 의해 참조된다는 점에 주목하라.

ModApp 는 수정자 참조뿐만 아니라 ModContext 클래스의 인스턴스도 포함한다. 그림 3과 다음 그림에서, ModContext 는 'MC' 라는 라벨이 붙은 박스로 표현된다. ModContext 는 수정자가 적용된 공간에 대한 정보를 저장한다. 그리고 수정자로 하여금 그것의 연산을 위해서 필요로하는 데이터를 저장할 수 있도록 한다.

특히 ModContext 는 그림 4에 보이는 세 개의 요소를 저장하는데, 아래에 설명되어 있다 :

그림 4
 

    - 변환행렬. 이 행렬은 수정자가 적용된 공간을 표현한다. 수정자 플러그인은 이 행렬을 오브젝트를 변형할 때 사용한다. 플러그인 수정자는 먼저 이 행렬을 사용해 위치를 변환한다. 다음으로 자체의 변형(deformation)을 적용한다. 그리고 나서 그것은 이 변환 행렬의 역행렬을 통해 위치를 되돌린다.

    - 변형(deformation) 경계 상자. 이것은 수정자의 스케일링을 표현한다. 단일 오브젝트에 대해 이것은 오브젝트의 경계 상자이다. 만약 수정자가 하위 오브젝트 선택에 대해서 적용되고 있다면, 그것은 하위 오브젝트 선택의 경계 상자이다. 수정자가 오브젝트 집합 선택(그리고 사용자 인터페이스의 "Use Pivot Points" 체크박스가 꺼져 있을 때)에 대해 적용되고 있다면, 이것은 전체 선택 집합의 경계 상자이다. 오브젝트 집합 선택에 대해 경계 상자는 일정하다(constant). 단일 오브젝트의 경우, 경계 상자는 일정하지 않다. 예를 들어 사용자가 실린더를 90도 각도로 구부렸다고 하면, 실린더의 높이가 변경되고, 우리는 실린더가 계속해서 90도로 구부려져 있기를 원할 것이다. 만약 경계 상자가 적용되지 않는다면, 실린더는 구부려지는 것을 통해 이동해 버릴 것이며, 구부려지는 각이 부정확할 것이다.

    - LocalModData 로부터 상속된 클래스의 인스턴스에 대한 포인터. 이것은 플러그인 개발자가 제어하는 ModContext 의 일부이다. 그것은 수정자가 응용프로그램 지정 데이터를 저장하는 곳이다. LocalModData 클래스는 반드시 구현해야만 하는 두 개의 메서드를 가지고 있다. 하나는 시스템이 ModContext 를 복사할 수 있도록 하는 Clone 프로시저이며, 다른 하나는 클래스가 적절하게 제거될 수 있도록 하는 가상 소멸자이다.
하나 이상의 수정자가 오브젝트에 적용될 수 있다. 그림5 는 이전의 구부러진 실린더에 부가적인 Taper 수정자를 적용한 것을 보여 준다.

그림 5
 
 
이 경우 새로운 ModApp 가 현존하는 Derived Object 에 삽입된다. 새로운 ModApp 의 수정자 참조는 Taper 수정자이다.

3ds 맥스는 씬 내의 요소에 대한 Reference Copies 를 허용한다. 주의 : 여기에서 'Reference' 라는 개념은 3ds 맥스 사용자 인터페이스로부터의 참조의 의미이다 --3ds 맥스 의존성 참조 의미나 C++ 참조가 아니다.

Reference Copy 가 생성될 때, 새로운 Derived Object 가 요소의 파이프라인에 삽입된다. Reference Copy 에 적용된 수정자는 새로운 Derived Object 에 삽입되는 그것의 ModApp 를 소유하게 된다. 그림6에서 그것의 그래픽적 설명을 볼 수 있다. 이 다이어그램은 Refernce Copy되고 있으며 Taper 수정자가 적용된 구부러진 실린더의 결과를 보여 준다. 새로운 Derived Object 의 오브젝트 참조는 원래의 Derived Object를 가리킨다는 점에 주목하라.

그림 6
 
그림6에서 Cylinder02 에 대한 수정자 스택은 아래와 같다 :

    Taper
    -------------------
    Bend
    -------------------
    Cylinder

------------------ 가 두 번 나타나는 것은 두 개의 Derived Object 가 사용되고 있음을 가리킨다는 것에 주목하라. 원래의 것은 Bend 를 가리키는 반면, 새로운 것은 Taper 를 가리킨다.

인스턴스화된 수정자

ModApp 클래스의 인스턴스는 수정자에 대해 부가적으로 존재한다. 왜냐하면 플러그인 수정자 자체가 (몇 몇 오브젝트에 의해 사용되기 위해) 인스턴스화될 수 있기 때문이다. 사용자가 하나 이상의 오브젝트에 수정자를 적용할 때, 수정자 클래스의 새로운 인스턴스가 생성되며, 오브젝트 사이에서 공유된다. 수정자가 적용된 각 오브젝트는 ModApp를 획득하기 위해서 그것의 파이프라인에 삽입된다. 이 ModApp 들은 같은 수정자를 참조하게 된다.

아래의 스크린 이미지와 다이어그램[그림 7과 8]에서, 사용자는 선택된 두 개의 독립적인 Cylinder 들을 씬에 가지고 있으며, "Use Pivot Points" 박스에 체크를 하고, Bend 수정자를 적용했다.
 
 

그림 7
 

그림 8
 
각 실린더는 자신만의 Derived Object 를 소유하고 있다는 점에 주목하라. 그러나 각 ModApp 의 수정자 참조는 같은 인스턴스화된 수정자를 가리킨다. ModApp 는 ModContext(다이어그램에서 'MC'라는 이름이 붙어 있다)도 저장한다. 이 ModContext 의 한 데이터 멤버는 변형 경계 상자이다. "Use Pivot Points" 가 구부림을 적용할 때 적용되어 있기 때문에, ModContext 에 저장되는 각 경계 상자는 각 실린더에 있어서 독립적인 크기로 적용된다. 부가적으로 각 ModContext 의 변환 행렬은 자신만의 공간에서 각 실린더에 적용되는 구부려짐을 반영한다. 구부림의 결과는 지역적으로 적용되며, 각 실린더에 대해서 독립적이다.

다음 경우는 이와 반대이다. 그림9의 스크린 캡춰에서 사용자는 다음과 같이 실린더에 Bend 수정자를 적용한다 : 먼저 독립적인 실린더를 선택한다. 그리고 나서 "Use Pivot Points" 버튼을 체크 해제한다. 그 다음에 Bend 수정자를 적용된다. 이것은 구부러짐이 전체적으로 전체적인 선택 집합에 대해 적용될 것임을 의미한다. 실린더들이 공통적인 중심에 대해 구부러지는 것에 주목하라.
 

그림 9
 
이 경우 구부려짐은 두 실린더의 경계 상자에 상대적으로 적용된다. 즉 ModContext 들의 경계 상자가 각 실린더에 대해 동일하다. 씬에서 하나의 실린더가 다른 것으로부터 멀어지도록 움직이면 어떤 일이 일어날 지에 대해 주목하라. 수정자가 선택되서 기즈모(gizmo)는 경계 상자를 그래픽적으로 보여 준다. 경계 상자의 크기는 모두 그대로이지만, 오브젝트는 더 이상 같은 월드 공간 관계를 공유하지 않는다.
 

그림 10
 
수정자로부터 ModApp를 분리하는 것은 이러한 유연성을 허용한다. ModApp의 ModContext 와 함께 저장되는 경계 상자는 수정자 응용프로그램의 스케일을 표현한다. ModContext 의 변환 행렬은 수정자가 적용된 공간을 표현한다. 인스턴스화된 수정자는 단지 이 정보를 사용해 각 입력 오브젝트를 적절히 수정한다.

파이프라인의 공간 Warp (월드 공간 수정자)

이 섹션은 World Space Modifier 가 적용된 요소의 파이프라인에 대해 논의한다. 아래는 Cylinder 및 Cylinder 가 space warp 에 연결되기(bounding) 전의 Ripple Space Warp 에 대한 다이어그램이다.(ripple : 잔물결, warp : 뒤틀림)

그림 11

 

다음 다이어그램은 그것이 Ripple Space Warp 에 연결된 이후의 Cylinder 파이프라인을 보여 준다.

그림 12
 
이 상태를 위한 수정자 스택은 다음과 같다 :

    Ripple Binding
    =======================
    Cylinder

실린더는 Base Object 이다. ==================== 는 WSM Derived Object 의 시작을 의미한다. Ripple Binding 은 월드 공간 수정자의 실제 응용프로그램이다.

Cylinder 가 Riple 에 연결될 때, 새로운 WSM Derived Object 가 Cylinder 의 파이프라인에 삽입된다. WSM Derived Object 는 Derived Object 와 유사한데, 이는 저장된 오브젝트 공간 수정자 ModApp 들이 특정 노드 안에 저장된다는 점만 제외하고는 같다(WSM Drived Object 는 사실 ClassID() 를 제외하고는 Derived Object 와 같은 클래스이다). 그것들은 특정 노드와 관련이 있기 때문에, 인스턴스화될 수 없다.

ModApp 의 수정자 참조는 새롭게 생성된 WSM Modifier 를 가리킨다. 이 WSM 수정자는 보통 씬 내의 노드에 대한 참조를 가진다. 이 경우 그것은 Ripple01 노드이다. 이 참조는 (노드의 월드 공간 변환 행렬로부터) 노드의 위치를 획득하기 위해서 사용된다. 그것은 이 행렬을 사용해 오브젝트의 위치를 변환하는 데 사용하는데, 이 오브젝트는 WSM 오브젝트의 공간으로 변형(deforming)되고 있으며, 이 공간은 실제로 변형(deformation)이 수행되는 곳이다. Space Warp 플러그인에 대한 부가정보를 원한다면, Advanced Topics 섹션 Space Warp Plug-Ins 를 참조하라.

ObjectState 세부 정보

오브젝트 상태는 파이프라인 다음에 오는 구조체이다. 오브젝트나 상속된 오브젝트 상에서 Object::Eval() 메서드가 호출될 때, 그것은 ObjectState 를 반환한다. 이것은 하나의 오브젝트 참조에서 다음으로 넘겨진다. ObjectState 는 세 가지 요소를 포함하고 있다 :

    - 파이프라인의 오브젝트에 대한 포인터. 수정자는 종종 이 포인터를 사용하여 파이프라인 내의 오브젝트를 참조한다. 이 오브젝트 포인터는 공용 데이터 멤버이며, Object * obj 로 정의되어 있다.
    - 행렬. 만약 오브젝트가 자신을 deformable 유형으로 변환할 수 없다면, 3ds 맥스는 행렬을 대신 변형한다. 행렬이 변형된 후에, 그것은 그것을 TriObject로 만드는 반복적인 작업을 사용해 직교(orthonomal) 행렬로 변환되는데, 이를 통해 얼마만큼의 변형이 수행되었는지를 알 수 있다. 카메라를 ripple 과 같은 공간 워프에 카메라를 연결함으로써 이러한 예를 볼 수 있다. 그 카메라는 공간 워프에 의해서 "변형되는" 만틈 튀어 오르거나 내려가게 될 것이다.

ObjectState 를 다루는 메서드에 대한 세부 정보를 원한다면 Class ObjectState 를 참조하라.

개발자가 파이프라인에 접근하기

이 섹션은 개발자가 파이프라인의 결과에 접근하고 그것을 사용해 작업을 하는 방법에 대해서 다룬다.

노드 파이프라인의 결과를 획득하는 데 사용할 수 있는 API 가 있다. 이것은 INode::EvalWorldState() 이다.

virtual const ObjectState& EvalWorldState(TimeValue time, BOOL evalHidden = TRUE) = 0;



이것은 노드 파이프라인의 결과를 씬에 나타나는 대로 반환한다. 이것은 개발자가 참조하는 오브젝트를 반환하지 않는다 -- 그것은 단지 파이프라인을 따라 내려온 오브젝트일 뿐이다. 예를 들어 씬 내에 Bend 와 Taper 가 적용된 Cylinder 가 있다고 하면, EvalWorldState 는 TriObject 를 포함하는 ObjectState 를 반환한다. 이것은 구부려지고 taper 가 적용된 TriObject 로 바뀐 실린더의 결과물이다. Class INode 를 참조하라.

만약 개발자가 씬 내에서 노드가 참조하는 오브젝트에 접근할 필요가 있다면, INode::GetObjectRef 메서드가 대신 사용되어야 한다. INode - Object Reference Methos 를 참조하라.

Class IDerivedObject 도 개발자로 하여금 상속된 오브젝트를 생성하고, 수정자를 추가/삭제하는 것을 허용한다. 씬 내의 노드에 대한 파이프라인에 접근하고자 한다면, 먼저 INode::GetObjectRef() 를 사용해 오브젝트 참조를 획득한다. 주어진 Object 포인터는 그것의 SuperClassID 를 검사해 GEN_DERIVOB_CLASS_ID 인지 확인한다. 만약 그 아이디가 맞다면 그것을 IDerivedObject 로 형변환할 수 있다. 세부적인 사항을 원한다면 Class IDerivedObject 를 참조하라.

 

채널 세부사항

채널은 수정자로 하여금 오브젝트의 특정 부분만을 수정할 수 있도록 허용한다. 파이프라인은 다음과 같은 채널들로 나뉜다 :

    GEOM_CHANNEL

    오브젝트의 정점들. 대부분의 수정자들이 이 채널만을 수정한다.

    TOPO_CHANNEL

    topoloty 채널. 예를 들어 면, 폴리곤 구조체. smoothing group 과 재질 또한 이 채널의 일부이다. edge visibility 도 이 채널의 일부이다. 왜냐하면 그것은 면 구조체의 속성이기 때문이다.

    TEXMAP_CHANNEL

    텍스처 정점들 및 절차적 매핑.

    MTL_CHANNEL

    이것은 더 이상 사용되지 않는다. 재질은 Face 데이터 구조에 포함되었으며, topoloty 채널의 일부이다.

    SELECT_CHANNEL

    하위 오브젝트 선택 채널. 오브젝트의 선택은 파이프라인을 따라 내려 간다. 선택이 실제로 비교되는 것은 특정 오브젝트 유형까지이다. 예를 들어 TriObject 는 면, 엣지(edge), 정점 선택에 대한 비트를 가지고 있다. 이 채널은 (Mesh 클래스의 selLevel 처럼) 실제 BitArray 가 사용되는 채널이다.

    SUBSEL_TYPE_CHANNEL

    이것은 선택의 현재 수준(level)이다. 파이프라인을 따라 내려가는 모든 오브젝트는 3ds 맥스 사용자 인터페이스에서 Sub-Object 드롭다운 메뉴와 관련된 특정 레벨에 존재한다. 이 채널은 오브젝트가 어떤 레벨에 존재하는지를 가리킨다. 이것은 오브젝트의 유형을 지정하기도 한다. 선택 수전을 표현하기 위해서 32비트가 사용된다. 모든 비트가 0이면, 오브젝트는 object selection level에 존재한다.

    DISP_ATTRIB_CHANNEL

    이들은 요소의 디스플레이를 제어하는 수 많은 비트들이다. 이 비트들은 오브젝트의 유형을 지정한다. Mesh 오브젝트에 대해 이것들은 면 법선 스케일, 서피스 법선의 디스플레이, 엣지 가시성, 디스플레이 플래그이다.

    EXTENSION_CHANNEL

    이 채널은 4.0 이상에서만 이용 가능하다.

    이것은 확장 채널 오브젝트에 의해 사용된다.

    VERTCOLOR_CHANNEL

    이것은 정점 채널 당 색상이다. 이것은 두 번째 텍스처 매핑 채널을 위해 사용되기도 한다.

    GFX_DATA_CHANNEL

    이 채널은 3ds 내부적으로 stripping 을 위해 사용된다. 플러그인 개발자는 이 채널을 지정해 그들의 플러그인에서 변경되거나 사용되도록 할 필요가 없다.

    TM_CHANNEL

    이것은 파이프라인을 따라 내려온 ObjectState TM 이다. 이 TM 은 수정자에 의해 수정될 것이다.

    GLOBMTL_CHANNEL

    이것은 더 이상 사용되지 않는다. 재질은 Face 데이터 구조체에 포함되었으며, 이는 topology 채널의 일부이다.

    다음 #define 은 채널의 그룹을 지정하기 위해서 사용된다 :

    #define OBJ_CHANNELS (TOPO_CHANNEL | GEOM_CHANNEL | SELECT_CHANNEL | TEXMAP_CHANNEL | MTL_CHANNEL | SUBSEL_TYPE_CHANNEL | DISP_ATTRIB_CHANNEL | VERTCOLOR_CHANNEL | GFX_DATA_CHANNEL | DISP_APPROX_CHANNEL | EXTENSION_CHANNEL)

    #define ALL_CHANNELS (OBJ_CHANNELS|TM_CHANNEL|GLOBMTL_CHANNEL)

    주목 : 이 샘플 코드의 일부는 이들 채널을 *_CHANNEL 을 사용해 PART_* 를 대신한다. 예를 들어 PART_GEOM | PART_TOPO 대신에 GEOM_CHANNEL | TOPO_CHANNEL 을 사용한다. 후자는 *_CHANNEL 버전이다.



수정자는 단지 수정하는 특정 채널에 대한 옵션만을 가지고 있다. 이것의 주요 목적은 MAX 의 캐싱 시스템이 더욱 효율적으로 작동하게 만들기 위함이다. 개별 캐시들은 파이프라인을 따라서 서로 다른 지점에서 서로 다른 채널을 위해 생성될 수 있다. 예를 들어 파이프라인의 텍스처 좌표계 부분이 변경된다면, 파이프라인의 기하도형 부분은 캐싱될 것이며, 기하도형 부분은 재평가될 필요가 없을 것이다. 이것은 수정자가 단지 재평가될 필요가 있는 기하도형에만 기반하고 있음을 의미한다.

채널의 의미를 정의하는 것은 오브젝트까지만이다. TOOP 채널을 예로 들자. TriObject에 대해 topology 는 면 구조체, 재질, smoothing 정보이다. SplineShape 에 대해 topology 는 정의되지 않는다. 이것은 SplineShpae 가 실질적으로는 단지 점의 배열일 뿐이고 topology를 가지지 않기 때문이다.

파이프라인에서의 데이터 흐름 - 예제

이 섹션은 Base Object 에서 결과 World Space Chache 까지의 파이프라인 내의 데이터 흐름에 대한 세부적인 관찰을 제공한다. 이것은 파이프라인의 오브젝트 공간 부분의 상속 오브젝트, 노드 변환 컨트롤러 응용프로그램, 파이프라인의 월드 공간 부분까지를 다룬다.

아래의 그림 13 은 Bend 수정자가 적용되고, Ripple 공간 워프에 연결된 절차적 Cylinder 를 보여 준다.

그림 13
 
파이프라인 내부의 데이터 흐름 다음에는 Object Reference가 온다. 위의 예제에서 Cylinder01 노드는 WSM Derived Object 를 가리키는 Object Reference 를 가지고 있다. 그것의 Object Reference 는 Derived Object 를 가리킨다. 그것의 Object Reference 는 Cylinder Base Object 를 가리킨다. 이것은 그것이 파이프라인을 따라 움직일 때마다 데이터가 따라 움직이는 경로이다.

이들 참조 사이에서 이동하는 실제 오브젝트는 ObjectState 이다. 이 ObjectState 는 Object Reference 상에서 호출되고 있는 Object::Eval() 메서드의 결과이다. 아래에 절차적 실린더 기저 오브젝트로부터 시작하는 파이프라인을 따라서 이 ObjectState 가 흘러가는 것에 대한 설명이 나와 있다.

이 파이프라인은 Base Object 에서 시작한다 -- 절차적 Cylinder. 이 시스템은 실린더에 자신을 평가할 것을 요구한다. 실린더의 Object::Eval() 구현에서, 그것은 단지 자신을 반환한다 [ return ObjectState(this); ]. 그것은 이 ObjectState 를 파이프라인의 다음 Object Reference로 반환한다. 그것은 Derived Object 의 Object Reference 이다.

Derived Object 는 이 ObjectState 를 그것의 각 ModApp를 통해 전달한다. 이 예제에서는 Bend Modifier 에 대한 ModApp만이 존재한다. Bend 는 Deformable 오브젝트를 요청한다 [ 그것의 Modifier::InputType()의 구현에서 이를 가리킨다 ]. ModApp 는 오브젝트를 수정자에 적합한 유형으로 변환하는 것을 다룬다. 실린더는 자신을 Deformable 오브젝트로 변환할 것을 요청받는다. 이는 Object::ConvertToType 의 구현에서 수행되는데, 새로운 TriObject 를 생성하고 TriObject 의 메시 포인터를 실린더의 삼각형 메시를 가리키도록 설정한다.

이 Deformable TriObject 는 Bend Modifier로 보내진다. Bend 는 ModApp 의 ModContext를 자신의 Modifier::ModifyObject() 메서드의 인자로서 보낸다. Bend 는 먼저 ModContext 의 변환 행렬을 사용해 TriObject 의 위치를 수정한다. 이것은 오브젝트를 수정자가 적용된 공간으로 이동시킨다. 다음으로 Bend 는 그것의 변형(deformation) 을 그 점들에 적용한다(그것들을 구부린다). 그리고 나서 Bend 는 TriObject의 위치를 ModContext 변환 행렬의 역행렬을 사용해 수정한다. 이것은 오브젝트를 다시 저장하게 되는데, 이제 그것은 이전의 것에 대해 bend 효과가 적용되었다는 차이점을 가지게 되었다.

이 시점에서 Derived Object 는 결과를 파이프라인의 다음 Object Reference 에 전달한다. 이 경우 그 결과는 구부려진 TriObject 이다. 그것은 World Space Derived Object 의 Object Reference로 반환된다.

Derived Object 와 World Sapce Modifier Derived Object 사이의 연결은 파이프라인이 오브젝트 공간에서 월드 공간으로 넘어가는 지점이다. 이 시점에서 노드 변환 컨트롤러의 결과와 오브젝트 오프셋 변환이 파이프라인에 들어 간다. 이러한 다양한 TM 에 대한 세부정보를 원한다면 Advanced Topics 섹션 Node and Object Offset Transforms 를 참조하라.

노드의 변환 컨트롤러와 오브젝트 오프셋 변환이 ObjectState 에 들어가는 것을 이해하기 위해서는 ObjectState TM 에 대해서 살펴볼 필요가 있다. 파이프라인을 따라 올라가는 ObjectState 의 일부인 TM 이 존재한다. 이 TM 은 단위행렬에서 시작한다. 가장 처음 발생하는 것은 이 TM 이 오브젝트 공간 파이프라인을 따라서 움직이는 것이다. 이 시점에서 모든 그것에 적용되어야 할 필요가 있는 모든 수정자들이 그것에 적용된다. 대부분의 경우 수정자는 거의 적용되지 않는다. 그러나 만약 파이프라인을 통해 이동하는 오브젝트가 Deformable 이지만 변형될 지점이 존재하지 않는다면(카메라처럼), Deformable 에서 작동하는 수정자가 그것에 적용될 것이다. 왜냐하면 카메라는 변형가능하지만, 수정될 지점이 존재하지 않기 때문이다(예를 들어 어떠한 메시도 카메라와 관련이 없다). 카메라에는 어떤 점도 존재하지 않기 때문에, 행렬이 대신 변형된다.

파이프라인의 오브젝트 공간 부분이 끝나는 지점에서, ObjectState 와 그것의 TM 이 결과로 나온다. 보통 이 TM 은 단위행렬이지만, 가끔은 (카메라와 같은 경우) 그렇지 않다.

이제 노드 변환 컨트롤러 TM 을 살펴볼 차례이다. 이 변환 컨트롤러 TM 은 노드의 부모 TM을 취해서 그것을 변환 컨트롤러의 Contro::GetValue() 메서드에 넘긴 결과이다. 이 컨트롤러는 그것의 상대적인 효과를 이 행렬에 적용하며, 그 결과가 Node TM 이다.

Node TM 에 대해서 오브젝트 오프셋 변환이 적용된다. 이 결과는 파이프라인의 오브젝트 공간 부분의 결과로서 나온 ObjectState TM 과 곱해진다. 이 결과 행렬은 다시 ObjectState TM 에 저장된다. 이 TM 은 아직 오브젝트에 적용되지 않는다는 점에 주의하라 -- 그것은 단지 ObjectState 에 저장되었을 뿐이다.

이 시점에서 우리는 Object TM 에 저장된 노드의 변환 컨틀로러와 오브젝트 오프셋 변환을 가지게 되었다.

이제 파이프라인의 월드 공간 부분과 월드 공간 수정자로 가 보자 -- 우리는 WSM Derived Object 의 첫 번째 ModApp 에 도달하였다. 첫 번째 World Space Modifier 는 오브젝트의 점을 ObjectState TM 을 사용해 변환한다. 그것은 월드 공간 내의 변형을 수행하는 것이며, Object TM 을 적용하는 것은 오브젝트를 월드 공간에 배치하는 것이기 때문이다. 이것은 deformable 개체들에 대해 자동적으로 일어 난다. 만약 (카메라처럼) TM 이 오브젝트에 대해서 적용되지 않았다면, 그 TM 은 계속해서 월드 공간 수정자에 의해서 변형될 것이다. 이 TM 이 오브젝트 점들에 적용되자 마자, TM 은 단위 행렬로 설정된다. 이 시점에 오브젝트들의 점들은 월드 공간으로 변환된다.

다음으로 ObjectState 는 Ripple 수정자로 넘겨져, 그것의 이펙트를 적용하게 된다. Ripple 수정자는 씬 내의 Ripple Object Node 에 대한 참조를 가지고 있다. 그것은 이 참조를 사용해 Ripple Node 의 Object TM 을 획득한다. 이것은 ripple 이펙트가가 적용되고 있는 공간이기 때문에 필요하다. Ripple Modifier 는 그것의 Ripple Node 에 대한 참조를 사용해 Ripple WSM Object 의 파라미터를 획득한다.

Ripple Modifier 는 이 데이터를 사용하고, 그것의 ripple 이펙트를 적용한다. 이 시점에서 그 결과는 Bend 그리고 Ripple 이 적용된 TriObject 이다. 이 ObjectState 는 파이프라인의 다음 Object Reference 로 반환된다. 이것은 씬 내의 노드에 대한 Object Reference 이다.

이 노드는 ObjectState 를 유지하는데, 이것은 효율적인 월드 공간 캐시이다. 이 월드 공간 캐시는 파이프라인의 결과를 위한 저장소이다. 이 캐시와 관련된 것은 유효성 인터벌(validity interval)이다. 시스템이 노드 파이프라인의 결과를 필요로 할 때, 그것은 캐시의 유효 인터벌이 유효한지 검사한다. 만약 유효하다면 캐시된 표현이 사용된다. 그렇지 않다면, 파이프라인은 재평가를 수행하고, 캐시가 유효하게 만들어진다. 유효 인터벌이 갱신되고, 캐시된 ObjectState 가 반환된다.

파이프라인과 INode TM 메서드들

이 섹션은 INode 메서드들인 GetObjectTM(), GetObjTMBeforeWSM(), GetObjTMAfterWSM() 및 그것과 관련된 파이프라인에 대해 다룬다.

INode::GetObjectTM() 메서드는 오브젝트의 점들을 오브젝트 공간에서 월드 공간으로 변환하는 데 사용된 행렬을 반환한다. 이 메서드가 사용되는 예제를 살펴 보자. 실린더 노드가 씬 내에 그려지는 방법에 대해 살펴보자. 실린더는 자신의 BaseObject::Display() 메서드 내에서 자신을 그린다. 이 메서드로 INode 의 포인터가 전달된다. Display()의 구현이 수행하는 것은 INode::GetObjecTM() 에 대한 호출이다. 이 메서드는 오브젝트 공간에서 월드 공간으로 오브젝트의 점을 변환하는 데 사용되었던 행렬을 반환한다. Diplay() 메서드는 GetObjectTM() 에서 반환된 행렬을 획득하고, 그것을 그래픽 윈도우에 설정한다(GraphicsWindow::SetTransform()을 사용). 이러한 방식으로 오브젝트가 오브젝트 공간에서 점을 그리기 시작할 때, 그것들은 이 행렬을 사용해 변환된다. 이것은 그것들이 그려져야 하는 대로 월드 공간에 배치하게 된다.

아래는 BaseObject::Display() 의 SipleObject 구현이다. 이것은 자신을 그리는 데 실린더가 사용하는 코드이다. GetObjectTM(t) 와 SetTransform(mat) 호출에 주목하라.

int SimpleObject::Display(TimeValue t, INode* inode,

 ViewExp *vpt, int flags)

 {

 if (!OKtoDisplay(t)) return 0;

 GraphicsWindow *gw = vpt->getGW();

 Matrix3 mat = inode->GetObjectTM(t);

 UpdateMesh(t); // UpdateMesh 는 요구된다면 t 시간에 BuildMesh() 를 호출한다.

 gw->setTransform(mat);

 mesh.render(gw, inode->Mtls(),

  (flags&USE_DAMAGE_RECT) ? &vpt->GetDammageRect() : NULL,

  COMP_ALL, inode->NumMtls());

 return(0);

 }


오브젝트의 점이 이미 월드 공간으로 변환된 오브젝트에 월드 공간 수정자가 적용될 경우가 있다. 만약 월드 공간 수정자가 이미 적용되었다면, 그 오브젝트의 점들은 이미 월드 공간으로 변환되어 있으며, 월드 공간 수정자에 의해 변형되었을 것이다. 위의 bent, rippled 실린더의 예제를 사용하면 정확히 이런 상황이 발생한다. ripple 공간 워프가 적용될 때, 오브젝트의 점들은 월드 공간으로 변환된다. 이 경우 그 점들은 그려질 때 월드 공간으로 다시 변환되지 않아야 한다(왜냐하면 그것들은 이미 월드 공간 워프가 적용되었기 때문이다). 문제는 오브젝트가 이미 월드 공간으로 변환되었는지 여부를 알 수 없다는 것이다.

3ds 맥스가 이 상황을 다루는 방식은 노드와 함께 어떤 상태 정보를 저장하는 것이다. 시스템이 오브젝트 상에서 Display(), HitTest() 등을 호출하기 이전에 , 그것은 플래그를 설정한다. 이 플래그는 이미 월드 공간으로 변환되었는지 여부를 가리킨다. GetObjectTM() 메서드는 이 플래그를 보고 반환할 적절한 행렬을 결정한다. 이러한 방식으로 GetObjectTM() 이 호출될 때, 그것은 월드 공간으로 들어가기 위해서 곱해질 필요가 있는 오브젝트의 행렬을 반환한다. 만약 오브젝트가 이미 월드 공간에 존재한다면, 그것은 단위 행렬을 반환할 것이다. 만약 월드 공간에 존재하지 않는다면, 그것은 거기에 들어가기 위한 행렬을 반환할 것이다. So all any objects need to do in their Display() methods is call GetObjectTM() and use whatever matrix is returned.

개발자가 오브젝트의 점들이 이미 월드 공간으로 변환되었는지 여부를 고려하지 않고서 전체 오브젝트 TM 에 대한 접근을 필요로 할 때가 있다. 예를 들어 개발자가 두 개의 오브젝트를 정렬하기 위한 유틸리티 플러그인을 생성하고 있었다고 하자. 이 경우 개발자는 Node TM 과 오브젝트 오프셋 변환을 포함하는 전체 오브젝트 TM 을 획득할 필요가 있을 것이다. 오브젝트의 점들이 월드 공간으로 변환되었는지 여부는 중요치 않으며, TM 자체가 중요하다. 이 경우 GetObjectTM() 은 작동하지 않는다. 왜냐하면 그것은 오브젝트가 이미 월드 공간에 존재할 경우 단위 행렬을 반환하기 때문이다.

이 문제를 해결하기 위해 3ds 맥스는 두 개의 다른 INode 메서드를 제공한다.

    GetObjTMBeforeWSM()

    이 메서드는 월드 공간 수정자가 적용되기 전에 적용된 전체 NodeTM 과 오브젝트 오프셋 변환을 그대로 획득한다.

    GetObjTMAfterWSM()

    이 메서드는 오브젝트의 점들이 월드 공간으로 이미 변환되어서 그것이 단위 행렬을 반환하지 않는 한, 전체 NodeTM, 오브젝트 오프셋 변환, 월드 공간 수정자 affect 를 획득한다.


이 메서드들을 사용해 개발자는 그들이 요구하는 어떠한 변환에라도 완전히 접근할 수 있다. 아래 코드 예제는 이 모든 메서드들을 사용한다. 이 함수는 현재 시간에 현재 선택 집합에 있는 첫 번째 오브젝트의 경계 상자를 계산한다. 그것은 Object TM 으로부터 스케일링되지 않은 모든 것들을 제거한다. 이러한 방식으로 노드의 회전은 경계 상자에 영향을 미치지 않게 된다.

이를 위해서 먼저 오브젝트가 월드 공간에 있는지 오브젝트 공간에 있는지 확인할 필요가 있다. 우리는 오브젝트 공간 경계 상자를 추구하고 있기 때문에(그리고 나중에 스케일링을 적용하기 때문에), 오브젝트가 월드 공간에 존재한다면 TM을 다시 오브젝트 공간으로 변환할 필요가 있다. 오브젝트가 월드 공간에 존재하는 지를 검사하기 위해서 GetObjTMAfterWSM() 을 호출한다. 만약 행렬이 단위 행렬이라면, 우리는 월드 공간에 있음을 알 수 있다. 왜냐하면 오브젝트의 점들이 ObjectState TM 에 의해서 변환되어 월드 공간으로 이동하면, ObjectState TM 은 단위행렬로 설정되기 때문이다. 결국 만약 행렬이 단위행렬이면 월드 공간에 있는 것이다.

오브젝트가 월드 공간에 있다면, 우리는 오브젝트 공간 TM 을 계산할 필요가 있다. 우리는 월드 공간 TM 의 역행렬을 취함으로써 이를 수행할 수 있다.

만약 오브젝트가 월드 공간에 있지 않다면, 우리는 단지 GetObjectTM() 을 호출해 오브젝트 TM 을 획득하기만 하면 된다.

일단 오브젝트 공간 TM 을 가지고 있다면, 우리는 행렬의 스케일링 부분을 배제하기 원할 것이다. 3ds 맥스는 이것을 쉽게 해 주는 API 집합을 제공한다. 이것은 decomp_affine() 을 호출함으로써 수행된다. 이 함수는 행렬을 이동, 회전, 스케일링 요소로 해체한다. 세부사항을 위해서는 Structure AffineParts 을 참조하라.

일단 행렬의 스케일링 부분을 획득하면, 우리는 경계 상자를 획득하고, GetDeformBBox() 를 호출함으로써 스케일링을 적용할 수 있다.

void Utility::ComputeBBox(Interface *ip) {

 if (ip->GetSelNodeCount()) {

  INode *node = ip->GetSelNode(0);

  Box3 box; // 계산된 상자

  Matrix3 mat; // Object TM

  Matrix3 sclMat(1); // 이것은 스케일링을 적용하기 위해 사용될 것이다

  // 현재 시간에서 파이프라인의 결과를 획득

  TimeValue t = ip->GetTime();

  Object *obj = node->EvalWorldState(t).obj;

  // 오브젝트가 월드 공간에 있는지 여부를 확인

  // 그래서 우리는 정확한 TM 을 획득할 수 있다.

  // 우리는 월드 공간 수정자가 적용된 이후의 오브젝트 TM 을

  // 획득함으로써 이를 검사할 수 있다.

   // 만약 행렬이 단위 행렬을 반환하면,

  // 오브젝트의 점들이 월드 공간으로 변환된 것이다.

  if (node->GetObjTMAfterWSM(t).IsIdentity()) {

   // 그것은 월드 공간에 존재하며, 다시 오브젝트 공간으로 돌려야 한다

   // 월드 공간 수정자가 적용되기 전에 반환된 행렬의 역행렬을

   // 계산함으로써 이를 수행할 수 있다.

   mat = Inverse(node->GetObjTMBeforeWSM(t));

  }

  else {

   // 그것은 오브젝트 공간에 있다. Object TM을 획득한다.

   mat = node->GetObjectTM(t);

  }

  // TM 에서 스케일링 부분을 배제

  AffineParts parts;

  decomp_affine(mat, &parts);

  ApplyScaling(sclMat, ScaleValue(parts.k*parts.f, parts.u));

  // 경계 상자 획득. 스케일링 부분을 적용

  obj->GetDeformBBox(t, box, &sclMat);

  // 크기와 프레임 번호를 보여줌

  float sx = box.pmax.x-box.pmin.x;

  float sy = box.pmax.y-box.pmin.y;

  float sz = box.pmax.z-box.pmin.z;

  TSTR title;

  title.printf(_T("Result at frame %d"),

   t/GetTicksPerFrame());

  TSTR buf;

  buf.printf(_T("The size is: (%.1f, %.1f, %.1f)"), sx, sy, sz);

  MessageBox(NULL, buf, title, MB_ICONINFORMATION|MB_OK);

 }

}


캐싱에 대한 중요한 점

(역주 : 이 부분 번역 특히 개판이니 원문을 꼭 참조하세요)

3ds 맥스 파이프라인 캐시 시스템에 대한 전체 세부 사항은 파이프라인을 사용해 효율적으로 작업하기 위해서 프로그래머가 이해할 필요가 있다. There is however one detail that may be useful for a Modifier tha is being adjusted interactively.

수정자가 편집되는 동안에는 그것의 LocalValidity() 의 구현에서 그것은 NEVER 를 반환할 수 있다. 이것은 캐시가 이전의 수정자 다음에 생성되도록 강제한다. 예를 들어 SimpleMod 클래스는 그것 이전에 생성되지 않는다. 파이프라인은 자주 변경되기 이전에는 상대적으로 일정한 무엇인가의 이후에 캐시를 배치하려고 시도할 것이다. 만약 수정자가 그것의 지역 유효성을 위해 NEVER 를 반환하면서 시작한다면, 이것은 캐시가 그것 이전에 생성되도록 만들 것이다. 이것은 쌍방향으로 편집되고 있는 수정자일 경우에 유용하다. 이러한 방식으로 시스템은 전체 파이프라인을 재평가할 필요가 없어진다. 이것은 상호작용을 주목할 만큼 강화해 줄 수 있다. 수정자의 편집이 끝난 이후에 수정자가 변경되지(animated) 않는다면, 그것 이전에 캐시를 보유할 필요가 없어진다. 결국 수정자의 편집이 끝난 이후에, 그것은 LocalValidity() 에 대해 NEVER 를 반환하는 것을 중단한다. SimpleMod 의 LocalValidity() 의 구현으로 부터 나왔다 :

Interval SimpleMod::LocalValidity(TimeValue t)

 {

 // 수정되고 있다면 NEVER를 반환해

  // 이전의 수정자 이후에 캐시가 생성되도록 강제한다.

 if (TestAFlag(A_MOD_BEING_EDITED))

  return NEVER;

 ...


수정자 스택 가지치기(branching)

boolean 오브젝트나 lofter 와 같은 Compound(합성) 오브젝트는 실제적으로 파이프라인이 가지치기를 하도록 만든다. 합성 오브젝트가 파이프라인에서 가지치기를 구현하기 위해서 사용하는 메서드들에 대한 세부 사항을 원한다면 Advanced Topics 섹션 Modifier stack Branching 을 참조하라.

파이프라인을 통과하는 오브젝트들의 흐름

어떤 플러그인 오브젝트는 파이프라인을 통해 이동한다. TriObject 와 PatchObject 가 그 예이다. 그러나 대부분의 플러그인은 그렇지 않은데, 그것들은 자신을 TriObjects 나 PatchObjects 로 변환하기 때문이며, 이들 오브젝트가 파이프라인을 통해 이동하게 된다. 파이프라인을 통해 이동하는 오브젝트를 생성하는 개발자를 위해, 몇 가지 파이프라인 개념과 이해해야만 하는 지정 메서드들이 있다. 이들에 대해 이 섹션에서 논의한다.

이들 메서드 대부분은 오브젝트가 파이프라인을 통해 이동할 때 시스템 내부에 제출되는 오버헤드의 양을 감소하는 것과 관련이 있다. 최대의 효율성을 위해서 시스템은 파이프라인을 따라 내려가는 오브젝트의 부가적인 복사본은 결코 생성하지 않을 것이다. 부가적으로 그 오브젝트는 채널로 쪼개지며, 개별 채널 복사가 최소로 유지될 것이다.

이를 수행하기 위해서, 시스템은 메모리를 해제하거나 오브젝트를 수정할 수 없음을 가리키는 '잠금(locks)' 집합을 사용한다. 오브젝트 잠금 및 채널 잠금 메서드들은 시스템에 의해 구현되며 호출된다. 플러그인 개발자는 채널에 대한 유효 인터벌을 유지하고, 필요할 때 채널의 새로운 복사본을 생성하고, 더 이상 필요없는 채널과 관련한 메모리를 해제하기 위한 메서드들을 구현한다. 다시 말하지만 이것은 파이프라인을 따라 내려 가는 오브젝트의 오버헤드를 최소화하기 위한 주요 목적을 가지고 있다.

모든 오브젝트는 그것의 "쉘(shell)" 이라 불리는 것을 소유한다. 이 쉘은 그것의 내부에 채널을 가지고 있다. 예제 채널은 GEOM_CHANNEL (일반적으로 정점 배열)이라 불리는 기하도형 채널이나 TOPO_CHANNEL (일반적으로 면의 배열) 이라고 불리는 위상(topology) 채널이다. 어떤 채널들은 항상 쉘 내에 제출된다. 예를 들어 단지 단일 값일 뿐이 상태 채널인 SUBSEL_TYPE_CHANNEL, SELECT_CHANNEL, DISP_ATTRIB_CHANNEL 이다. 이들은 동적으로 할당 해제되지 않으며 항상 제출된다. GEOM_CHANNEL, TOPO_CHANNEL, TEXMAP_CHANNEL 과 같은 다른 채널들은 동적으로 할당되며, 시스템은 가능한 한 그것들의 부가적인 복사본을 가지려고 시도하지 않는다.

Object 클래스에 있는 다음 메서드들은 쉘의 상태를 다룬다. 이 메서드들은 시스템에 의해 구현되며 호출된다.

    void LockObject()

    이 메서드는 전체적으로 오브젝트를 잠근다.

    void UnlcokObject()

    이 메서드는 전체적으로 오브젝트의 잠금을 해제한다.

    int IsObjectLocked()

    오브젝트가 잠겨 있다면 0이 아닌값; 그렇지 않으면 0 을 반환한다.


만약 쉘이 잠견있다면, 파이프라인에 의해 제거될 수 없다. 예를 들어 파이프라인의 구(sphere) 가 잠겨 있다고 하자. 그것은 씬 내에 존재하기 때문에 제거될 수 없다. 만약 bend 수정자가 구에 적용되면, 구는 자신을 TriObject 로 변환한다. 이 TriObject 는 파이프라인을 따라 내려가며, 단지 임시 개체일 뿐이다. 이 오브젝트는 그것이 제거될 수 있음을 의미하는 잠금 해제가 될 것이다. 이런 상황은 ModApp 가 그것을 캐싱하기로 하지 않는 이상 진실이다. 이 경우 ModApp 가 그것에 대한 소유권을 획득하기 때문에 잠기게 되며, 결국 그것은 제거될 수 없다. 이 메서드들은 플러그인 오브젝트가 아니라 시스템에 의해 구현되고 호출된다는 것에 주목하라. 그것들은 단지 Object 클래스 내부의 private 데이터 멤버를 조작할 뿐이다.

논의해야할 주제들이 더 있다 -- 채널 잠금. 이것은 오브젝트를 전체적으로 잠그는 것이 아니라 단지 오브젝트 내의 특정 채널만을 잠근다. 예를 들어서 ModApp 는 캐싱된 기하도형 채널을 가지고 있을 수 있다(그것이 파이프라인의 어딘가에 캐싱된 정점 배열을 가지고 있다고 하자). 시스템이 이 파이프라인을 평가하고 있을 때, 그것이 요구하는 특정 채널이 캐싱되었다는 것을 인지하면, 파이프라인의 남은 부분들을 평가하는 대신에, 그것은 캐시를 사용할 것이다. 이것은 파이프라인을 따라 내려 가고 있는 오브젝트로 캐싱된 채널의 shallow copy 라 불리는 것을 수행한다. 이것은 단지 캐싱된 채널에 대한 포인터를 파이프라인을 따라 내려가는 오브젝트로 복사하는 것 뿐이다. 그래서 두 개의 기하도형 채널이 존재하며, 그것들은 같은 정점 배열을 가리킨다. 이것은 요구되는 메모리 오버헤드를 줄여주는데, 전체 정점 배열을 복사하는 대신에 같은 메모리 블록을 공유하는 두 개의 메시가 존재하기 때문이다. 다시 말하지만 이는 shallow copy 라고 불린다. 시스템(MAX) 는 이 모든 것을 다룬다. 맥스는 그것이 사용되고 있을 때 어떠한 소유자가 가지고 있는 것이 제거될 수 있는지 혹은 제거될 수 없는지 여부를 주의깊게 살핀다.

시스템에 다루는 채널 잠금 메서드들이 아래에 있다. 이 메섣들은 플러그인 오브젝트가 아니라 시스템에 의해 구현되고 호출된다. 그것들은 단지 Object 클래스의 private 데이터 멤버를 조작할 뿐이다.

    void LockChannels(ChannelMask channels)

    오브젝트의 지정 채널을 잠근다.

    void UnlockChannels(ChannelMask channels)

    오브젝트의 지정 채널을 잠금 해제한다.

    ChannelMask GetChannelLocks()

    채널의 잠금 상태를 반환한다.

    void SetChannelLocks(ChannelMask channels)

    오브젝트 채널의 잠금 상태를 설정한다.

    ChannelMask GetChannelLocks(ChannelMask m)

    지정 채널의 잠근 상태를 반환한다.


만약 채널이 잠겨 있다면, 그것은 오브젝트가 그 채널을 소유하지 않으며, 그것을 해제하지도 않는다는 것을 의미한다. 만약 채널의 잠금이 해제된다면, 오브젝트는 그것을 소유하고, 해제할 수도 있음을 의미한다. 잠겨진 채널은 수정되어서는 안된다. 예를 들어 파이프라인으로 올라가서 캐싱된 채널이 수정되었다면, 캐싱된 버전은 더 이상 정확한 것이 아니게 된다. 캐싱된 채널을 잠그는 것은 발생할 수 있는 이러한 수정을 막아 준다.

위의 메서드에 있는 ChannelMask 는 unsigned long 값이다. 각 채널은 비트로 표현된다. 주의 : 개발자는 채널 번호(TOPO_CHAN_NUM, GEOM_CHAN_NUM 등) 와 채널 비트(TOPO_CHANNEL, GEOM_CHANNEL 등)를 혼동해서는 안 된다. 어떤 메서드들은 번호에 의해 채널을 참조하고, 어떤 메서드들은 비트에 의해 채널을 참조한다. 개발자는 이들 두 가지를 혼동해서는 안 된다. 왜냐하면 컴파일러는 이것을 검사해서 에러를 반환하지 않기 때문이다.

채널의 의미는 오브젝트에 의해 정의된다. 세 가지 주요 채널(동적으로 할당되는)은 GEOM_CHANNEL, TOPO_CHANNEL, TEXMAP_CHANNEL 이다. 다른 채널들은 항상 제출된다. 개발자는 그들의 오브젝트의 어떤 부분이 GEOM_CHANNEL, TOPO_CHANNEL, TEXMAP_CHANNEL 이 될 것인지 결정해야만 한다. TriObject 는 GEOM_CHANNEL을 정의해 메시의 점이나 정점들을 표현한다. TriObject 는 TOPO_CHANNEL 을 정의해 면이나 폴리곤 구조체를 표현한다. 이것은 smoothing 그룹이나 재질도 포함한다.

각 채널에 대한 유효 인터벌을 저장하기 위해서 파이프라인을 따라 내려 가는 것은 오브젝트까지로 한정되어 있다. 애니메이션되는 반지름 인자를 가진 구 오브젝트를 생각해 보자. 이 구의 기하도형 채널에 대한 유효 인터벌은 즉시(instantaneous)일 것이다(단일 TimeValue 에 대해서 유효함). 반지름이 애니메이션되어 항상 변경되기 때문이다. 그러나 구의 topology 채널은 결코 변경되지 않는다. 결국 topology 채널에 대한 유효 인터벌은 FOREVER 이다. 더 세부 사항을 알고 싶다면 Advanced Topics 섹션 Intervals 를 참조하라. 아래의 메서드는 다른 조건이 없는 한 플러그인 오브젝트에서 개발자에 의해 구현되어야 한다.

    virtual Interval ChannelValidity(TimeValue t, int nchan);

    오브젝트의 nchan 채널에 대한 현재 유효 인터벌을 획득한다.

    virtual void SetChannelValidity(int nchan, Interval v);

    지정된 채널의 유효 인터벌을 설정한다.

    void UpdateValidity(int nchan, Interval v);

    시스템에 의해 구현된다. 이 메서드는 지정된 채널 유효성과 인터벌 v 의 AND 연산을 위해 호출된다.

    virtual void InvalidateChannels(ChannelMask channels);

    이 메서드는 주어진 채널 마스크를 위한 인터벌을 무효화한다. 이것은 유효 인터벌을 비어있도록 설정할 뿐이다(인터벌 상에서 SetEmpty() 호출).


어떤 경우 시스템은 플러그인 오브젝트에 수정(modification)을 위해 특정 채널을 비교하도록 요청해야만 한다. 예를 들어 채널이 파이프라인에 캐싱되고 수정되면, 캐싱된 버전 또한 수정될 것이다(왜냐하면 둘 다 같은 메모리를 가리키기 때문이다). 이것은 시스템을 혼란스럽게 만들고, 결국 잠겨진 채널은 수정되지 않아야 한다. 만약 시스템이 그 채널 중 하나를 수정할 필요가 있다면, 시스템에 의해 구현된 아래의 메서드들을 호출하게 될 것이다.

    void ReadyChannelsForMod(ChannelMask channels);

    이 메서드는 채널 마스크에 의해 지정된 채널을 쓰기 가능하게 만든다.


위에서 언급했듯이 잠겨진 채널은 수정되어서는 안 된다. 이 메서드는 잠겨진 채널을 획득해서 그것을 위한 새로운 메모리 블록을 할당한다. 그리고 나서 잠겨진 채널을 새로운 메모리 블록으로 복사하고, 현재 채널에 대해 새로운 메모리를 설정한다. 그리고 나서 그것은 채널의 잠금을 해제한다. 이는 플러그인에 의해 구현된 NewAndCopyChannels() 를 호출함으로써 수행되며, 아래에 설명되어 있다. 이러한 방식으로 시스템은 채널을 수정할 수 있으며, 그것이 더 이상 같은 메모리를 가리키지 않도록 함으로써 캐싱된 복사본에 영향을 미치지 않게 한다.

다음 메서드들은 오브젝트 채널의 할당과 복사를 다룬다. 이 메서드들은 플러그인에 의해 구현된다.

    virtual Object *MakeShallowCopy(ChannelMask channels);

    이 메서드는 새로운 쉘을 생성하고, 지정된 채널에 shallow 복사를 수행한다.

    virtual void ShallowCopy(Object* fromOb, ChannelMask channels);

    이 메서드에는 쉘이 넘겨받아 지정된 채널을 그 안에 복사한다. shallow 복사는 단지 포인터를 복사한다(예를 들어 정점 포인터나 면 포인터).

    virtual void NewAndCopyChannels(ChannelMask channels);

    이 메서드는 지정된 채널을 취해 그것을 복사한다. 그리고 나서 그것을 (잠금으로써) 읽기 전용으로 만든다.

    virtual void FreeChannels(ChannelMask channels);

    이 메서드는 지정된 채널과 관련된 메모리를 해제한다. 그리고 채널과 관련된 인터벌을 무효화(empty) 한다.


다음 메서드는 캐싱 및 shallow 복사와 관련이 있다. 이 메서드는 파티클 시스템에 의해서만 구현된다. 파티클 시스템은 파이프라인이 작동하는 방식을 회피하며, 그것은 이 메서드를 구현해서 결코 캐싱되지 않도록 보장한다. 파티클 시스템은 자체의 캐싱 메커니즘을 가지고 있다. 파티클 시스템 이외의 다른 오브젝트들은 TRUE 를 반환하는 기본 구현을 사용할 수 있다. 파티클들은 이를 재정의하고 FALSE 를 반환한다.

    virtual BOOL CanCacheObject() {return TRUE;}

    파이프라인을 따라 내려가는 오브젝트를 소유한 개발자는 위에서 언급한 모든 메서드들의 구현을 제공하는 TriObject를 위한 소스코드를 보고 싶을 것이다. 이 코드는 MAXSDK\SAMPLES\HOWTO\MISC\TRIOBJECT.CPP 에 있다.


[출처] - 라이푸님의 http://blog.naver.com/lifeisforu 네이버 블로그