BabylonJS 기초 4일차 : 라이트 이해(조명 4종)



BabylonJS 기초 4일차 : 라이트 이해(조명 4종)

요약

3D 씬이 어둡게 보이는 가장 흔한 이유는 “카메라가 아니라 라이트 문제”입니다. Babylon.js에서는 기본적으로 Hemispheric / Directional / Point / Spot 네 가지 조명을 제공합니다.

오늘은 이 4종 라이트를 아주 쉽게 비교해 보고, 빛의 위치·강도·색기본 그림자 ON/OFF까지 실습해서 “왜 어둡게 보이는지”를 스스로 해결할 수 있는 상태가 되는 것이 목표입니다.

목차


1. 라이트가 하는 일 한 줄 정리

라이트 = “씬 전체에 색과 밝기를 입히는 조명 스위치”라고 생각하시면 됩니다. 메쉬(박스, 구, 캐릭터 등)에 머티리얼과 텍스처를 아무리 예쁘게 입혀도, 라이트가 없으면 화면은 거의 까맣게 보입니다. Babylon.js에서는 라이트가 각 픽셀에 도달하는 빛의 세기와 색을 계산하고, 머티리얼이 그 빛을 어떻게 반사/하이라이트할지 결정합니다.


2. 조명 4종 개념 정리 (비유 위주)

2-1. HemisphericLight – “방 안 전체를 은근히 밝히는 형광등”

Hemispheric 라이트는 전체적인 주변 밝기(환경광)를 담당합니다. 빛이 특정 점에서 나오기보다는 “위쪽 하늘에서 은은하게 내려오는 빛”이라고 생각하면 이해가 쉽습니다.

  • 어두운 씬을 빠르게 “대충 전체적으로” 밝게 만들고 싶을 때 가장 먼저 쓰는 라이트
  • direction은 빛이 들어오는 방향이 아니라 “어디서 반사되어 오는지”를 의미
  • intensity를 0~1 사이에서 조절하면 전체 씬 밝기가 조절됩니다.
  • groundColor를 이용해 바닥에서 튀어 오르는 색(예: 약간 청록색)을 표현할 수 있습니다.

2-2. DirectionalLight – “태양처럼 한 방향에서 쏟아지는 평행광”

Directional 라이트는 “태양빛” 비슷합니다. 빛이 아주 멀리서 오기 때문에 씬 전체에 거의 평행한 방향으로 빛이 들어옵니다.

  • 무한히 멀리 있는 태양을 생각하면 됩니다. 빛의 위치보다 “방향(direction)”이 더 중요합니다.
  • 실외 씬, 낮 장면, 큰 맵(도시, 들판, 건물들)에서 많이 사용됩니다.
  • 그림자를 만들 때 자주 사용하는 타입입니다.

2-3. PointLight – “천장 전구, 전구 하나”

Point 라이트는 한 점에서 사방으로 퍼지는 전구라고 보시면 됩니다. 위치가 아주 중요하고, 가까운 곳은 밝고 먼 곳은 점점 어두워집니다.

  • 실내 방 전구, 가로등, 램프 표현에 적합합니다.
  • 위치(position)를 바꾸면 빛이 움직이는 느낌이 바로 드러납니다.
  • 거리에 따라 감쇠(falloff)가 적용되어 현실적인 느낌을 줄 수 있습니다.

2-4. SpotLight – “손전등, 무대 스포트라이트”

Spot 라이트는 원뿔 모양으로 비추는 손전등이라고 생각하시면 됩니다. 방향과 각도(퍼지는 폭), 밝기 감쇠를 모두 제어할 수 있습니다.

  • 플레이어가 손전등을 들고 돌아다니는 게임, 무대 공연 조명에 많이 사용
  • angle로 빔의 넓이, exponent로 중심부의 강도와 테두리 부드러움을 조절
  • 그림자와 함께 쓰면 “한 곳만 환하게 비치는” 극적인 장면 연출 가능

3. 라이트 4종 비교 표

타입 비유 주요 용도 핵심 속성 장점 주의점
Hemispheric 방 전체를 비추는 형광등 기본 환경광, 씬 전체가 너무 어두울 때 intensity, diffuse, groundColor 설정이 매우 간단, 씬을 빠르게 “살릴” 수 있음 그림자 표현엔 적합하지 않음, 평평한 느낌이 날 수 있음
Directional 태양빛 야외 씬, 넓은 맵, 메인 광원 direction, intensity 씬 전체에 일정한 방향의 빛, 그림자 표현에 좋음 거리 감쇠가 없어 실내 전구 표현에는 부자연스러울 수 있음
Point 전구 한 개, 가로등 실내 조명, 램프, 가로등, 작은 영역 position, intensity 위치 변경만으로도 빛 움직임이 직관적으로 보임 여러 개를 사용하면 성능에 영향, 그림자 사용 시 더욱 주의
Spot 손전등, 무대 스포트라이트 특정 대상 강조, 캐릭터 손전등, 무대 연출 position, direction, angle, intensity 아주 극적인 연출 가능, 시선 유도에 좋음 각도·강도 설정을 잘못하면 너무 인공적인 느낌이 날 수 있음

