Skip to content

3D 파이프라인 최적화 - gltf, 렌더 루프·화면 텍스처 정비 - #93

Merged
pcwadarong merged 2 commits into
mainfrom
refactor/3d-optimization
Jul 8, 2026
Merged

pcwadarong merged 2 commits into
mainfrom
refactor/3d-optimization

Conversation

@pcwadarong

@pcwadarong pcwadarong commented Jul 8, 2026 •

Copy link
Copy Markdown
Owner

작업 내용

1. 에셋 빌드 파이프라인 (gltf-transform) — 다운로드 47.9MB → 9.6MB

  • scripts/optimize-models.mjs / optimize-textures.mjs 추가 (pnpm optimize:models|textures)
  • 지오메트리 Draco → Meshopt 전환 (디코더가 three 번들에 내장 — 추가 요청 0)
  • 임베디드 텍스처 WebP화: 주범이던 4096×4096 fabric roughness(15.7MB) → 1024 WebP. character.glb 32MB → 4.6MB, 프롭 3종 9.4MB → 0.66MB
  • 캐릭터는 보수적 변환만(dedup/prune/resample/sparse/meshopt) — 애니메이션 4종·메시 22개 이름·모프 타깃 보존을 inspect로 대조 검증. weld/simplify/quantize/join 금지 (스키닝·이름 기반 조회 보호)
  • ORM PNG → near-lossless WebP (linear 데이터 채널 번짐 방지), 원본은 assets-src/에 백업 (inspect 베이스라인 포함)

2. 로더·캐싱

  • Draco 디코더 self-host — gstatic CDN 의존 제거
  • 모델 경로 상수화 + .v2 파일명 버전 규칙, /models·/textures·/decoders에 Cache-Control: immutable (재방문 시 재검증 요청 0)
  • 두 캔버스에 <Preload all /> — 셰이더 컴파일/텍스처 업로드를 첫 가시화 전 완료

3. 렌더 루프

  • <RenderWhenVisible /> 신규: IntersectionObserver + visibilitychange로 화면 밖/백그라운드 탭에서 frameloop 정지 (OR로 정지, AND로 재개). 히어로를 보는 동안 하단 연락처 캔버스가 60fps로 돌던 문제 해소
  • frameloop=demand는 상시 idle 애니메이션 특성상 부적합해 배제, 대신 mixer delta 클램프(≤1/30)로 재개 시 점프 방지
  • 그림자 frustum 타이트닝(±6, 1024맵, bias) — 루즈한 2048맵보다 선명하고 저렴
  • 아웃라인 EffectComposer에 multisampling — 호버 시 MSAA가 사라지던 문제 복원

4. 노트북 화면 텍스처 (CanvasTexture 유지 + 품질 개선)

  • SVG foreignObject/RenderTexture/html2canvas 비교 검토 후 현 Canvas 2D 방식 유지가 최선으로 판단 (폰트 임베딩·canvas taint·렌더패스 비용 문제)
  • DPR 기반 2× 슈퍼샘플링 (논리 좌표계 유지 — 기존 레이아웃 상수 무변경)
  • generic sans-serif → body 실폰트(Pretendard) + document.fonts.ready 후 재드로우 (폴백 선렌더로 빈 화면 없음)
  • renderer 이방성(anisotropy) 주입 — 비스듬한 각도에서 텍스트 선명도 유지
  • 향후 실제 영상 재생이 필요하면 offscreen <video> → THREE.VideoTexture를 동일 심(applyCharacterScreenTexture)에 무변경 삽입 가능

@vercel

vercel Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chaen Ready Ready Preview, Comment Jul 8, 2026 7:45am

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca581187-1374-484f-ba2b-05bad0f573f0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/3d-optimization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

pcwadarong and others added 2 commits July 8, 2026 16:39
- Draco→Meshopt 전환 + 임베디드 텍스처 WebP화 스크립트(프롭 공격적/캐릭터 보수적 변환 분리)
- 주범이던 4K fabric roughness(15.7MB)를 1024 WebP로, character.glb 32MB→4.6MB
- ORM PNG는 near-lossless WebP(채널 번짐 방지), 원본은 assets-src/에 백업(inspect 베이스라인 포함)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 모델 경로 상수화(.v2 버전 규칙) + Draco 디코더 self-host로 gstatic CDN 의존 제거, Preload로 셰이더 선컴파일
- 노트북 화면 CanvasTexture: DPR 슈퍼샘플링, body 실폰트+fonts.ready 재드로우, renderer 이방성 주입
- 비가시(스크롤 밖/백그라운드 탭) 시 frameloop 정지, 그림자 frustum 타이트닝, 아웃라인 composer MSAA 복원
- /models·/textures·/decoders에 immutable 캐시 헤더

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pcwadarong
pcwadarong force-pushed the refactor/3d-optimization branch from 91fd5ae to b076843 Compare July 8, 2026 07:39
Base automatically changed from refactor/api-optimization to main July 8, 2026 07:58
@pcwadarong pcwadarong self-assigned this Jul 8, 2026
@pcwadarong
pcwadarong merged commit 39b7c05 into main Jul 8, 2026
6 checks passed
pcwadarong added a commit that referenced this pull request Aug 29, 2026
초판을 a2cfed7(#93 이전)에서 측정하는 바람에 이미 해결된 문제를 다뤘다.
origin/main 기준으로 재측정하고 살아남은 것만 남긴다.

#93이 끝낸 것(에셋 48MB→9.6MB, Meshopt 전환, .vN + immutable 캐시,
RenderWhenVisible, 디코더 self-host)은 "이미 해결됨" 표로 기록해 다음에 같은
분석을 반복하지 않게 한다.

