파츠 줍기 프롬프트 위젯(드롭 파츠 Radius 진입 시 표시)에 파츠 이미지와 이름을 띄우려 했다.
파츠 이미지 소스로 콘텐츠 브라우저의 스켈레탈 메시 썸네일을 활용하고 싶었는데, 썸네일 캡처 API(ThumbnailTools, FObjectThumbnail)가 에디터 전용 모듈(UnrealEd) 에만 존재한다.
런타임 게임 모듈에서는 접근 자체가 불가하고, 패키징(쿠킹) 시 썸네일 데이터도 제거된다.
해결 방향은 두 가지였다:
SceneCapture로 메시를 실시간 렌더해 RenderTarget을 아이콘으로 사용 → 드롭 프롬프트처럼 자주 뜨고 사라지는 UI에는 메모리/성능 부담이 큼UNSPartDefinition::Icon에 저장 → 런타임 비용 0, 기존 소프트 레퍼런스 비동기 로드 구조와 자연스럽게 연결됨방법 B를 선택했고, 이 작업을 에디터 플러그인으로 구현했다.
Type: Editor)은 에디터 빌드에만 포함되고 패키징 시 자동으로 제외됨Plugins/PartIconTool/
├── PartIconTool.uplugin (Editor 타입, LoadingPhase: PostEngineInit)
└── Source/PartIconTool/
├── PartIconTool.Build.cs
├── Public/
│ ├── PartIconTool.h (모듈 클래스 선언)
│ └── PartIconToolSettings.h (UDeveloperSettings — 캡처 파라미터)
└── Private/
└── PartIconTool.cpp (2패스 SceneCapture + 메뉴 등록)
의존 모듈
| 모듈 | 용도 |
|---|---|
UnrealEd | FPreviewScene, 에디터 유틸리티 |
ToolMenus | 콘텐츠 브라우저 우클릭 메뉴 확장 |
ContentBrowser | UContentBrowserAssetContextMenuContext |
AssetRegistry | AssetCreated 알림 (콘텐츠 브라우저 즉시 반영) |
RenderCore | FlushRenderingCommands |
DeveloperSettings | UPartIconToolSettings 베이스 클래스 |
NeoSanctum | UNSPartDefinition 타입 직접 참조 |
UNSPartDefinition 에셋 1개 이상 선택 → 우클릭GetDefault<UPartIconToolSettings>()으로 캡처 파라미터 읽기 (해상도/각도/밝기)PartMesh.LoadSynchronous()로 스켈레탈 메시 로드Mesh->WaitForPendingInitOrStreaming() + FlushRenderingCommands() — 렌더 리소스 준비 대기FPreviewScene 생성 — 격리된 씬에 메시 + 라이트 + USceneCaptureComponent2D 배치GShaderCompilingManager->FinishAllCompilation() — 머티리얼 셰이더 컴파일 완료 대기SCS_FinalColorLDR) — 색상 BGRA8 캡처SCS_SceneColorHDR) — 커버리지 마스크 캡처, 1 - alpha로 누끼 알파 생성UTexture2D 애셋 생성T_<DefName>_IconTC_EditorIcon / TEXTUREGROUP_UI / NeverStream = true / SRGB = trueDef->Icon에 지정PartMesh가 비어 있는 Definition은 경고 로그 출력 후 건너뜀.
캡처 파라미터 조절: Project Settings > Plugins > Part Icon Tool에서 빌드 없이 조절 가능.
| 설정 | 기본값 | 설명 |
|---|---|---|
IconResolution | 256 | 캡처 해상도 (px) |
OrbitPitch | -11.25 | 카메라 상하 각도 |
OrbitYaw | -157.5 | 카메라 좌우 각도 |
OrbitZoom | 0 | 줌 오프셋 |
DistanceMultiplier | 1.15 | 메시 여백 배율 |
LightBrightness | 1.0 | 정면 라이트 밝기 |
파츠 줍기 프롬프트에 아이콘을 보여주기로 했고, 그 아이콘을 스켈레탈 메시 썸네일처럼 찍어서 쓰고 싶다는 요구였다. 처음엔 ThumbnailTools로 썸네일을 그대로 읽어 텍스처로 저장하는 구조로 시작했다.
GenerateThumbnailForObjectToSaveToDisk(Mesh)
→ FObjectThumbnail → 픽셀 추출 → UTexture2D 저장
문제: 처음 캡처된 이미지가 완전히 검정이었다. 렌더 리소스가 GPU에 올라가기 전에 캡처되었기 때문. WaitForPendingInitOrStreaming() + FlushRenderingCommands()를 추가했지만 여전히 검정이 나왔다. ThumbnailTools 자체가 에디터 뷰포트 상태에 의존적이라 격리된 환경에서 안정적으로 동작하지 않는다는 결론.
썸네일 렌더러에 의존하지 않고 USceneCaptureComponent2D로 직접 메시를 찍는 구조로 전환했다. FPreviewScene으로 격리된 씬을 만들고 메시 컴포넌트와 라이트, 카메라를 직접 배치한다.
FPreviewScene
└── USkeletalMeshComponent (파츠 메시)
└── USceneCaptureComponent2D
→ UTextureRenderTarget2D → 픽셀 읽기 → UTexture2D 저장
이 구조로 전환하자 검정 문제가 사라졌다.
아이콘을 위젯 배경에 자연스럽게 녹이고 싶다는 요구가 추가됐다. SCS_FinalColorLDR은 렌더 안 된 영역도 alpha=1(불투명)로 채워서 검정 배경이 생긴다. 해결을 위해 2패스 캡처로 전환:
SCS_FinalColorLDR) — 색상 정확도 높은 BGRA8 캡처SCS_SceneColorHDR) — alpha 채널에 커버리지 마스크 포함 (1 - alpha = 메시가 있는 곳)두 패스 결과를 합쳐서 1패스 색상 + 2패스 커버리지를 alpha로 적용, 배경이 투명한 PNG형 텍스처 생성.
// 1패스: 색상 픽셀 읽기
Capture->CaptureSource = SCS_FinalColorLDR;
Capture->CaptureScene();
RTResource->ReadPixels(Pixels, ...);
// 2패스: 알파(커버리지) 픽셀 읽기
Capture->CaptureSource = SCS_SceneColorHDR;
Capture->CaptureScene();
MaskResource->ReadLinearColorPixels(MaskPixels);
// 합성
for (int32 i = 0; i < Pixels.Num(); ++i)
{
const float Coverage = FMath::Clamp(1.f - MaskPixels[i].A, 0.f, 1.f);
Pixels[i].A = static_cast<uint8>(FMath::RoundToInt(Coverage * 255.f));
}
2세대로 전환 후 메시가 렌더되긴 하는데 머티리얼이 모두 흰색(기본 머티리얼)으로 나오는 문제가 생겼다. 원인은 머티리얼 셰이더가 아직 컴파일 중인 상태에서 캡처했기 때문. GShaderCompilingManager->FinishAllCompilation() 호출로 해결.
라이트 밝기(LightBrightness=4.0) + 자동 노출(AutoExposure=1.0)이 겹쳐서 메시 전체가 흰색으로 날아가는 문제가 발생했다. LightBrightness를 1.0으로 낮추고 ShowFlags.SetBloom(false)로 블룸 번짐을 차단했다.
각도나 밝기를 코드 수정 없이 바꿀 수 있게 해달라는 요구. UDeveloperSettings(config=Editor)를 상속한 UPartIconToolSettings 클래스를 추가해 Project Settings > Plugins > Part Icon Tool 에서 직접 조절할 수 있게 했다. 빌드 없이 설정 변경 후 바로 Generate하면 즉시 반영된다.
Plugins/PartIconTool/
├── PartIconTool.uplugin
└── Source/PartIconTool/
├── PartIconTool.Build.cs
├── Public/
│ ├── PartIconTool.h
│ └── PartIconToolSettings.h (UDeveloperSettings — 캡처 파라미터)
└── Private/
└── PartIconTool.cpp (2패스 SceneCapture + 메뉴 등록)
캡처 파이프라인 요약
[콘텐츠 브라우저 우클릭 → Generate Icon from Mesh]
↓
PartMesh.LoadSynchronous() + WaitForPendingInitOrStreaming()
↓
FPreviewScene 생성 (메시 + 라이트 + SceneCaptureComponent2D)
↓
GShaderCompilingManager->FinishAllCompilation() ← 셰이더 준비 대기
↓
1패스: SCS_FinalColorLDR → Pixels (색상)
2패스: SCS_SceneColorHDR → MaskPixels (커버리지)
↓
Pixels[i].A = 1 - MaskPixels[i].A ← 누끼 합성
↓
UTexture2D 생성/갱신 (T_<DefName>_Icon, TC_EditorIcon)
↓
Def->Icon = 생성된 텍스처, 패키지 저장
의존 모듈
| 모듈 | 용도 |
|---|---|
UnrealEd | FPreviewScene, 에디터 유틸리티 |
ToolMenus | 콘텐츠 브라우저 우클릭 메뉴 확장 |
ContentBrowser | UContentBrowserAssetContextMenuContext |
AssetRegistry | AssetCreated 알림 (콘텐츠 브라우저 즉시 반영) |
RenderCore | FlushRenderingCommands |
DeveloperSettings | UPartIconToolSettings 베이스 클래스 |
NeoSanctum | UNSPartDefinition 타입 직접 참조 |
| # | 증상 | 원인 | 해결 |
|---|---|---|---|
| 1 | 캡처 결과가 완전 검정 | ThumbnailTools 에디터 뷰포트 의존성, 리소스 미준비 | SceneCapture + FPreviewScene으로 전환 |
| 2 | FlushRenderingCommands LNK2019 | Build.cs에 RenderCore 누락 | PrivateDependencyModuleNames에 추가 |
| 3 | 머티리얼이 흰색 기본 머티리얼로 렌더됨 | 캡처 시 셰이더 아직 컴파일 중 | GShaderCompilingManager->FinishAllCompilation() 추가 |
| 4 | 메시 전체가 흰색으로 날아감 (과노출) | LightBrightness=4.0 + 자동 노출 중첩 | LightBrightness=1.0 + SetBloom(false) |
| 5 | 배경이 검정으로 고정됨 (누끼 안 됨) | SCS_FinalColorLDR은 alpha 항상 불투명 | 2패스 캡처: 2패스 alpha로 커버리지 마스크 생성 후 합성 |
UNSPartDefinition)을 콘텐츠 브라우저에서 선택T_<DefName>_Icon 텍스처가 생성되고 Icon 필드에 자동 지정됨각도나 밝기를 조정하려면 Project Settings > Plugins > Part Icon Tool 에서 수치를 바꾼 뒤 다시 Generate하면 된다. 빌드 불필요.
파츠를 추가할 때마다 이 작업을 1회 실행해야 아이콘이 갱신된다.