# 3D 장면 형식

> Nodaro 3D 장면 플랜의 구성을 버전 1의 기본 도형과 키프레임부터 버전 2의 GLB 에셋, 시맨틱 엔티티, 카메라 트랙, 샷, 편집 오버레이까지 설명합니다.

Source: https://nodaro.ai/ko/docs/developers/embed/scene3d-format

**3D 장면 플랜**은 Nodaro가 편집 가능한 3D 장면을 위해 저장하는 데이터로, `planType: "3d-scene"`과 명시적인 `schemaVersion`을 가진 컴포지션입니다. [**3D 장면 생성**](https://nodaro.ai/docs/nodes/video/generate-3d-scene)과 [**3D 장면 편집**](https://nodaro.ai/docs/nodes/video/edit-3d-scene) 작업은 `output_data.scenePlan`으로 플랜을 반환하고, [**동영상 렌더링**](https://nodaro.ai/docs/nodes/video/render-video)은 플랜을 MP4로 만들며, [3D 장면 뷰포트](https://nodaro.ai/docs/developers/embed/scene3d)는 플랜을 그립니다. 이 페이지에서는 플랜이 어떻게 구성되는지 설명하므로, 내 코드에서 플랜을 저장하고, 검증하고, 보여 주고, 편집할 수 있습니다.

## 모든 버전의 규칙
- **단위와 축.** 위치는 Y축이 위를 향하는 오른손 좌표계에서 미터 단위입니다. 오일러 회전은 라디안 단위입니다.
- **시간.** 프레임 번호는 0부터 시작합니다. 장면 길이는 1~60초이고 초당 15~60프레임이며, 기본값은 24fps에서 4초입니다.
- **리비전.** 모든 플랜에는 `revisionId`가 있습니다. 편집은 플랜을 절대 바꾸지 않습니다. 대신 `parentRevisionId`가 원래 리비전을 가리키는 새 리비전을 만듭니다. 렌더링된 MP4는 각각 정확히 하나의 리비전을 사용합니다.
- **버전.** 특정 버전에만 있는 필드를 읽기 전에 `schemaVersion`을 읽으세요. 버전 1은 기본 도형 형상을 기술하고, 버전 2는 베이크된 형상과 샷을 추가합니다.
- **지원 기능 확인.** `GET /v1/3d-scene/capabilities`는 배포가 지원하는 제작 엔진, 즉 지원하는 장면 버전을 알려 줍니다. 선택적 엔진은 배포의 구성에 따라 다릅니다. 기본값으로 쓰이는 기본 엔진은 버전 1을 만듭니다.

## 타입과 스키마
`@nodaro/shared` 패키지는 타입과 검증기를 내보냅니다.

| 내보내는 항목 | 설명 |
| --- | --- |
| `Scene3DPlanV1`, `Scene3DPlanV2` | 각 버전의 타입입니다. |
| `Scene3DPlan` | 두 버전의 유니언입니다. |
| `scene3DAnyPlanSchema` | 두 버전을 모두 받는 검증기입니다. |
| `scene3DPlanSchema` | 버전 1 전용 검증기입니다. |
| `scene3DPlanV2Schema` | 버전 2 전용 검증기입니다. |

검증기는 구조와 필드 간 규칙을 확인합니다. 부모 순환, 누락된 부모, 마지막 프레임 이후의 키프레임, 길이 상한을 검사합니다. 검증에 실패한 플랜은 통째로 거부됩니다.

## 버전 1: 기본 도형과 키프레임
버전 1 장면은 그 자체로 완결됩니다. 간단한 캐릭터 대역을 포함해 크기가 정해진 기본 도형과 그룹을 기술하며, 각 오브젝트와 카메라에 희소 키프레임이 있습니다. 텍스처가 있는 정밀한 메시나 물리 시뮬레이션은 재현하지 않습니다.

버전 1 플랜에는 다음이 저장됩니다.

- 오브젝트 ID, 변환, 크기
- 카메라의 위치, 대상, 렌즈
- 조명과 배경
- 각 오브젝트와 카메라의 키프레임

| 제한 | 값 |
| --- | --- |
| 장면당 오브젝트 | 100개 |
| 오브젝트 또는 카메라 트랙당 키프레임 | 240개 |
| 오브젝트 ID 길이 | 64자 |
| 프레임 너비와 높이 | 각 축 100~2,560px |

### 버전 1 장면 편집
`POST /v1/3d-scene/edit`은 `scenePlan`과 `expectedRevisionId`, 그리고 지시 `prompt` 또는 `operations` 목록 중 하나를 받습니다. 리비전이 일치하지 않으면 거부됩니다. 연산은 모델을 호출하지 않고 실행됩니다.

| 연산 | 필드 |
| --- | --- |
| `set-object` | `objectId`, 그리고 ID를 제외한 오브젝트 필드에 대한 `changes` |
| `add-object` | `object` |
| `remove-object` | `objectId` |
| `set-camera` | `changes` |
| `set-lighting` | `changes` |
| `set-background` | `color` |

연산은 지정한 필드만 정확히 바꿉니다. 키프레임이 있는 포즈를 바꾸려면 키프레임 변경도 포함하세요. 편집된 장면 전체가 다시 검증되므로, 편집 후에 고아가 된 부모나 참조가 남을 수 없습니다. 지시 편집 중에 오브젝트를 그대로 유지하려면 `lockedObjectIds`를 사용하세요. [**3D 장면 편집**](https://nodaro.ai/docs/nodes/video/edit-3d-scene)을 참고하세요.

## 버전 2: 베이크된 형상
버전 2 장면은 버전 1의 개념에 네 가지를 추가합니다.

- **저장된 GLB 형상**: 불변 에셋으로 저장됩니다.
- **시맨틱 엔티티**: 사용자가 선택하고 편집하는, 이름이 있는 장면의 부분입니다.
- **매 프레임의 카메라 트랙**: 프레임마다 카메라 포즈가 있습니다.
- **연속된 샷**: 사이가 정확하게 컷으로 나뉜 프레임 범위입니다.

### 에셋
버전 2 에셋 참조에는 불투명한 `assetId`, 종류, 역할, 바이트 길이, SHA-256 다이제스트가 들어 있습니다. 저장소 자격 증명이나 다운로드 URL은 들어 있지 않습니다.

- 리더에는 참조된 바이트가 정확히 필요합니다. 누락되었거나, 너무 크거나, 변경된 에셋은 아무것도 그리기 전에 거부합니다.
- 재생에는 형상과 카메라 데이터를 불러옵니다. 장면의 네이티브 소스 파일은 별도의 다운로드이며, 별도의 권한이 필요합니다.
- 저장된 각 리비전은 이전 리비전에서 다시 사용하는 바이트를 포함해 모든 에셋을 고정합니다.

### 엔티티
시맨틱 엔티티는 선택과 편집을 위해 GLB 형상 안의 루트와 그 루트의 재질 역할에 이름을 붙입니다.

- **재질.** 편집 가능한 모든 재질 역할은 엔티티 자체 형상 안의 재질을 가리킵니다. 클레이 셰이딩은 각 재질의 기본 색상을 유지하므로, 차량의 차체 도색을 바꿔도 타이어는 그대로입니다.
- **부모.** 엔티티는 부모를 선언할 수 있습니다. 그러면 내보낸 루트가 부모의 루트 안에 중첩되고, 노드 변환은 부모를 기준으로 합니다. 부모의 배치와 베이크된 애니메이션은 파일 자체를 통해 자식에게 전달됩니다.
- **일치.** 플랜과 GLB 파일은 같은 부모 관계를 기술해야 합니다. 둘이 일치하지 않는 장면은 리더가 거부합니다.
- **소유.** 형상, 재질, 선택은 그것을 선언한 엔티티에 속합니다. 부모의 색을 바꿔도 그 안에 중첩된 엔티티에는 절대 영향을 주지 않습니다.
- **구성용 루트.** 부모 엔티티는 형상을 전혀 갖지 않고, 그 안에 중첩된 엔티티를 위한 베이크된 변환만 가질 수 있으며, 이 변환은 애니메이션될 수도 있습니다. 안에 무언가 중첩되어 있으면 리더는 이를 받아들입니다.

### 앵커
엔티티 **앵커**는 이름이 있는 점으로, 고정된 `name`과 엔티티 로컬 공간에서의 `position`으로 이루어집니다.

- 에셋 엔티티의 경우, 선택 항목인 `nodeName`이 앵커를 그 엔티티 자체 루트 안의 원시 GLB 노드에 연결합니다. 그러면 위치와 선택 항목인 회전은 그 노드의 로컬 좌표를 사용하고, 노드의 애니메이션, 조상, 엔티티의 수동 편집을 따라갑니다.
- 예를 들어 `{ "name": "door.tip", "nodeName": "car/door.hinge", "position": [1, 0, 0] }`은 경첩의 X축을 따라 1미터 떨어진 곳에 점을 둡니다.
- 노드가 없거나 중첩된 자식 엔티티 안으로 연결하면 재생 전에 거부됩니다. 기본 도형과 그룹의 앵커는 엔티티의 로컬 공간에 남습니다.
- 제작 엔진이 이런 연결을 만들 수 있는지는 엔진에 따라 다릅니다.

### 카메라와 애니메이션
매 프레임의 카메라 트랙에는 프레임마다 위치, 회전, 투영이 있습니다. 리더는 롤과 샷 사이의 정확한 컷을 포함해 이 값을 그대로 유지합니다. 애니메이션은 요청한 프레임에서 샘플링되므로, 뒤로 탐색하든 어느 한 프레임을 렌더링하든 같은 포즈가 나옵니다.

### 표시 여부
엔티티의 선택 항목인 `visible` 플래그는 베이크된 표시 여부를 기록하며, 이 플래그가 없으면 엔티티가 표시됩니다.

- 숨겨진 형상은 불러온 상태로 남고 애니메이션도 계속 실행되므로, 오버레이가 즉시 다시 표시할 수 있습니다.
- 오버레이는 베이크된 값보다 우선하며, 오버레이를 제거하면 베이크된 값이 복원됩니다.
- 숨겨진 부모는 그 안에 중첩된 모든 것도 숨깁니다.
- 숨겨진 오브젝트는 미리보기에서 클릭을 받지 않습니다. 엔티티 목록에서 선택해 다시 표시하세요.
- 베이크된 표시 여부는 리비전 콘텐츠 다이제스트의 일부입니다.

## 오버레이로 버전 2 장면 편집
버전 2 편집은 베이크된 장면 위에 놓이는 불변의 **오버레이**입니다. 오버레이에는 변환, 재질 색상, 표시 여부, 샷 카메라 오프셋의 네 종류가 있습니다. 오버레이는 기본 형상이나 카메라 바이트를 절대 건드리지 않습니다.

- **변환 오버레이**는 엔티티와 그 조상의 베이크된 배치 위에 적용됩니다. `space`는 값을 어느 기준 좌표계에서 읽을지를 나타냅니다.
  - `local`은 베이크된 배치를 포함한 엔티티 자신의 부모 좌표계를 뜻합니다. 값은 그 좌표계에서 일정하게 유지되며, 편집은 움직이는 부모를 따라 함께 이동합니다.
  - `world`는 장면의 축을 뜻합니다. 값은 엔티티의 월드 위치, 회전, 스케일입니다. 회전, 스케일, 애니메이션이 적용된 조상은 엔티티가 최종적으로 놓이는 위치를 바꿀 뿐, 숫자의 의미는 절대 바꾸지 않습니다. 엔티티는 부모 관계를 유지하고 자체 베이크된 애니메이션도 유지하며, 편집이 지정한 채널만 고정됩니다.
  - 리더는 어느 좌표계로도 표현할 수 없는 한 가지 경우를 거부합니다. 회전 아래에 비균등 스케일이 있는 조상으로, 이 경우 변환이 위치, 회전, 스케일로 나뉘지 않습니다.
- **표시 여부 오버레이**는 엔티티와 그 안에 중첩된 모든 것을 숨깁니다. 각 엔티티는 자체 표시 여부를 유지하므로, 조상을 다시 표시하면 스스로 숨겨지지 않았던 자손만 정확히 복원됩니다.
- **편집할 때마다 새 리비전이 만들어지며,** 부모 리비전과 새 콘텐츠 다이제스트를 가집니다. 엔티티 잠금이 적용되며, 오래된 리비전이나 다이제스트를 기준으로 한 편집은 거부됩니다.
- **파생 파일은 이어지지 않습니다.** 포스터, 검증 보고서, 네이티브 다운로드는 편집된 리비전에 연결되기 전에 그 리비전용으로 다시 만들어집니다.

생성 작업과 LLM 비용 없이 오버레이를 저장하려면, `newRevisionId`, 기준 `expectedContentHash`, `operations`, 그리고 선택 항목인 `lockedObjectIds`를 지정해 `POST /v1/3d-scene/revisions/:revisionId/edits`를 호출하거나, SDK에서 `client.scene3d.applyEdits()`를 사용하세요. OAuth 앱에는 `workflows:write` 스코프가 필요합니다. [3D 장면 뷰포트](https://nodaro.ai/docs/developers/embed/scene3d#edit-a-baked-scene)가 이 연산을 대신 만들어 줍니다.

## 렌더링
브라우저 미리보기와 MP4 렌더링은 같은 리더를 사용하므로, 장면은 둘에서 똑같이 보입니다. 버전 2는 클레이 형상과 강체 애니메이션을 받으며, 텍스처가 있거나, 스키닝되었거나, 모프 타깃이 있는 에셋은 거부합니다.

| 버전 2 제한 | 값 |
| --- | --- |
| 시맨틱 엔티티 | 100개 |
| 메시 노드 | 2,000개 |
| 삼각형 | 200,000개 |
| 샷 | 32개 |
| 에셋 | 64개 |
| 재정의 | 200개 |
| 재생용 에셋 바이트 | 64MiB |

스키마는 타이밍, 크기, 계층 깊이, 매니페스트 크기도 제한합니다. 렌더링 가격은 플랜의 프레임 크기에 따라 다릅니다. [**동영상 렌더링**](https://nodaro.ai/docs/nodes/video/render-video)을 참고하세요.

## 가져온 GLB
새 장면은 저장된 리비전에 이미 있는 GLB 파일에서 시작할 수 있습니다. `POST /v1/3d-scene/generate`에서 `inputAssets`는 그중 최대 8개를 각각 `{ id, revisionId, assetId, label? }` 형식으로 선택합니다. 이미지와 동영상 `references`는 별도의 필드로 남습니다.

- 가져오기에는 가져오기를 지원하는 제작 엔진이 필요합니다. 기본 엔진과 가져오기를 지원하지 않는 엔진은 과금 전에 이를 거부합니다.
- 보내는 것은 ID뿐입니다. Nodaro가 정확한 리비전에 대한 접근 권한을 확인하고, 다이제스트와 바이트 길이를 직접 제공합니다. 호출자가 보낸 URL이나 영수증은 거부됩니다.
- 엔진이 가져온 GLB를 읽을 때 Nodaro는 접근 권한을 다시 확인하고 수명이 짧은 다운로드 권한을 발급합니다. 이 권한은 전송용 자격 증명이며, 장면 플랜의 일부가 되는 일은 절대 없습니다.
- 저장된 리비전은 그것을 만드는 데 쓰인 파일의 비공개 사본을 보관할 수 있습니다. 이 사본은 리비전 소유자의 것이며, 수동 편집을 거쳐도 고정된 상태로 남고, 재생용 에셋이나 공개 다운로드에 절대 나타나지 않습니다.

## API로 바이너리 파일 읽기
버전 2 매니페스트는 에셋의 이름을 나열하지만, 바이트는 일반 Bearer 인증을 사용하는 인증된 API에서 받습니다.

| 메서드 | 경로 | 반환 내용 |
| --- | --- | --- |
| `GET` | `/v1/3d-scene/revisions/:revisionId` | 장면 매니페스트와 에셋 디스크립터. |
| `GET` | `/v1/3d-scene/revisions/:revisionId/assets/:assetId` | GLB나 카메라 트랙 같은 재생용 에셋 하나. |
| `GET` | `/v1/3d-scene/revisions/:revisionId/source` | 리비전이 보관하고 있는 경우, 편집 가능한 네이티브 소스. |

- 재생용 에셋에는 리비전이 속한 워크플로의 보기 권한이 필요합니다. 네이티브 소스에는 편집 권한이 필요합니다. 개인 리비전은 소유자만 읽을 수 있습니다.
- 삭제되었거나 접근할 수 없는 리비전은 `404`로 응답하며, 모든 응답에는 `Cache-Control: no-store`가 붙습니다.
- SDK에서 `client.scene3d.assetBytes(revisionId, asset)`과 `client.scene3d.sourceBytes(revisionId)`는 `ArrayBuffer`를 반환하고, 선언된 길이를 확인합니다.

생성, 편집, 렌더링 엔드포인트는 [3D 장면 API](https://nodaro.ai/docs/developers/api/3d-scenes)를 참고하세요.

## 자주 묻는 질문

### Nodaro 3D 장면은 어떤 단위와 축을 사용하나요?

Y축이 위를 향하는 오른손 좌표계에서 미터 단위를 사용합니다. 프레임 번호는 0부터 시작하며, 오일러 회전은 라디안 단위입니다.

### 버전 1 장면과 버전 2 장면은 어떻게 다른가요?

버전 1 장면은 오브젝트와 카메라의 기본 도형과 희소 키프레임을 모두 플랜 안에 기술합니다. 버전 2 장면은 저장된 GLB 형상, 시맨틱 엔티티, 매 프레임의 카메라 트랙, 연속된 샷을 추가하며, 바이너리 파일은 인증된 API 뒤에 남습니다.

### 내 코드에서 장면 플랜을 검증하려면 어떻게 하나요?

@nodaro/shared 패키지의 스키마를 사용하세요. scene3DAnyPlanSchema는 두 버전을 모두 받고, scene3DPlanSchema는 버전 1만 검증합니다.

### 장면을 편집하면 원래 플랜이 바뀌나요?

아니요. 편집할 때마다 자체 리비전 ID와 부모 리비전 링크를 가진 새로운 불변 리비전이 만들어집니다. 편집한 원래 플랜은 그대로 남으며, 버전 2 편집은 기본 형상과 카메라 파일을 그대로 유지합니다.

### 버전 2 장면에서는 어떤 GLB 파일을 사용할 수 있나요?

강체 애니메이션이 있는 클레이 형상입니다. 텍스처가 있거나, 스키닝되었거나, 모프 타깃이 있는 에셋은 거부됩니다. 한 장면에는 엔티티 최대 100개, 메시 노드 2,000개, 삼각형 200,000개, 샷 32개, 재생용 에셋 64MiB까지 담을 수 있습니다.