남은 건 VRAM이다. WebP는 파일만 줄이고 GPU에는 RGBA8로 올라가므로 해상도가
그대로인 한 VRAM은 안 줄어든다 — 파일은 5배 줄었는데 VRAM은 714MB→380MB로
1.9배뿐이다. 그래서 목표를 파일 크기에서 VRAM으로 바꿨다.

- 죽은 roughness 텍스처가 아직 임베드돼 있다. #93은 압축만 하고 제거하지
  않았다. 파일로는 1.2MB지만 VRAM으로는 90MB다.
- KTX2 미도입이 나머지 238MB의 원인.

브랜치를 6개에서 3개로 줄였다. 셋 다 파일이 겹치지 않아 병합 순서도 무관하다.
04(믹서 가드) 지시서에는 #93의 RenderWhenVisible이 재현을 가린다는 점과,
delta 클램프를 지우면 안 되는 이유를 추가했다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pcwadarong pcwadarong mentioned this pull request Aug 29, 2026
6 of 7 tasks
@pcwadarong
pcwadarong deleted the refactor/3d-optimization branch August 29, 2026 08:22
pcwadarong added a commit that referenced this pull request Aug 31, 2026
`SceneAssetPreloader`가 루트 레이아웃에 있어 `/articles`, `/resume`, `/project`,
`/guest`, admin 등 3D를 전혀 렌더하지 않는 모든 라우트가 씬 GLB를 통째로 받고
있었다. #93·#95가 총량을 크게 줄였지만 안 쓰는 페이지에서는 여전히 100% 낭비다.

3D 진입점은 `HomeHeroScene`과 `ContactScene` 둘뿐이고 둘 다 `views/home`의
`HomePage`가 유일한 부모다. 그래서 후보는 셋이었고, 그중 홈 뷰 안은 함정이다 —
`HomePage`는 `getHomePageData`(Supabase storage 조회)를 await 한 뒤에야 렌더되고
`(home)/loading.tsx`가 그 세그먼트에 Suspense 경계를 만든다. 프리페치를 거기 두면
데이터 조회를 통째로 기다린다. 비3D 라우트에서 아끼려다 홈 진입을 느리게 만드는 셈이다.

홈 데이터 조회에 3초 지연을 주입하고 첫 GLB 요청 시각을 측정했다(dev, 배치별로
서버를 새로 띄워 각 3회):

| 프리페처 위치 | 첫 GLB 요청 | 데이터 조회를 기다리는가 |
| --- | --- | --- |
| `app/layout.tsx` (변경 전) | 151 / 192 / 149 ms | 아니오 |
| `(home)/layout.tsx` (채택) | 782 / 1115 / 805 ms | 아니오 |
| `HomePage` 안 | 3159 / 3783 / 3567 ms | 예 |

레이아웃은 페이지 세그먼트의 대기와 무관하게 먼저 스트리밍되므로 데이터 조회와
GLB를 병렬로 받는다. 루트 레이아웃보다 ≈650ms 늦는 건 데이터 의존이 아니라 해당
세그먼트 청크의 로드·하이드레이트 순서 차이고, dev 세그먼트별 컴파일이 섞인 값이라
프로덕션에서는 더 줄어든다. 라우트 범위를 좁힌 대가로 감수한다.

`requestIdleCallback` 지연은 넣지 않았다. 홈의 첫 화면이 히어로 3D 씬 자체라
GLB는 초기 페인트의 경쟁자가 아니라 그 화면을 그리는 자원이다.

`layout.tsx`의 `import React`는 계약 테스트가 렌더하기 위해서다. tsconfig가
`jsx: preserve`라 vitest의 esbuild는 classic 런타임으로 변환한다.
@pcwadarong pcwadarong changed the title ♻️ refactor: 3D 파이프라인 최적화 — 에셋 48MB→10MB, 렌더 루프·화면 텍스처 정비 3D 파이프라인 최적화 - gltf, 렌더 루프·화면 텍스처 정비 Sep 1, 2026
pcwadarong added a commit that referenced this pull request Sep 6, 2026
`SceneAssetPreloader`가 루트 레이아웃에 있어 `/articles`, `/resume`, `/project`,
`/guest`, admin 등 3D를 전혀 렌더하지 않는 모든 라우트가 씬 GLB를 통째로 받고
있었다. #93·#95가 총량을 크게 줄였지만 안 쓰는 페이지에서는 여전히 100% 낭비다.

