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