영화, 게임, 기타 3D 그래픽 프로덕션에서는 상당한 양의 데이터를 생성, 저장, 전송하는 경우가 많습니다. 이러한 데이터는 언리얼 엔진, Maya, Houdini, Blender 같은 다양한 소프트웨어에서 제각기 독점적인 씬 디스크립션 형식을 갖춘 아트 파이프라인에서 생성될 수 있습니다.
유니버설 씬 디스크립션(Universal Scene Description, USD) 교환 포맷은 여러 요소 에셋으로 구성된 임의의 3D 씬을 안정적이고 스케일 조절 가능한 방식으로 교환 및 보강하기 위해 Pixar에서 개발한 오픈 소스 포맷입니다. USD는 읽기, 쓰기, 편집, 빠른 3D 지오메트리 프리뷰 및 셰이딩을 위한 다양한 툴세트를 제공할 뿐 아니라 요소 에셋(예시: 모델) 또는 애니메이션을 교환할 수 있도록 지원합니다.
다른 교환 패키지와 달리 USD는 불특정 다수의 에셋을 버추얼 세트, 씬, 샷으로 결합하고 관리하는 작업을 지원합니다. 그런 다음 하나의 씬 그래프에서 일관된 하나의 API를 통해 이를 애플리케이션 간에 전송하고 변경 사항을 유지하면서 편집(오버라이드)할 수 있습니다.
USD를 사용하는 이유
USD는 다양한 3D 애플리케이션 간에 상당한 양의 데이터를 이동하기 위한 공통된 언어를 제공합니다. 이를 통해 아트 파이프라인에 대한 의사 결정을 내릴 때 반복적이고 변경사항을 유지할 수 있는 방식을 통해 유연성을 확보할 수 있으며 다수의 3D 아티스트(애니메이터, 라이팅 또는 셰이딩 아티스트, 모델러, FX 아티스트 등) 간에 협업을 촉진할 수 있습니다.
언리얼 엔진의 USD 지원
언리얼 엔진은 USD 스테이지(USD Stage) 패널과 양방향 Python 워크플로를 통해 USD 지원을 제공합니다.
USD 스테이지 창 이미지를 클릭하면 최대 크기로 볼 수 있습니다.
USD 스테이지(USD Stage) 워크플로는 USD 데이터를 스태틱 메시나 머티리얼 같은 네이티브 언리얼 엔진 에셋으로 변경하지 않고 USD 데이터를 네이티브로 처리하여 작업합니다. 이렇게 하면 USD 데이터를 언리얼 엔진으로 더 빠르게 가져오고, USD 콘텐츠의 원래 구조를 더 명확하게 확인하고, 디스크에서 소스 USD에 적용하는 변경 사항을 실시간으로 업데이트할 수 있습니다.
USD 스테이지는 다음과 같은 기능을 제공합니다.
- 3D 데이터를 '프림'으로 표현: 스태틱 메시, 스켈레탈 메시, HISM, 머티리얼, 라이트, 카메라, 베리언트, 애니메이션, 블렌드 셰이프 등
- 변경 유지 어트리뷰트 편집
- USD 씬 그래프 및 계층구조 시각화
- 페이로드를 사용한 콘텐츠 로딩 및 언로딩
- USD 프리뷰 표면을 갖춘 텍스처 지원
- 프리뷰 표면 및 디스플레이 컬러를 갖춘 머티리얼 지원
- Alembic, 커스텀 패스 리졸버 등 USD 플러그인 지원
- 런타임에서 USD 피처 지원. 자세한 내용은 USD 런타임 지원을 참조하세요.
.usd
,.usda
,.usdc
,.usdz
포맷 로드
유니버설 씬 디스크립션에 대한 보다 일반적인 내용은 Pixar의 USD 소개 페이지를 참고하세요.
Python 워크플로로 작업하는 방법에 대한 자세한 내용은 Python 스크립팅을 참고하세요.
지원되는 작업
언리얼 엔진으로 임포트하기
USD 스테이지에 표시되는 콘텐츠, 즉 스태틱 메시, 스켈레탈 메시, 라이트, 폴리지, 랜드스케이프 는 다음 과 같은 방법을 사용하여 언리얼 엔진으로 임포트할 수 있습니다.
- 파일(File) > 레벨로 임포트(Import Into Level) 사용. 이 프로세스는 스태틱 메시, 스켈레탈 메시, 머티리얼, 텍스처 같은 에셋과 액터를 모두 임포트합니다.
- 콘텐츠 브라우저(Content Browser) 에서 추가(Add)/임포트(Import) 버튼 사용. 이 프로세스는 에셋만 임포트합니다.
- 파일을 콘텐츠 브라우저(Content Browser) 에 드래그 앤 드롭. 이 프로세스는 에셋만 임포트합니다.
- USD 스테이지 에디터(USD Stage Editor) 에서 액션(Action) > 임포트 옵션 사용. 이 프로세스는 에셋과 액터를 모두 임포트합니다. 임포트 프로세스가 완료된 뒤 USD 스테이지의 에셋을 콘텐츠 브라우저(Content Browser) 의 새 액터로 대체할 수 있습니다.
애니메이션 제작 및 편집하기
USD 파일에 저장된 애니메이션은 USDStageActor 의 프로퍼티(Properties) 패널에서 찾을 수 있는 연관된 '레벨 시퀀스(Level Sequence)'를 사용하여 액세스할 수 있습니다.
USD 레벨 스테이지를 선택하고 '프로퍼티' 패널에서 시퀀스를 더블 클릭하여 '레벨 시퀀스'를 엽니다. 이미지를 클릭하면 최대 크기로 볼 수 있습니다.
USD xform 애니메이션은 '레벨 시퀀스' 에서 트랜스폼(Transform) 트랙으로 표시됩니다. 플로트, 부울, 스켈레탈 본 등 기타 애니메이션 형식은 시간(Time) 트랙을 통해 표시됩니다. 위 이미지의 USD 애니메이션 데이터는 애니메이션 경과시간 동안 각 타임코드에서 키/값 쌍으로 표시됩니다.
USD 스테이지(USD Stage) 에서 생성한 레벨 시퀀스(Level Sequence) 를 통해 USD 스테이지에서 스폰된 액터에 바인딩하고 트랜스폼(Transform) 트랙을 통해 애니메이션을 추가할 수 있습니다.
USD 런타임 지원
언리얼 엔진은 레벨 내 USD 스테이지 액터에서 'Set Root Layer'라는 블루프린트 노드를 호출하여 런타임에서 USD 파일을 로드할 수 있도록 지원합니다.
Set Root Layer 노드. 이미지를 클릭하면 최대 크기로 볼 수 있습니다.
이 노드는 에디터에서 프로세스가 처리되는 방식과 동일하게 필수 에셋을 생성하고 액터 및 컴포넌트를 레벨에 생성합니다. 다양한 USD 스테이지 액터 프로퍼티를 제어하기 위한 추가적인 블루프린트 함수는 다음과 같습니다.
블루프린트 함수 | 설명 |
---|---|
Get Generated Assets | 지정된 프림 패스 내에서 프림에 대해 생성된 에셋을 구하고 배열 내에 배치합니다. USD 스테이지 액터 및 프림 패스를 입력으로 사용합니다. |
Get Generated Component | 지정된 프림 패스 내에서 프림에 대해 생성된 컴포넌트를 구합니다. USD 스테이지 액터 및 프림 패스를 입력으로 사용합니다. |
Get Source Prim Path | USD 스테이지에서 지정된 오브젝트에 대한 프림 패스를 구합니다. USD 스테이지 액터 및 오브젝트 레퍼런스를 입력으로 사용합니다. |
Get Time | 타깃 USD 스테이지 액터 내에서 현재 타임스탬프를 구합니다. USD 스테이지 액터를 타깃으로 취합니다. |
Set Initial Load Set | 로드할 초기 페이로드를 설정합니다. USD 스테이지 액터를 입력으로 사용하며 다음과 같은 옵션을 제공합니다.
|
Set Purpose to Load | 로드할 초기 목적을 설정합니다. USD 스테이지 액터 및 인티저를 입력으로 사용합니다.
|
Set Render Context | USD 스테이지의 렌더 컨텍스트를 설정합니다. USD 스테이지 액터를 타깃으로, 렌더 컨텍스트 레퍼런스를 입력으로 취합니다. |
Set Time | USD 스테이지의 현재 타임스탬프를 설정합니다. USD 스테이지 액터를 타깃으로, 플로트 값을 입력으로 취합니다. |
목적(Purpose) 어트리뷰트 및 기타 USD 용어에 대한 자세한 내용은 Pixar의 USD 용어집을 참고하세요.
이 프로세스를 사용하면 USD 파일 콘텐츠를 런타임에서 로드 및 표시하는 애플리케이션을 생성할 수 있습니다.
USD 임포터를 런타임에 활성화하려면 UE_(version)\Engine\Source
폴더에 있는 Project.Target.cs
파일에 다음 줄을 추가합니다. Project는 프로젝트 이름입니다.
GlobalDefinitions.Add("FORCE_ANSI_ALLOCATOR=1");
예를 들면 다음과 같습니다.
public class YourProjectTarget : TargetRules
{
public YourProjectTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new string[] { "YourProject" } );
GlobalDefinitions.Add("FORCE_ANSI_ALLOCATOR=1");
}
}
NVIDIA MDL 지원
언리얼 엔진은 NVIDIA MDL USD 스키마를 사용하여 MDL 표면 머티리얼을 지원합니다. NVIDIA MDL에 대한 자세한 내용은 NVIDIA의 USD 셰이더 어트리뷰트를 참고하세요.
멀티 유저 편집 지원
멀티 유저 편집은 다음과 같은 다양한 USD 스테이지 작업에서 지원됩니다.
- 프림 추가 및 제거
- 프림 이름 변경
- 프림 어트리뷰트 편집
- 비저빌리티 토글
- 현재 스테이지 열기, 닫기, 변경
USD 프로젝트에 대한 멀티 유저 지원을 활성화하려면 USD 멀티 유저 동기화(USD Multi-User synchronization) 플러그인을 활성화합니다. 플러그인의 작동 방식에 대해 더 자세히 알아보려면 플러그인으로 작업하기 페이지를 참조하세요.
USD 멀티 유저 편집은 각 클라이언트에서 USD 스테이지의 루트 레이어(Root Layer) 프로퍼티를 동기화하여 모든 사용자가 동일한 USD 파일을 가질 수 있게 합니다. 이를 위해 각 클라이언트가 동일한 USD 스테이지를 로컬로 열고, 각 시스템에서 에셋 및 컴포넌트를 스폰하고, 각 에셋에 수행된 작업만 동기화합니다.
멀티 유저 편집 세션 동안 모든 사용자가 동일한 파일 경로를 사용하여 USD 파일에 액세스하는 것이 중요합니다. 각 클라이언트가 동일한 파일에 액세스할 수 있도록 타깃 USD 파일을 프로젝트 폴더 내에 저장하고 소스 컨트롤을 사용하여 관리할 것을 권장합니다.
언리얼 엔진의 멀티 유저 편집에 대한 자세한 내용은 언리얼 엔진의 멀티 유저 편집 페이지를 참고하세요.
멀티 유저 세션 중에는 프림 삭제를 실행 취소할 수 없습니다.
USD 임포터 플러그인 활성화하기
언리얼 에디터에서 USD를 사용하여 작업을 시작하려면 플러그인(Plugins) 메뉴에서 USD 임포터(USD Importer) 플러그인을 활성화해야 합니다. 플러그인의 작동 방식에 대해 더 자세히 알아보려면 플러그인으로 작업하기 페이지를 참조하세요.
에디터가 재시작되면 레벨 에디터의 창(Window) > 버추얼 프로덕션(Virtual Production) 메뉴 아래에 USD 스테이지(USD Stage) 옵션이 새로 표시됩니다.
레벨에 추가할 수 있는 새 USD 스테이지 액터(USD Stage Actor) 가 액터 배치(Place Actors) 패널에 표시됩니다.