4. Shadow 맛보기: 그림자가 생기는 원리

Babylon.js에서 그림자는 ShadowGenerator라는 도우미 객체가 있어야 합니다. 

흐름을 아주 단순화하면 다음과 같습니다.

  1. 그림자를 만들고 싶은 라이트(주로 Directional/Spot)에 new BABYLON.ShadowGenerator(...)를 붙입니다.
  2. 빛을 가리는 메쉬(캐릭터, 박스 등)를 shadowGenerator.getShadowMap().renderList에 넣어 줍니다.
  3. 그림자를 받는 바닥 메쉬에 receiveShadows = true를 설정합니다.

이렇게 하면 빛과 물체, 바닥 사이에 관계가 생기면서 자연스러운 그림자가 표현됩니다. 이번 포스팅에서는 아주 기본적인 ON/OFF 정도만 맛보고, 세부 품질(소프트 섀도, 해상도 등)은 나중 단계에서 다룬다고 생각하시면 됩니다.


5. 조명 변화 비교 씬 예제 코드

아래 예제는 동일한 씬에 라이트 4개를 만들어 두고, 버튼으로 어떤 라이트를 켤지 선택하는 데모입니다. 또한 Directional 라이트에 기본 그림자를 적용해, 그림자 ON/OFF도 확인할 수 있게 구성했습니다.

<!-- index.html (필요 부분만 예시) -->
<canvas id="renderCanvas" style="width:100%; height:420px;"></canvas>

<div style="margin-top:8px;">
  <button id="btnHemi">Hemispheric</button>
  <button id="btnDir">Directional</button>
  <button id="btnPoint">Point</button>
  <button id="btnSpot">Spot</button>
  <button id="btnShadow">Shadow ON/OFF</button>
</div>

<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script>
// JS 코드
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);

const createScene = function () {
  const scene = new BABYLON.Scene(engine);

  // 카메라 (ArcRotate, 박스 중심을 바라봄)
  const camera = new BABYLON.ArcRotateCamera(
    "camera",
    Math.PI / 4,
    Math.PI / 3,
    12,
    new BABYLON.Vector3(0, 1, 0),
    scene
  );
  camera.attachControl(canvas, true);

  // 기본 메쉬
  const ground = BABYLON.MeshBuilder.CreateGround("ground",
    { width: 20, height: 20 }, scene);
  const box = BABYLON.MeshBuilder.CreateBox("box",
    { size: 2 }, scene);
  box.position.y = 1;

  // 1) Hemispheric Light
  const hemiLight = new BABYLON.HemisphericLight(
    "hemiLight",
    new BABYLON.Vector3(0, 1, 0), // 위쪽에서 내려오는 빛
    scene
  );
  hemiLight.intensity = 0.9;

  // 2) Directional Light
  const dirLight = new BABYLON.DirectionalLight(
    "dirLight",
    new BABYLON.Vector3(-1, -2, 1),  // 왼쪽 위에서 오른쪽 아래로
    scene
  );
  dirLight.position = new BABYLON.Vector3(10, 10, -10);
  dirLight.intensity = 0.0; // 처음엔 꺼둠

  // 3) Point Light
  const pointLight = new BABYLON.PointLight(
    "pointLight",
    new BABYLON.Vector3(0, 5, -5),
    scene
  );
  pointLight.intensity = 0.0; // 처음엔 꺼둠

  // 4) Spot Light
  const spotLight = new BABYLON.SpotLight(
    "spotLight",
    new BABYLON.Vector3(0, 6, -6),             // 위치
    new BABYLON.Vector3(0, -1, 1),             // 방향
    Math.PI / 4,                               // 각도
    10,                                        // exponent
    scene
  );
  spotLight.intensity = 0.0; // 처음엔 꺼둠

  // Directional Light에 그림자 생성기 추가
  const shadowGenerator = new BABYLON.ShadowGenerator(1024, dirLight);
  shadowGenerator.useBlurExponentialShadowMap = true;
  shadowGenerator.blurKernel = 16;

  // 그림자를 만들 메쉬 (캐스터)
  shadowGenerator.getShadowMap().renderList.push(box);
  // 그림자를 받을 메쉬
  ground.receiveShadows = true;

  let shadowEnabled = true;

  // 라이트를 모두 꺼놓고 하나만 켜는 헬퍼
  function setActiveLight(type) {
    hemiLight.intensity = 0.0;
    dirLight.intensity = 0.0;
    pointLight.intensity = 0.0;
    spotLight.intensity = 0.0;

    switch (type) {
      case "hemi":
        hemiLight.intensity = 0.9;
        break;
      case "dir":
        dirLight.intensity = 1.0;
        break;
      case "point":
        pointLight.intensity = 1.0;
        break;
      case "spot":
        spotLight.intensity = 1.0;
        break;
    }
  }

  // 버튼 이벤트 연결
  document.getElementById("btnHemi").onclick = () => setActiveLight("hemi");
  document.getElementById("btnDir").onclick = () => setActiveLight("dir");
  document.getElementById("btnPoint").onclick = () => setActiveLight("point");
  document.getElementById("btnSpot").onclick = () => setActiveLight("spot");

  // Shadow ON/OFF
  document.getElementById("btnShadow").onclick = () => {
    shadowEnabled = !shadowEnabled;
    shadowGenerator.getShadowMap().renderList =
      shadowEnabled ? [box] : [];
  };

  // 시작은 Hemispheric Light
  setActiveLight("hemi");

  return scene;
};

