하늘과 원경
출처: room-at-dawn (MIT), src/scene/sky.js, src/scene/parts/exterior.js.
핵심 아이디어: 하늘은 한 번만 정의한다.
- 베이킹(baking)에서는 해석적 하늘(analytic sky)을 사용합니다: 지평선에서 천정까지의 그라데이션과 태양 방향의 광채(glow)로 구성됩니다.
- 보이는 하늘 돔(sky dome)은 동일한 그라데이션 위에 구름을 추가하지만, 평균 밝기가 그라데이션과 거의 같도록 합니다: 구름 사이의 맑은 하늘은 평균보다 어둡고, 구름 가장자리는 더 밝으며, 구름 중심은 거의 같습니다.
- 이렇게 하면 창밖에서 보이는 하늘이 방 안으로 들어오는 빛과 일치합니다. 이를 확인하기 위해
measureDome을 사용합니다.
파일
| 경로 | 내용 |
|---|---|
scripts/sky.js | SKIES (dawn, day, overcast, golden), skyRadiance (threejs-baked-gi의 worker와 동일한 공식), bakeCfg, skyDome, SKY_U (uTime), skyLights, skyEnvironment, measureDome |
scripts/backdrop.js | windowVisibility (창문 개구부 가시성 및 LOD), hazePatch (대기 원근 안개, 기존 셰이더 패치 유지), grassTexture, roofTexture, windowTexture, crownTexture, Soup, houses, lawn, gravelPath, hedge, crownWall |
example/ | 2층 방(마당보다 5.5m 높음)에서 창밖을 바라보는 예시: 잔디, 길, 산울타리, 세 채의 집, 나무, 먼 나무 벽 |
example/preview*.jpg | 네 가지 하늘 프리셋을 실내에서 본 모습; 창가, 하늘을 올려다봄, 외부 조감 |
references/sky.md | 하늘 셰이더의 각 레이어, 기본 파라미터, 조정 방법, 베이킹과의 연동 방법 |
references/backdrop.md | 가시성 판단, 안개, 각 원경 요소, 성능 데이터, 발생한 문제, 다른 엔진으로의 이식 방법 |
예시의 나무는 threejs-procedural-vegetation(상대 경로 ../../threejs-procedural-vegetation/)을 사용했습니다.
예시 실행
cd <skill 폴더>/.. && python3 -m http.server 5426 --bind 127.0.0.1
# http://127.0.0.1:5426/threejs-sky-backdrop/example/
(<skill 폴더>는 이 스킬을 로드할 때 표시되는 Base directory입니다; 플러그인으로 설치된 경우 ~/.claude/plugins/cache/ 아래에 있습니다)
?sky=dawn|day|overcast|golden: 하늘 프리셋.?cam=window|wide|up|out: 방 안에서 보기 / 창가 / 하늘 올려다보기 / 외부 조감.?cull=0: 가시성 컬링 비활성화 (비교용).?t=5: 시간 고정, 구름이 움직이지 않음.
window.__sky에는 소요 시간, 삼각형 수, 잎사귀 수, 그리고 하늘 돔과 해석적 하늘의 비율이 포함됩니다.
사용법
import { SKIES, skyDome, bakeCfg, skyLights, skyEnvironment, measureDome, SKY_U } from './scripts/sky.js';
const S = SKIES.dawn;
scene.add(skyDome(S)); // 베이킹된 실내: display 0.3 (기본값); 실외: skyDome(S, { display: 1 })
const cfg = { ...bakeCfg(S), portal, room }; // threejs-baked-gi의 runBake에 전달
// 베이킹하지 않는 씬:
const { sun, hemi } = skyLights(S); scene.add(hemi); if (sun) scene.add(sun, sun.target);
scene.environment = skyEnvironment(renderer, S);
console.log(measureDome(renderer, S)); // ratio 0.85–1.15
renderer.setAnimationLoop((ms) => { SKY_U.uTime.value = ms / 1000; ... });
반드시 지켜야 할 사항
- 하늘은 한 번만 정의한다: 베이킹, 하늘 돔, 조명이 모두 동일한 프리셋에서 생성됩니다. 하늘 색상을 변경했다면 다시 베이킹해야 합니다.
- 구름을 조정한 후에는 반드시
measureDome을 실행하세요. 하늘 돔을 float cube map에 렌더링하고, cos 가중치로 상반구 평균을 계산하여 해석적 하늘과 비교합니다.- 비율이 0.85–1.15 사이에 있어야 합니다. 그렇지 않으면 창밖 하늘과 들어오는 빛이 일치하지 않습니다.
{ pure: true }옵션을 추가하면 그라데이션만 렌더링하며, 결과가 정확히 1.000이어야 측정 자체가 올바른 것입니다.
- 구름의 색상은 절대 밝기(absolute luminance) 이며, 하늘과 동일한 단위이고 배수가 아닙니다. 그라데이션에 맞춰 스케일링하도록 변경해 본 결과, 새벽 프리셋에서 비율이 0.73으로 떨어져서 원래 프로젝트 코드로 되돌렸습니다.
- 베이킹된 실내에서는 하늘 돔을 더 어둡게 렌더링합니다 (
display약 0.3). 노출이 실내에 맞춰져 있기 때문에, 하늘을 실제 밝기로 그리면 화면이 하얗게 날아가서 색상을 잃습니다. 이는 조명용 밝기와는 무관합니다. - 보이는 것만 렌더링합니다.
windowVisibility는 방 안의 여러 눈 위치에서 두께가 있는 창문 개구부를 통해 바깥을 봅니다.- 보이지 않는 잎사귀: 식물 스킬의
lodAt이 0을 반환하여 소량의 큰 카드만 남기고, 베이킹 시 나무 그늘이 생기도록 합니다. - 비스듬히 볼 때만 보이는 것: 1을 반환하여 더 드물게 렌더링합니다.
- 보이지 않는 잎사귀: 식물 스킬의
- 원경에는
hazePatch를 적용해야 합니다. 10m 이상 거리에서는 지평선 방향으로 색상이 희미해집니다. 그렇지 않으면 먼 집과 나무가 가까운 것과 동일한 깊이와 채도로 보여 스티커처럼 보입니다.
실측 (RTX 4090, Chrome headless, 1280×800)
| 항목 | 값 |
|---|---|
| 하늘 돔 ÷ 해석적 하늘 | dawn 0.934, day 1.080, overcast 1.034, golden 1.034; pure 모드 1.000 |
| 원경 (잔디, 길, 산울타리, 집, 나무 벽) | 31–34 ms |
| 나무 (주 나무 4그루, 중경 8그루, 원경 26그루) | 생성 0.14–0.16 s, 메시 구축 0.31 s |
| 가시성 컬링 | 잎사귀 117,808 → 97,589 (−17%), 삼각형 42.5만 → 38.3만. 주로 외벽에 붙어 창문 양쪽에 있는 두 그루의 나무에서 절감됨 |
| 하늘 환경 맵 + 두 번의 측정 | 약 0.09 s |