3D 진입점은 `HomeHeroScene`과 `ContactScene` 둘뿐이고 둘 다 `views/home`의
`HomePage`가 유일한 부모다. 그래서 후보는 셋이었고, 그중 홈 뷰 안은 함정이다 —
`HomePage`는 `getHomePageData`(Supabase storage 조회)를 await 한 뒤에야 렌더되고
`(home)/loading.tsx`가 그 세그먼트에 Suspense 경계를 만든다. 프리페치를 거기 두면
데이터 조회를 통째로 기다린다. 비3D 라우트에서 아끼려다 홈 진입을 느리게 만드는 셈이다.

홈 데이터 조회에 3초 지연을 주입하고 첫 GLB 요청 시각을 측정했다(dev, 배치별로
서버를 새로 띄워 각 3회):

| 프리페처 위치 | 첫 GLB 요청 | 데이터 조회를 기다리는가 |
| --- | --- | --- |
| `app/layout.tsx` (변경 전) | 151 / 192 / 149 ms | 아니오 |
| `(home)/layout.tsx` (채택) | 782 / 1115 / 805 ms | 아니오 |
| `HomePage` 안 | 3159 / 3783 / 3567 ms | 예 |

레이아웃은 페이지 세그먼트의 대기와 무관하게 먼저 스트리밍되므로 데이터 조회와
GLB를 병렬로 받는다. 루트 레이아웃보다 ≈650ms 늦는 건 데이터 의존이 아니라 해당
세그먼트 청크의 로드·하이드레이트 순서 차이고, dev 세그먼트별 컴파일이 섞인 값이라
프로덕션에서는 더 줄어든다. 라우트 범위를 좁힌 대가로 감수한다.

`requestIdleCallback` 지연은 넣지 않았다. 홈의 첫 화면이 히어로 3D 씬 자체라
GLB는 초기 페인트의 경쟁자가 아니라 그 화면을 그리는 자원이다.

`layout.tsx`의 `import React`는 계약 테스트가 렌더하기 위해서다. tsconfig가
`jsx: preserve`라 vitest의 esbuild는 classic 런타임으로 변환한다.
pcwadarong added a commit that referenced this pull request Sep 6, 2026
`SceneAssetPreloader`가 루트 레이아웃에 있어 `/articles`, `/resume`, `/project`,
`/guest`, admin 등 3D를 전혀 렌더하지 않는 모든 라우트가 씬 GLB를 통째로 받고
있었다. #93·#95가 총량을 크게 줄였지만 안 쓰는 페이지에서는 여전히 100% 낭비다.

3D 진입점은 `HomeHeroScene`과 `ContactScene` 둘뿐이고 둘 다 `views/home`의
`HomePage`가 유일한 부모다. 그래서 후보는 셋이었고, 그중 홈 뷰 안은 함정이다 —
`HomePage`는 `getHomePageData`(Supabase storage 조회)를 await 한 뒤에야 렌더되고
`(home)/loading.tsx`가 그 세그먼트에 Suspense 경계를 만든다. 프리페치를 거기 두면
데이터 조회를 통째로 기다린다. 비3D 라우트에서 아끼려다 홈 진입을 느리게 만드는 셈이다.

홈 데이터 조회에 3초 지연을 주입하고 첫 GLB 요청 시각을 측정했다(dev, 배치별로
서버를 새로 띄워 각 3회):

| 프리페처 위치 | 첫 GLB 요청 | 데이터 조회를 기다리는가 |
| --- | --- | --- |
| `app/layout.tsx` (변경 전) | 151 / 192 / 149 ms | 아니오 |
| `(home)/layout.tsx` (채택) | 782 / 1115 / 805 ms | 아니오 |
| `HomePage` 안 | 3159 / 3783 / 3567 ms | 예 |

레이아웃은 페이지 세그먼트의 대기와 무관하게 먼저 스트리밍되므로 데이터 조회와
GLB를 병렬로 받는다. 루트 레이아웃보다 ≈650ms 늦는 건 데이터 의존이 아니라 해당
세그먼트 청크의 로드·하이드레이트 순서 차이고, dev 세그먼트별 컴파일이 섞인 값이라
프로덕션에서는 더 줄어든다. 라우트 범위를 좁힌 대가로 감수한다.

`requestIdleCallback` 지연은 넣지 않았다. 홈의 첫 화면이 히어로 3D 씬 자체라
GLB는 초기 페인트의 경쟁자가 아니라 그 화면을 그리는 자원이다.

`layout.tsx`의 `import React`는 계약 테스트가 렌더하기 위해서다. tsconfig가
`jsx: preserve`라 vitest의 esbuild는 classic 런타임으로 변환한다.

This branch was successfully deployed

1 active deployment
Preview — b076843a Deployed Jul 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant