스카이박스(절차적 하늘)

Skybox (procedural sky)

이미지 없이, 대기 산란 물리를 흉내 낸 셰이더 하나로 시간대에 따라 변하는 하늘을 그리는 기법.

다른 이름: Sky domePreetham sky modelTHREE.Sky
···
js
import * as THREE from 'three';
import { Sky } from 'three/addons/objects/Sky.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(55, 1, 0.1, 400);
camera.position.set(0, 1.2, 6);

const sky = new Sky();
sky.scale.setScalar(100);
scene.add(sky);
const sun = new THREE.Vector3();
sky.material.uniforms.turbidity.value = 3.2;
sky.material.uniforms.rayleigh.value = 1.6;
sky.material.uniforms.mieCoefficient.value = 0.006;
sky.material.uniforms.mieDirectionalG.value = 0.8;

const ground = new THREE.Mesh(new THREE.CircleGeometry(30, 48), new THREE.MeshStandardMaterial({ color: 0x33482c, roughness: 1 }));
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.6;
scene.add(ground);
const dl = new THREE.DirectionalLight(0xffffff, 1.8);
scene.add(dl);
scene.add(new THREE.AmbientLight(0x9fb3d9, 1.1));

const deco = new THREE.Group();
for (let i = 0; i < 7; i++) {
  const m = new THREE.Mesh(new THREE.ConeGeometry(0.3 + Math.random() * 0.3, 1 + Math.random(), 6), new THREE.MeshStandardMaterial({ color: 0x3a5230, roughness: 0.9 }));
  const a = (i / 7) * Math.PI * 2;
  m.position.set(Math.cos(a) * 3.2, -0.1, Math.sin(a) * 3.2 - 2);
  deco.add(m);
}
scene.add(deco);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  const elevation = 20 + Math.sin(time * 0.15) * 12;
  const azimuth = time * 8;
  const phi = THREE.MathUtils.degToRad(90 - elevation);
  const theta = THREE.MathUtils.degToRad(azimuth);
  sun.setFromSphericalCoords(1, phi, theta);
  sky.material.uniforms.sunPosition.value.copy(sun);
  if (sky.material.uniforms.time) sky.material.uniforms.time.value = time;
  dl.position.copy(sun).multiplyScalar(20);
  camera.position.x = Math.sin(time * 0.1) * 1.5;
  camera.lookAt(0, 0.6, -2);
  renderer.render(scene, camera);
});

전통적인 스카이박스는 정육면체 여섯 면에 하늘 사진을 붙이는 방식이지만, three/addons/objects/Sky.js는 사진 대신 물리 기반 대기 산란(Preetham 모델)을 셰이더로 계산합니다. 태양 방향(sunPosition) 하나만 넣어주면 레일리 산란(파란 하늘)과 미 산란(태양 주변 뿌연 후광)을 함께 계산해서, 태양이 낮으면 붉게, 높으면 파랗게 자동으로 바뀝니다.

sky.scale.setScalar(N)으로 크기를 정한 커다란 정육면체(BoxGeometry) 안쪽 면(side: BackSide)에 이 셰이더가 입혀지므로, 카메라가 그 박스 안에만 있으면 항상 하늘로 보입니다. 태양 위치는 구면 좌표로 넣습니다: elevation(고도각)과 azimuth(방위각)를 THREE.MathUtils.degToRad로 라디안으로 바꾼 뒤 Vector3.setFromSphericalCoords로 방향 벡터를 만들어 sky.material.uniforms.sunPosition.value에 넣습니다.

이 데모는 시간에 따라 elevation을 8~32도 사이로 흔들어 노을 진 하늘에서 한낮 하늘로 서서히 바뀌는 걸 보여줍니다. turbidity(대기 혼탁도)·rayleigh(하늘색 강도)·mieCoefficient/mieDirectionalG(태양 후광 형태) 네 값으로 맑은 날부터 뿌연 날까지 다양한 하늘을 튜닝할 수 있습니다.

언제 쓰나

외부 HDRI 없이 빠르게 하늘 배경이 필요할 때, 낮/밤·일출/일몰 전환을 연출할 때.