Improve: 프랍 썸네일 생성 개선 (카메라 각도, 조명)

- 카메라 각도 변경: 앞쪽에서 3/4 뷰로 촬영
- 3점 조명 시스템 적용 (키/필/림 라이트)
- 조명 강도 증가로 더 밝은 썸네일 생성
- 모든 프랍 썸네일 재생성

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
user 2026-01-08 21:39:27 +09:00
parent 4b957035b8
commit 25111e0dc5
31 changed files with 100 additions and 94 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -30,7 +30,7 @@ namespace Streamingle.Prop.Editor
private const string DATABASE_PATH = "Assets/Resources/Settings/PropDatabase.asset";
private const string THUMBNAIL_FOLDER = "Thumbnail";
private const int THUMBNAIL_SIZE = 128;
private const int THUMBNAIL_CAPTURE_SIZE = 256;
private const int THUMBNAIL_CAPTURE_SIZE = 512;
private const int GRID_PADDING = 10;
private PropDatabase _database;
@ -1208,13 +1208,13 @@ namespace Streamingle.Prop.Editor
return null;
}
// 카메라 설정
// 카메라 설정 - 퍼스펙티브, 투명 배경
var cameraGo = new GameObject("[ThumbnailCamera_Temp]");
var camera = cameraGo.AddComponent<Camera>();
camera.backgroundColor = new Color(0.15f, 0.15f, 0.15f, 0f);
camera.backgroundColor = new Color(0, 0, 0, 0); // 투명 배경
camera.clearFlags = CameraClearFlags.SolidColor;
camera.orthographic = false;
camera.fieldOfView = 30f; // 좁은 FOV로 왜곡 최소화
camera.orthographic = false; // 퍼스펙티브 카메라
camera.fieldOfView = 35f; // 적당한 FOV로 자연스러운 원근감
camera.nearClipPlane = 0.01f;
camera.farClipPlane = 10000f;
@ -1226,30 +1226,35 @@ namespace Streamingle.Prop.Editor
rt.antiAliasing = 4; // 안티앨리어싱 추가
camera.targetTexture = rt;
// 씬 조명이 없는 경우를 대비한 보조 조명
GameObject backupLightGo = null;
var existingLights = FindObjectsByType<Light>(FindObjectsSortMode.None);
bool hasSceneLight = existingLights.Any(l => l.enabled && l.gameObject.activeInHierarchy && l.type == LightType.Directional);
// 항상 전용 조명 사용 (씬 조명과 독립적으로 일관된 썸네일 생성)
var lightContainer = new GameObject("[ThumbnailLights_Temp]");
if (!hasSceneLight)
{
// 씬에 디렉셔널 라이트가 없으면 임시 조명 추가
backupLightGo = new GameObject("[ThumbnailLight_Temp]");
var light = backupLightGo.AddComponent<Light>();
light.type = LightType.Directional;
light.transform.rotation = Quaternion.Euler(50, -30, 0);
light.intensity = 1.5f;
light.color = new Color(1f, 0.98f, 0.95f); // 약간 따뜻한 색
// 메인 키 라이트 - 카메라 방향에서 약간 위에서 비춤 (밝게)
var keyLightGo = new GameObject("[KeyLight]");
keyLightGo.transform.SetParent(lightContainer.transform);
var keyLight = keyLightGo.AddComponent<Light>();
keyLight.type = LightType.Directional;
keyLight.transform.rotation = Quaternion.Euler(35, -25, 0); // 앞쪽 위에서
keyLight.intensity = 2.0f; // 더 밝게
keyLight.color = new Color(1f, 0.98f, 0.96f); // 따뜻한 화이트
// 보조 조명 (필/림 라이트)
var fillLightGo = new GameObject("[ThumbnailFillLight_Temp]");
var fillLight = fillLightGo.AddComponent<Light>();
fillLight.type = LightType.Directional;
fillLight.transform.rotation = Quaternion.Euler(30, 150, 0);
fillLight.intensity = 0.5f;
fillLight.color = new Color(0.8f, 0.85f, 1f); // 약간 차가운 색
fillLightGo.transform.SetParent(backupLightGo.transform);
}
// 필 라이트 - 반대쪽에서 그림자 채우기
var fillLightGo = new GameObject("[FillLight]");
fillLightGo.transform.SetParent(lightContainer.transform);
var fillLight = fillLightGo.AddComponent<Light>();
fillLight.type = LightType.Directional;
fillLight.transform.rotation = Quaternion.Euler(20, 160, 0); // 뒤쪽에서
fillLight.intensity = 1.0f;
fillLight.color = new Color(0.9f, 0.92f, 1f); // 약간 차가운 색
// 림 라이트 - 뒤쪽에서 윤곽 강조
var rimLightGo = new GameObject("[RimLight]");
rimLightGo.transform.SetParent(lightContainer.transform);
var rimLight = rimLightGo.AddComponent<Light>();
rimLight.type = LightType.Directional;
rimLight.transform.rotation = Quaternion.Euler(10, 180, 0); // 정면 뒤쪽
rimLight.intensity = 0.8f;
rimLight.color = Color.white;
// 렌더링
camera.Render();
@ -1262,7 +1267,7 @@ namespace Streamingle.Prop.Editor
RenderTexture.active = null;
// 정리
if (backupLightGo != null) DestroyImmediate(backupLightGo);
DestroyImmediate(lightContainer);
DestroyImmediate(cameraGo);
DestroyImmediate(rt);
DestroyImmediate(tempInstance);
@ -1339,9 +1344,10 @@ namespace Streamingle.Prop.Editor
/// </summary>
private void PositionCameraToFitBounds(Camera camera, Bounds bounds)
{
// 대각선 방향에서 촬영 (앞쪽에서 45도 각도)
// Z+ 방향이 앞쪽이므로 Z를 양수로 설정하여 앞에서 촬영
Vector3 viewDirection = new Vector3(-0.7f, 0.5f, 1f).normalized;
// 앞쪽에서 약간 위, 약간 오른쪽에서 촬영 (3/4 뷰)
// Unity에서 일반적으로 Z-가 앞쪽 (캐릭터가 바라보는 방향)
// 카메라는 Z+ 방향에서 Z- 방향을 바라봄
Vector3 viewDirection = new Vector3(0.3f, 0.4f, 1f).normalized;
// 바운드 크기 계산
float boundsSphereRadius = bounds.extents.magnitude;
@ -1350,10 +1356,10 @@ namespace Streamingle.Prop.Editor
float fov = camera.fieldOfView * Mathf.Deg2Rad;
float distance = boundsSphereRadius / Mathf.Sin(fov / 2f);
// 약간의 여백 추가 (1.2배)
distance *= 1.2f;
// 약간의 여백 추가 (1.3배 - 좀 더 여유있게)
distance *= 1.3f;
// 카메라 위치 설정
// 카메라 위치 설정 - 오브젝트 앞에서 촬영
camera.transform.position = bounds.center + viewDirection * distance;
camera.transform.LookAt(bounds.center);