const scene = createScene();

engine.runRenderLoop(() => {
  scene.render();
});

window.addEventListener("resize", () => {
  engine.resize();
});
</script>

이 예제로 다음을 직접 눈으로 확인하실 수 있습니다.

  • Hemispheric 라이트만 켰을 때와 다른 라이트를 켰을 때의 전체 분위기 차이
  • Directional / Point / Spot 라이트가 각각 어떤 방향과 범위로 빛을 뿌리는지
  • Shadow ON/OFF 버튼으로 그림자가 생겼을 때와 없을 때의 입체감 차이

6. 실습 단계: 직접 따라 하기

  1. 기본 프로젝트 준비
    index.html 파일을 하나 만들고, <canvas id="renderCanvas"> 와 Babylon.js CDN 스크립트를 포함합니다. (위 코드처럼)
  2. 씬과 카메라, 박스/바닥 만들기
    이전 1~3일차에서 했던 방식대로 엔진(BABYLON.Engine), 씬(BABYLON.Scene), ArcRotate 카메라, 바닥, 박스를 생성합니다.
  3. 라이트 4종을 차례로 추가
    • 먼저 Hemispheric 라이트를 추가해 씬 전체를 적당히 밝게 만듭니다.
    • 그 다음 Directional, Point, Spot 라이트를 추가하되, 처음에는 intensity = 0으로 꺼둡니다.
    • 버튼을 눌렀을 때만 해당 라이트의 intensity를 1로 켜고, 나머지는 0으로 꺼지게 처리합니다.
  4. 라이트 위치·방향·색 바꿔 보기
    • Point 라이트의 position.y를 2 → 8로 바꾸며, 그림 위치가 어떻게 변하는지 관찰해 보세요.
    • Spot 라이트의 angle 값을 Math.PI / 6Math.PI / 3 으로 바꿔 빔이 좁았다가 넓어지는 느낌을 확인합니다.
    • diffuse = new BABYLON.Color3(1, 0, 0) 처럼 색을 바꾸어 붉은 조명, 푸른 조명을 만들어 봅니다.
  5. 기본 그림자 켜고 끄기
    • Directional 라이트에 ShadowGenerator를 붙이고, 박스를 renderList에 추가합니다.
    • 버튼 클릭 시 renderList를 비우거나 다시 채우는 방식으로 그림자 ON/OFF를 구현합니다.
    • 그림자가 켜졌을 때가 훨씬 입체적으로 느껴지는지 눈으로 확인해 보세요.
  6. “왜 어둡지?”라는 질문에 답해 보기
    이제 실제 프로젝트에서 화면이 어둡게 보이면, 다음 체크리스트를 떠올려 보시면 됩니다.
    • 라이트가 최소 하나라도 있는가? (특히 Hemispheric 하나만 있어도 기본은 보입니다.)
    • 라이트의 intensity가 0에 가깝게 되어 있지는 않은가?
    • Point/Spot 라이트의 경우, 메쉬와 너무 멀리 떨어져 있지 않은가?
    • 머티리얼의 emissive가 0이고 diffuse 색이 너무 어둡지는 않은가?

7. 추가로 생각해볼 점

  • 실제 게임이나 서비스에서는 라이트 개수가 많을수록 성능에 영향을 줍니다. 기본 머티리얼은 한 번에 처리할 수 있는 라이트 수가 제한되어 있으므로(보통 4개), 핵심 라이트 위주로 사용하거나, 필요한 메쉬에만 특정 라이트를 연결하는 구조가 좋습니다.
  • 씬 전체 분위기는 Hemispheric + Directional 조합으로 잡고, Point/Spot은 “강조용”으로만 사용하는 패턴이 상당히 많이 쓰입니다.
  • 향후에는 Clustered Lighting 같이 더 고급 라이트 기술도 활용할 수 있는데, 이는 많은 수의 포인트/스팟 라이트를 효율적으로 처리하기 위한 기능입니다.


Reactions

댓글 쓰기

0 댓글