액터 배치 패널에서 사용 가능한 새 USD 액터
언리얼 엔진에서 USD 사용하기
언리얼 엔진에서의 USD 콘텐츠 작업은 'USD 스테이지 에디터'와 USD 스테이지 액터에서 시작됩니다.
USD 스테이지 패널입니다.
숫자 | 설명 |
---|---|
1 | 계층구조 |
2 | 프로퍼티 |
3 | 레이어 |
USD 스테이지 워크플로
USD 스테이지 액터는 로드된 USD 파일 콘텐츠를 위한 컨테이너 역할을 하며 레벨 내 해당 데이터를 위한 앵커를 제공합니다. USD 파일에서 로드되고 '뷰포트(Viewport)'에 표시되는 3D 씬 오브젝트는 대부분의 다른 언리얼 엔진 기능과 호환되며 이를 다른 액터와 동일하게 취급할 수 있습니다. 애니메이팅된 스켈레탈 메시 등 다른 USD 파일의 콘텐츠를 참조하는 프림을 추가할 수 있습니다.
'USD 스테이지 에디터'에서 파일 > 저장(Save) 메뉴를 사용하여 USD 스테이지에 변경 사항을 저장하면 해당 변경 사항이 USD 파일에 작성됩니다.
USD 스테이지에 대한 자세한 내용은 USD 스테이지 에디터 퀵스타트 페이지를 참고하세요.
언리얼 엔진은 USD 파일이 열렸을 때 USD 스테이지에서 로드된 에셋에 대해 라이트맵을 자동으로 생성하지 않습니다. 이로 인해 스태틱 라이팅이 빌드되었을 때 씬이 완전히 어두워질 수 있습니다.
Python 스크립팅
USD를 사용하여 Python 스크립트를 작성하면 배치 작업, 씬 편집 등 유저 인터페이스를 사용할 경우 수행하기 어렵거나 시간이 오래 걸리는 다양한 작업을 수행할 수 있는 유연한 방법을 제공합니다. 많은 수의 프림 어트리뷰트를 숨기거나 편집하는 등의 작업을 출력 로그(Output Log) 패널에서 실행되는 유연한 Python 스크립트를 통해 빠르게 자동화할 수 있습니다.
Python을 사용하려면 먼저 Python 에디터 스크립트(Python Editor Script) 플러그인을 활성화해야 합니다. 이 플러그인에 대해 자세히 알아보려면 플러그인으로 작업하기 페이지를 참고하세요.
그런 다음 레벨 에디터 하단에 있는 출력 로그(Output Log)를 사용하여 Python 스크립트를 실행할 수 있습니다. 창(Window) > 출력 로그(Output Log) 에서 출력 로그를 별도의 패널로 열 수도 있습니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
언리얼 엔진에서 Python 스크립팅을 사용하는 방법에 대한 자세한 정보는 Python을 사용한 언리얼 에디터 스크립팅 페이지를 참고하세요.
Python 스크립트 사용 사례
언리얼 엔진과 함께 배포되는 USD SDK 버전이 21.05로 업그레이드되었을 때 USDLux 라이트 스키마에 포함된 다양한 어트리뷰트 이름이 변경되었습니다. 이 문제를 해결하기 위해 언리얼 엔진은 USDLux 프림 어트리뷰트의 이름을 21.05 명명 규칙으로 변경하는 Python 스크립트를 포함합니다.
from pxr import Usd, Sdf, UsdLux
import argparse
def rename_spec(layer, edit, prim_path, attribute, prefix_before, prefix_after):
path_before = prim_path.AppendProperty(prefix_before + attribute)
path_after = prim_path.AppendProperty(prefix_after + attribute)
# 적용할 수 없는 네임스페이스 편집을 추가하면 전체 배치가 취소되기 때문에 매번 체크해야 합니다.
if layer.GetAttributeAtPath(path_before):
print(f"Trying to rename '{path_before}' to '{path_after}'")
edit.Add(path_before, path_after)
def rename_specs(layer, edit, prim_path, reverse=False):
prefix_before = 'inputs:' if reverse else ''
prefix_after = '' if reverse else 'inputs:'
# 라이트
rename_spec(layer, edit, prim_path, 'intensity', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'exposure', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'diffuse', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'specular', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'normalize', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'color', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'enableColorTemperature', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'colorTemperature', prefix_before, prefix_after)
# ShapingAPI
rename_spec(layer, edit, prim_path, 'shaping:focus', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shaping:focusTint', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shaping:cone:angle', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shaping:cone:softness', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shaping:ies:file', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shaping:ies:angleScale', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shaping:ies:normalize', prefix_before, prefix_after)
# ShadowAPI
rename_spec(layer, edit, prim_path, 'shadow:enable', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shadow:color', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shadow:distance', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shadow:falloff', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'shadow:falloffGamma', prefix_before, prefix_after)
# DistantLight
rename_spec(layer, edit, prim_path, 'angle', prefix_before, prefix_after)
# DiskLight, SphereLight, CylinderLight
# Note: treatAsPoint should not have the 'inputs:' prefix so we ignore it
rename_spec(layer, edit, prim_path, 'radius', prefix_before, prefix_after)
# RectLight
rename_spec(layer, edit, prim_path, 'width', prefix_before, prefix_after)
rename_spec(layer, edit, prim_path, 'height', prefix_before, prefix_after)
# CylinderLight
rename_spec(layer, edit, prim_path, 'length', prefix_before, prefix_after)
# RectLight, DomeLight
rename_spec(layer, edit, prim_path, 'texture:file', prefix_before, prefix_after)
# DomeLight
rename_spec(layer, edit, prim_path, 'texture:format', prefix_before, prefix_after)
def collect_light_prims(prim_path, prim, traverse_variants, light_prim_paths, visited_paths):
if not prim:
return
if prim_path in visited_paths:
return
visited_paths.add(prim_path)
# 베리어트마다 플립하여 stage.Traverse() 이터레이터를 무효화할 수 있으므로 수동으로 이동합니다.
for child in prim.GetChildren():
# 예시: /Root/Prim/Child
child_path = prim_path.AppendChild(child.GetName())
if UsdLux.Light(child):
light_prim_paths.add(child_path)
traversed_grandchildren = False
if traverse_variants:
varsets = child.GetVariantSets()
for varset_name in varsets.GetNames():
varset = varsets.GetVariantSet(varset_name)
original_selection = varset.GetVariantSelection() if varset.HasAuthoredVariantSelection() else None
# 세션 레이어에서만 선택을 전환합니다.
with Usd.EditContext(prim.GetStage(), prim.GetStage().GetSessionLayer()):
for variant_name in varset.GetVariantNames():
varset.SetVariantSelection(variant_name)
# 예시: /Root/Prim/Child{VarName=Var}
varchild_path = child_path.AppendVariantSelection(varset_name, variant_name)
collect_light_prims(varchild_path, child, traverse_variants, light_prim_paths, visited_paths)
traversed_grandchildren = True
if original_selection:
varset.SetVariantSelection(original_selection)
else:
varset.ClearVariantSelection()
if not traversed_grandchildren:
collect_light_prims(child_path, child, traverse_variants, light_prim_paths, visited_paths)
def update_lights_on_stage(stage_root, traverse_variants=False, reverse=False):
""" 루트 레이어 `stage_root` 로 스테이지를 탐색하고 라이트 프림의 어트리뷰트를 USD 21.05에서 또는 USD 21.05로 업데이트합니다.
이 접근법에서는 구성된 스테이지를 이동하며, 베리언트를 통해 플립하거나 입력 인수에 따르지 않고
UsdLux 라이트인 프림에 대한 경로를 수집하고, 나중에 모든 스테이지의 레이어를 이동하며
라이트 프림 어트리뷰트의 모든 스펙을 'inputs:' 접두사를 추가하여 21.05로 이름을 변경하거나
'inputs:' 접두사를 제거하여 21.05 이전의 스키마로 이름을 변경합니다.
우선 구성된 스테이지를 이동하며 UsdLux 라이트 프림 어트리뷰트만 수정하고
구체의 "반경" 어트리뷰트 등의 수정은 피합니다.
"""
stage = Usd.Stage.Open(stage_root, Usd.Stage.LoadAll)
layers_to_traverse = stage.GetUsedLayers(True)
# 구성된 스테이지에서 UsdLux 프림을 수집합니다.
light_prim_paths = set()
visited_paths = set()
collect_light_prims(Sdf.Path("/"), stage.GetPseudoRoot(), traverse_variants, light_prim_paths, visited_paths)
print("Collected light prims:")
for l in light_prim_paths:
print(f"\t{l}")
# 모든 레이어를 이동하며 라이트 프림의 모든 관련된 어트리뷰트 이름을 변경합니다.
visited_paths = set()
for layer in layers_to_traverse:
# 단일 네임스페이스 편집에서 이 레이어에 대한 모든 이름 변경 작업을 배치 처리합니다.
edit = Sdf.BatchNamespaceEdit()
def visit_path(path):
attr_spec = layer.GetAttributeAtPath(path)
if attr_spec:
prim_path = attr_spec.owner.path
# 각 프림을 한 번만 방문하고 모든 UsdLux 프로퍼티를 한 번에 처리합니다.
if prim_path in visited_paths:
return
visited_paths.add(prim_path)
if prim_path in light_prim_paths:
rename_specs(layer, edit, prim_path, reverse)
layer.Traverse("/", visit_path)
if len(edit.edits) == 0:
print(f"Nothing to rename on layer '{layer.identifier}'")
else:
if layer.CanApply(edit):
layer.Apply(edit)
print(f"Applied change to layer '{layer.identifier}'")
else:
print(f"Failed to apply change to layer '{layer.identifier}'")
# 모든 레이어를 저장합니다.
for layer in layers_to_traverse:
if not layer.anonymous:
layer.Save()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Update light prims to USD 21.05')
parser.add_argument('root_layer_path', type=str,
help='Full path to the root layer of the stage to update e.g. "C:/MyFolder/MyLevel.usda"')
parser.add_argument('--v', '--traverse_variants', dest='traverse_variants', action='store_true',
help='Whether to also flip through every variant in every variant set encountered when looking for light prims')
parser.add_argument('--r', '--reverse', dest='reverse', action='store_true',
help='Optional argument to do the reverse change instead: Rename 21.05 UsdLux light attributes so that they follow the schema from before 21.05')
args = parser.parse_args()
update_lights_on_stage(args.root_layer_path, args.traverse_variants, args.reverse)
USDImporter 소스 파일에서 찾을 수 있는 스크립트는 Engine/Plugins/Importers/USDImporter/Content/Python/usd_unreal/update_lights_to_21_05.py
에 있습니다.
출력 로그 에서 스크립트를 실행하려면 다음 단계를 따릅니다.
-
창(Window) > 출력 로그(Output Log) 를 선택하여 출력 로그(Output Log) 를 엽니다.
-
명령줄 필드 왼쪽의 Cmd 드롭다운을 클릭하고 'Python'을 선택합니다.
-
명령줄 필드에 다음을 입력합니다.
"C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\Importers\USDImporter\Content\Python\usd_unreal\update_lights_to_21_05.py" "C:/path/to/root_layer.usda"
"C:/path/to/root_layer.usda"
는 USD 파일의 경로입니다.위 샘플은 언리얼 엔진 설치에 대한 디폴트 경로를 포함합니다. 언리얼 엔진 버전을 디폴트 위치에 설치하지 않은 경우 경로를 업데이트해야 합니다.
-
Enter 를 눌러서 명령을 실행합니다.