본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 29. · 0 Views
Three.js 카메라 및 조명 설정 완벽 가이드
3D 웹 개발에서 가장 중요한 카메라와 조명 설정을 실무 중심으로 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.
목차
1. 카메라 종류 및 선택 기준
신입 개발자 김민수 씨는 처음으로 3D 웹 게임 프로젝트를 맡게 되었습니다. 팀장님이 "카메라부터 설정해보세요"라고 하셨는데, Three.js에는 카메라 종류가 여러 개였습니다.
"어떤 걸 써야 하죠?"
Three.js의 카메라는 크게 PerspectiveCamera와 OrthographicCamera로 나뉩니다. 마치 사진을 찍을 때 일반 카메라와 건축 전문 카메라를 선택하는 것과 같습니다.
원근감이 필요한 게임이나 인터랙티브 콘텐츠에는 PerspectiveCamera를, 건축 시각화나 2D 게임에는 OrthographicCamera를 사용합니다.
다음 코드를 살펴봅시다.
// PerspectiveCamera 생성 - 가장 일반적인 선택
// 인자: FOV(시야각), 종횡비, near, far
const camera = new THREE.PerspectiveCamera(
75, // FOV: 넓을수록 광각
window.innerWidth / window.innerHeight, // 화면 비율
0.1, // near: 이것보다 가까우면 안 보임
1000 // far: 이것보다 멀면 안 보임
);
// 카메라 위치 설정
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0); // 원점을 바라봄
김민수 씨는 당황했습니다. 학교에서는 3D 이론만 배웠지, 실제 어떤 카메라를 써야 하는지는 배우지 못했습니다.
옆자리 박선배 개발자가 다가와 모니터를 보더니 웃으며 말했습니다. "처음엔 다 헷갈려요.
간단하게 설명해 드릴게요." 그렇다면 Three.js의 카메라란 정확히 무엇일까요? 쉽게 비유하자면, 카메라는 마치 영화 촬영 감독이 들고 있는 카메라와 같습니다.
감독이 어디에 서서 어떤 렌즈로 촬영하느냐에 따라 관객이 보는 화면이 완전히 달라집니다. Three.js에서도 카메라의 위치와 종류에 따라 사용자가 보는 3D 장면이 완전히 바뀝니다.
카메라 선택이 왜 중요한가? 잘못된 카메라를 선택하면 프로젝트 전체가 어색해집니다.
예를 들어 1인칭 슈팅 게임에 OrthographicCamera를 사용하면 깊이감이 없어 어색합니다. 반대로 건축 도면 뷰어에 PerspectiveCamera를 쓰면 선이 휘어져 보여 정확한 치수를 확인하기 어렵습니다.
가장 많이 사용하는 PerspectiveCamera부터 알아봅시다. PerspectiveCamera는 우리 눈으로 세상을 보는 것과 같은 방식입니다.
가까운 물체는 크게, 먼 물체는 작게 보입니다. 철로가 멀리 갈수록 좁아 보이는 것처럼 말이죠.
게임, 제품 뷰어, 인터랙티브 콘텐츠 대부분이 이 카메라를 사용합니다. 코드를 자세히 살펴보겠습니다.
첫 번째 인자인 **FOV(Field of View, 시야각)**는 75도입니다. 이 값이 클수록 광각 렌즈처럼 넓은 범위가 보이고, 작을수록 망원 렌즈처럼 좁은 범위가 보입니다.
일반적으로 60~90도 사이를 사용합니다. 두 번째 인자는 **종횡비(Aspect Ratio)**입니다.
보통 화면의 가로를 세로로 나눈 값을 사용합니다. 창 크기가 바뀔 때마다 이 값도 업데이트해야 합니다.
세 번째와 네 번째 인자는 near와 far 클리핑 평면입니다. near보다 가까운 물체와 far보다 먼 물체는 렌더링되지 않습니다.
이는 성능 최적화를 위해 중요합니다. near를 0으로 설정하면 카메라 내부 계산에 문제가 생기므로 항상 0보다 큰 값을 사용해야 합니다.
그렇다면 OrthographicCamera는 언제 사용할까요? OrthographicCamera는 원근감이 없는 카메라입니다.
마치 건축 도면처럼 거리에 상관없이 모든 물체가 같은 크기로 보입니다. 2D 게임, 건축 시각화, 데이터 시각화에 적합합니다.
미니맵을 구현할 때도 자주 사용됩니다. 실무 활용 사례를 살펴봅시다.
어떤 가구 쇼핑몰에서 3D 제품 뷰어를 만든다고 가정해봅시다. 사용자가 소파를 360도 돌려보며 확인할 수 있어야 합니다.
이 경우 PerspectiveCamera를 사용해 자연스러운 원근감을 제공합니다. 반면 같은 쇼핑몰에서 방 평면도를 보여줄 때는 OrthographicCamera를 사용해 정확한 비율을 유지합니다.
박선배가 조언을 덧붙였습니다. "초보자들이 자주 하는 실수가 있어요.
near 값을 너무 크게 설정해서 가까운 물체가 안 보이거나, far 값을 너무 작게 설정해서 멀리 있는 배경이 갑자기 사라지는 경우죠. 처음엔 near는 0.1, far는 1000 정도로 시작하고, 필요에 따라 조정하세요." 김민수 씨는 이제 이해했다는 표정을 지었습니다.
카메라 선택은 프로젝트의 첫 단추입니다. 게임이나 인터랙티브 콘텐츠라면 PerspectiveCamera를, 정확한 비율이 중요한 도면이나 2D 게임이라면 OrthographicCamera를 선택하세요.
여러분의 프로젝트 성격에 맞는 카메라를 선택하는 것이 성공적인 3D 웹 개발의 시작입니다.
실전 팁
💡 - FOV는 60~90도 사이가 자연스럽습니다. 75도가 가장 일반적입니다.
- near는 절대 0으로 설정하지 마세요. 0.1 이상을 권장합니다.
- 창 크기 변경 시 카메라의 aspect도 함께 업데이트해야 왜곡이 없습니다.
2. 카메라가 플레이어를 따라가게 만들기
김민수 씨가 캐릭터를 움직이는 코드를 완성했습니다. 그런데 캐릭터가 화면 밖으로 사라져 버렸습니다.
"카메라가 따라오지 않네요?" 박선배가 웃으며 말했습니다. "카메라를 플레이어에 연결해야죠."
카메라가 플레이어를 따라가게 하려면 매 프레임마다 카메라의 위치를 플레이어 위치에 맞춰 업데이트해야 합니다. 마치 카메라맨이 배우를 쫓아가며 촬영하는 것과 같습니다.
Three.js에서는 animation loop 안에서 카메라의 position을 플레이어의 position에 연동시킵니다.
다음 코드를 살펴봅시다.
// 플레이어 객체 (예: 큐브)
const player = new THREE.Mesh(
new THREE.BoxGeometry(1, 2, 1),
new THREE.MeshStandardMaterial({ color: 0x00ff00 })
);
scene.add(player);
// 애니메이션 루프
function animate() {
requestAnimationFrame(animate);
// 플레이어가 움직임 (예: 키보드 입력 처리 후)
// player.position.x += 0.1;
// 카메라가 플레이어를 따라감
camera.position.x = player.position.x;
camera.position.y = player.position.y + 3; // 플레이어 위 3유닛
camera.position.z = player.position.z + 5; // 플레이어 뒤 5유닛
camera.lookAt(player.position); // 플레이어를 바라봄
renderer.render(scene, camera);
}
김민수 씨는 캐릭터가 화면 밖으로 사라지는 모습을 보며 당황했습니다. 분명히 움직임 코드는 제대로 작성했는데, 캐릭터만 움직이고 카메라는 그대로였습니다.
마치 무대 위의 배우가 움직이는데 카메라는 고정되어 있는 상황과 같았습니다. 박선배가 설명을 시작했습니다.
"3D 게임에서 카메라는 저절로 움직이지 않아요. 우리가 명시적으로 움직여줘야 합니다.
영화 촬영 현장을 생각해보세요. 배우가 걸어가면 카메라맨도 함께 움직여야 배우가 프레임 안에 있죠." 카메라 추적의 핵심 원리는 무엇일까요?
간단합니다. 플레이어가 움직일 때마다 카메라도 같은 방향으로 같은 거리만큼 움직이면 됩니다.
마치 플레이어 뒤에 보이지 않는 실로 카메라가 연결되어 있다고 상상하면 쉽습니다. 플레이어가 앞으로 가면 카메라도 앞으로, 옆으로 가면 카메라도 옆으로 따라갑니다.
왜 매 프레임마다 업데이트해야 할까요? Three.js의 렌더링은 requestAnimationFrame을 사용한 루프 방식입니다.
보통 초당 60번 화면을 그립니다. 플레이어의 위치가 변할 때마다 카메라도 함께 업데이트하지 않으면, 플레이어는 계속 움직이는데 카메라는 원래 자리에 그대로 있게 됩니다.
코드의 핵심 부분을 살펴봅시다. camera.position.x = player.position.x 이 부분은 카메라의 x축 위치를 플레이어와 동일하게 맞춥니다.
그런데 y축과 z축을 보면 단순히 같은 값이 아니라 +3, +5를 더했습니다. 왜 그럴까요?
만약 카메라와 플레이어의 위치가 완전히 같다면 어떻게 될까요? 카메라가 플레이어 내부에 들어가 아무것도 보이지 않습니다.
따라서 카메라는 플레이어보다 약간 위쪽과 뒤쪽에 위치해야 합니다. +3은 위로 3유닛, +5는 뒤로 5유닛 떨어진 위치를 의미합니다.
**camera.lookAt(player.position)**은 카메라가 항상 플레이어를 바라보도록 합니다. lookAt 메서드는 카메라의 방향을 특정 좌표로 향하게 합니다.
플레이어가 좌우로 움직여도 카메라는 항상 플레이어 중심을 바라봅니다. 마치 카메라맨이 배우의 얼굴을 계속 렌즈 중앙에 두려고 노력하는 것과 같습니다.
실무에서는 어떻게 활용될까요? 모바일 러닝 게임을 만든다고 가정해봅시다.
캐릭터가 끊임없이 앞으로 달립니다. 카메라는 캐릭터 뒤에서 일정한 거리를 유지하며 따라갑니다.
이때 위의 코드 패턴을 사용하면 자연스러운 추격 카메라를 구현할 수 있습니다. 서브웨이 서퍼즈 같은 게임이 이런 방식을 사용합니다.
주의할 점이 있습니다. 카메라가 너무 가까우면 플레이어가 화면을 가득 채워 주변 환경을 보기 어렵습니다.
반대로 너무 멀면 플레이어가 작아져 몰입감이 떨어집니다. 적절한 거리는 게임 장르와 캐릭터 크기에 따라 다릅니다.
여러 값을 테스트하며 최적의 거리를 찾아야 합니다. 또 다른 흔한 실수는 lookAt을 빼먹는 것입니다.
카메라 위치만 업데이트하고 lookAt을 호출하지 않으면 카메라가 엉뚱한 방향을 바라봅니다. 특히 플레이어가 회전할 때 이 문제가 두드러집니다.
항상 위치 업데이트와 lookAt을 함께 사용해야 합니다. 김민수 씨가 코드를 적용하고 테스트했습니다.
이제 캐릭터가 움직이면 카메라가 자연스럽게 따라왔습니다. "오, 진짜 게임 같네요!" 김민수 씨는 신이 나서 캐릭터를 이리저리 움직여 봤습니다.
박선배가 웃으며 말했습니다. "이제 3인칭 카메라도 만들어볼까요?" 카메라 추적은 3D 게임의 기본 중의 기본입니다.
이 패턴을 이해하면 다양한 카메라 움직임을 구현할 수 있습니다. 여러분의 게임에서도 이 방식을 활용해 자연스러운 카메라 워크를 만들어 보세요.
실전 팁
💡 - 카메라 오프셋(+3, +5)은 게임 장르에 따라 조정하세요. 액션 게임은 가깝게, 전략 게임은 멀게.
- lookAt을 매 프레임 호출하면 성능에 영향을 줄 수 있으니, 필요한 경우에만 호출하도록 최적화할 수 있습니다.
- 카메라가 갑자기 움직이지 않도록 lerp(선형 보간)를 사용해 부드럽게 이동시킬 수 있습니다.
3. 3인칭 카메라 구현
김민수 씨의 게임에서 캐릭터가 좌우로 움직일 때 카메라가 딱딱하게 따라갔습니다. 박선배가 말했습니다.
"지금은 카메라가 기계처럼 움직이네요. 부드러운 3인칭 카메라를 만들어 봅시다."
3인칭 카메라는 플레이어를 바라보는 시점으로, 플레이어 뒤쪽에서 일정 거리를 유지합니다. 핵심은 **선형 보간(lerp)**을 사용해 카메라가 부드럽게 따라오도록 하는 것입니다.
마치 숙련된 카메라맨이 배우를 자연스럽게 추적하는 것처럼 부드러운 움직임을 구현합니다.
다음 코드를 살펴봅시다.
// 3인칭 카메라 설정
const cameraOffset = new THREE.Vector3(0, 5, 10); // 카메라 오프셋
const cameraLookAtOffset = new THREE.Vector3(0, 1, 0); // 바라볼 위치 오프셋
const lerpFactor = 0.1; // 부드러움 정도 (0~1, 작을수록 부드럽고 느림)
function animate() {
requestAnimationFrame(animate);
// 목표 카메라 위치 계산
const targetPosition = new THREE.Vector3()
.copy(player.position)
.add(cameraOffset);
// 부드럽게 이동 (lerp)
camera.position.lerp(targetPosition, lerpFactor);
// 플레이어 약간 위를 바라봄
const lookAtTarget = new THREE.Vector3()
.copy(player.position)
.add(cameraLookAtOffset);
camera.lookAt(lookAtTarget);
renderer.render(scene, camera);
}
김민수 씨는 카메라가 따라오긴 하지만 뭔가 어색하다고 느꼈습니다. 캐릭터가 갑자기 방향을 바꾸면 카메라도 똑같이 갑자기 움직였습니다.
AAA 게임처럼 부드럽지 않았습니다. 박선배가 화면을 보더니 고개를 끄덕였습니다.
"예상했어요. 지금은 카메라가 플레이어 위치를 그대로 복사하고 있어서 로봇처럼 움직입니다.
실제 게임에서는 카메라가 약간 지연되면서 부드럽게 따라오죠. 그게 바로 lerp입니다." lerp란 무엇일까요?
**lerp(Linear Interpolation, 선형 보간)**는 두 값 사이를 부드럽게 이동하는 기법입니다. 예를 들어 현재 위치가 0이고 목표 위치가 10일 때, 한 번에 10으로 점프하는 대신 1, 2, 3...
이렇게 조금씩 이동합니다. 마치 자동차가 급발진하지 않고 서서히 가속하는 것과 같습니다.
Three.js의 Vector3에는 lerp 메서드가 내장되어 있습니다. **camera.position.lerp(targetPosition, lerpFactor)**는 현재 카메라 위치에서 목표 위치로 lerpFactor만큼 이동합니다.
lerpFactor가 0.1이면 거리의 10%만큼 이동하고, 0.5면 50%만큼 이동합니다. 매 프레임마다 조금씩 이동하므로 부드러운 움직임이 만들어집니다.
lerpFactor 값에 따라 느낌이 완전히 달라집니다. 0.05처럼 작은 값을 사용하면 카메라가 천천히 따라옵니다.
시네마틱한 느낌이 나지만 반응이 느릴 수 있습니다. 0.2처럼 큰 값을 사용하면 빠르게 따라붙지만 부드러움이 줄어듭니다.
보통 0.1~0.15 사이가 적당합니다. 코드를 단계별로 분석해봅시다.
먼저 cameraOffset은 플레이어로부터 카메라까지의 상대적 위치입니다. (0, 5, 10)은 플레이어 위로 5유닛, 뒤로 10유닛 떨어진 위치를 의미합니다.
이 값을 조정하면 카메라 각도를 바꿀 수 있습니다. targetPosition은 카메라가 이동해야 할 목표 위치입니다.
플레이어의 현재 위치에 offset을 더해서 계산합니다. 플레이어가 움직이면 목표 위치도 함께 움직입니다.
**camera.position.lerp(targetPosition, lerpFactor)**가 핵심입니다. 카메라는 한 번에 목표 위치로 이동하지 않고, 매 프레임마다 10%씩 가까워집니다.
이렇게 하면 플레이어가 급정거해도 카메라는 부드럽게 멈춥니다. cameraLookAtOffset은 플레이어의 발이 아닌 약간 위(예: 허리 높이)를 바라보도록 합니다.
플레이어의 정확한 위치를 바라보면 카메라가 너무 아래를 향합니다. (0, 1, 0)을 더하면 플레이어보다 1유닛 위를 바라보므로 더 자연스러운 구도가 됩니다.
실무 사례를 살펴봅시다. 언차티드, 툼레이더 같은 3인칭 액션 어드벤처 게임을 생각해보세요.
주인공이 달리다가 갑자기 멈출 때 카메라가 자연스럽게 감속하며 멈춥니다. 이것이 바로 lerp를 활용한 결과입니다.
만약 lerp 없이 직접 위치를 복사했다면 카메라가 딱딱하게 멈춰 어지러움을 유발했을 것입니다. 초보자들이 자주 하는 실수가 있습니다.
lerpFactor를 1로 설정하면 lerp의 의미가 없어집니다. 100% 이동이므로 즉시 목표 위치로 점프합니다.
또한 lerpFactor를 프레임 레이트와 무관하게 설정하면 컴퓨터마다 속도가 다를 수 있습니다. 더 정교한 구현을 위해서는 deltaTime(프레임 간 시간 차이)을 곱해야 합니다.
박선배가 추가 설명을 했습니다. "게임 장르에 따라 카메라 느낌을 다르게 가져가야 해요.
FPS 게임처럼 빠른 반응이 필요하면 lerpFactor를 크게, RPG처럼 여유로운 게임이면 작게 설정하세요. 테스트 플레이를 통해 최적의 값을 찾는 게 중요합니다." 김민수 씨가 코드를 적용하자 카메라가 훨씬 부드러워졌습니다.
이제 캐릭터가 점프하거나 급회전해도 카메라가 자연스럽게 따라왔습니다. "와, 진짜 게임 같아졌어요!" 김민수 씨는 만족스러운 표정을 지었습니다.
3인칭 카메라는 플레이어 경험에 큰 영향을 줍니다. lerp를 활용한 부드러운 카메라 움직임은 게임의 완성도를 크게 높여줍니다.
여러분의 프로젝트에도 적용해 보세요.
실전 팁
💡 - lerpFactor는 0.1~0.15 사이에서 시작해 게임 느낌에 맞게 조정하세요.
- 프레임 레이트에 독립적인 움직임을 위해 deltaTime을 곱하는 것을 고려하세요.
- 카메라가 벽을 뚫고 들어가지 않도록 충돌 감지를 추가하면 더 완성도 높은 카메라가 됩니다.
4. 조명 종류 및 활용법
김민수 씨의 3D 씬이 완성되어 가는데, 뭔가 밋밋했습니다. 모든 것이 똑같은 밝기로 보였습니다.
박선배가 말했습니다. "조명이 없어서 그래요.
조명은 3D의 생명입니다."
Three.js의 조명에는 AmbientLight, DirectionalLight, PointLight, SpotLight 등이 있습니다. 마치 사진 스튜디오에서 다양한 조명 장비를 사용하는 것처럼, 각 조명은 고유한 특성과 용도가 있습니다.
AmbientLight는 전체적인 밝기를, DirectionalLight는 태양광을, PointLight는 전구를, SpotLight는 무대 조명을 재현합니다.
다음 코드를 살펴봅시다.
// 1. 전체 기본 밝기 (그림자 없음)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);
// 2. 방향성 조명 - 태양광 (그림자 가능)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true; // 그림자 생성
scene.add(directionalLight);
// 3. 점 조명 - 전구 (모든 방향으로 빛 발산)
const pointLight = new THREE.PointLight(0xff6600, 1, 50);
pointLight.position.set(0, 5, 0);
scene.add(pointLight);
// 4. 스포트라이트 - 원뿔 모양 조명
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(10, 10, 10);
spotLight.angle = Math.PI / 6; // 30도 각도
scene.add(spotLight);
김민수 씨는 모델과 카메라는 완벽한데 왜 결과물이 밋밋한지 이해할 수 없었습니다. 모든 면이 똑같은 색으로 보여서 입체감이 전혀 없었습니다.
마치 흑백 사진처럼 느껴졌습니다. 박선배가 설명을 시작했습니다.
"3D 그래픽에서 조명이 없으면 물체가 제대로 보이지 않습니다. 실제 세계를 생각해보세요.
완전히 어두운 방에서는 아무것도 보이지 않죠. 빛이 있어야 물체의 형태와 색이 보입니다.
Three.js도 마찬가지예요." 조명은 왜 이렇게 중요할까요? 조명은 단순히 밝기만 조절하는 게 아닙니다.
물체의 입체감, 분위기, 감정까지 전달합니다. 공포 게임의 어두운 복도와 밝은 RPG 마을의 차이는 대부분 조명에서 나옵니다.
같은 3D 모델이라도 조명이 다르면 완전히 다른 느낌을 줍니다. **AmbientLight(환경광)**부터 알아봅시다.
AmbientLight는 모든 방향에서 균일하게 비추는 빛입니다. 마치 흐린 날의 하늘빛처럼 그림자 없이 전체를 은은하게 밝힙니다.
단독으로 사용하면 밋밋하지만, 다른 조명과 함께 사용하면 너무 어두운 그림자를 부드럽게 만들어줍니다. 보통 0.2~0.4 정도의 낮은 강도로 사용합니다.
**DirectionalLight(방향광)**는 가장 자주 사용됩니다. 태양광을 재현하는 조명으로, 특정 방향에서 평행하게 빛이 들어옵니다.
위치는 빛의 방향만 결정하고 거리는 영향을 주지 않습니다. 야외 씬에서 필수적이며, castShadow를 true로 설정하면 사실적인 그림자를 만들 수 있습니다.
게임 월드의 낮/밤 사이클을 구현할 때도 이 조명을 사용합니다. **PointLight(점광)**는 전구나 횃불을 표현합니다.
한 점에서 모든 방향으로 빛이 퍼져나갑니다. 세 번째 인자는 빛이 도달하는 최대 거리입니다.
50이면 50유닛 밖에서는 빛이 닿지 않습니다. 던전의 횃불, 가로등, 폭발 효과 등에 활용됩니다.
색상을 0xff6600(주황색)으로 설정하면 따뜻한 불빛을 연출할 수 있습니다. **SpotLight(스포트라이트)**는 무대 조명 같은 효과를 냅니다.
원뿔 모양으로 빛이 퍼지며, angle로 원뿔의 각도를 조절합니다. Math.PI / 6은 약 30도입니다.
무대, 자동차 헤드라이트, 손전등 등을 표현할 때 사용합니다. penumbra 속성으로 가장자리의 부드러움도 조절할 수 있습니다.
실제 프로젝트에서는 어떻게 조합할까요? 실내 씬을 만든다고 가정해봅시다.
먼저 AmbientLight로 기본 밝기를 설정합니다. 그 다음 창문에서 들어오는 햇빛을 DirectionalLight로 표현하고, 천장의 전구들을 PointLight로 배치합니다.
책상 스탠드는 SpotLight로 구현합니다. 이렇게 여러 조명을 조합하면 사실적인 실내 환경이 완성됩니다.
주의할 점이 있습니다. 조명이 많을수록 성능이 떨어집니다.
특히 그림자를 생성하는 조명은 계산 비용이 큽니다. 모바일이나 저사양 기기를 타겟으로 한다면 조명 개수를 3~5개로 제한하는 것이 좋습니다.
불필요한 조명은 과감히 제거하세요. 또 다른 실수는 AmbientLight만 사용하는 것입니다.
AmbientLight만 있으면 모든 면이 똑같은 밝기로 보여 입체감이 사라집니다. 반드시 DirectionalLight나 PointLight 같은 방향성 조명을 함께 사용해야 음영이 생겨 입체감이 살아납니다.
김민수 씨가 조명을 추가하자 세계가 살아났습니다. 같은 모델인데도 조명을 추가하니 완전히 다른 느낌이 났습니다.
그림자가 생기고 하이라이트가 반짝이며 진짜 3D처럼 보였습니다. "조명이 이렇게 중요한 줄 몰랐어요!" 김민수 씨는 감탄했습니다.
조명은 3D 씬의 품질을 결정하는 핵심 요소입니다. 각 조명의 특성을 이해하고 적절히 조합하면 AAA 게임 같은 비주얼을 만들 수 있습니다.
여러분의 씬에 생명을 불어넣어 보세요.
실전 팁
💡 - 조명은 최소한으로 유지하되, AmbientLight + DirectionalLight 조합은 거의 항상 사용됩니다.
- 그림자는 성능에 영향을 주므로 꼭 필요한 조명에만 castShadow를 설정하세요.
- 조명의 색상을 활용하면 분위기를 크게 바꿀 수 있습니다. 따뜻한 주황색, 차가운 파란색 등을 실험해보세요.
5. 조명 업데이트 및 동적 제어
김민수 씨가 낮/밤 사이클을 구현하고 싶었습니다. "조명을 시간에 따라 바꿀 수 있나요?" 박선배가 웃으며 답했습니다.
"당연하죠. 조명도 실시간으로 제어할 수 있습니다."
Three.js의 조명은 런타임에 자유롭게 조절할 수 있습니다. 밝기(intensity), 색상(color), 위치(position) 모두 실시간으로 변경 가능합니다.
마치 디제이가 무대 조명을 조작하는 것처럼, 게임이나 인터랙티브 콘텐츠에서 조명을 동적으로 제어해 분위기를 바꿀 수 있습니다.
다음 코드를 살펴봅시다.
// 조명 객체 생성
const sunLight = new THREE.DirectionalLight(0xffffff, 1);
sunLight.position.set(10, 10, 10);
scene.add(sunLight);
let timeOfDay = 0; // 0 = 밤, 1 = 낮
function animate() {
requestAnimationFrame(animate);
// 시간 경과 (천천히 증가)
timeOfDay += 0.001;
if (timeOfDay > 1) timeOfDay = 0;
// 낮/밤에 따라 조명 강도 조절
sunLight.intensity = timeOfDay; // 0~1 사이 값
// 색상도 변경 (노란색 -> 주황색 -> 파란색)
if (timeOfDay < 0.5) {
// 밤 -> 새벽: 파란색 -> 노란색
sunLight.color.setHex(0x0066ff).lerp(
new THREE.Color(0xffff00),
timeOfDay * 2
);
} else {
// 낮 -> 저녁: 노란색 -> 주황색
sunLight.color.setHex(0xffff00).lerp(
new THREE.Color(0xff6600),
(timeOfDay - 0.5) * 2
);
}
renderer.render(scene, camera);
}
김민수 씨는 마인크래프트처럼 시간이 지나면서 하늘이 어두워지는 효과를 만들고 싶었습니다. 하지만 어떻게 시작해야 할지 막막했습니다.
조명을 한 번 만들면 고정되는 줄 알았기 때문입니다. 박선배가 설명했습니다.
"조명은 정적인 게 아니에요. 모든 속성을 실시간으로 바꿀 수 있습니다.
마치 실제 영화 촬영장에서 조명 감독이 조명의 밝기와 색을 조절하는 것처럼요." 동적 조명 제어의 핵심은 무엇일까요? 간단합니다.
조명 객체의 속성을 애니메이션 루프 안에서 수정하면 됩니다. intensity는 밝기, color는 색상, position은 위치를 제어합니다.
매 프레임마다 이 값들을 바꾸면 조명이 움직이거나 깜빡이거나 색이 변합니다. intensity는 조명의 강도를 조절합니다.
0이면 완전히 꺼지고, 1은 기본 밝기, 2는 두 배 밝기입니다. 위 코드에서는 timeOfDay 값(0~1)을 그대로 intensity에 할당해 밤에는 어둡고 낮에는 밝게 만듭니다.
이렇게 하면 자연스러운 낮/밤 전환이 일어납니다. 색상 변경은 어떻게 할까요?
**color.setHex()**로 색상을 설정하고, **lerp()**로 부드럽게 전환합니다. 예를 들어 밤(파란색 0x0066ff)에서 낮(노란색 0xffff00)으로 변할 때, 중간 과정에서 보라색, 연한 파란색, 흰색을 거칩니다.
lerp는 두 색상 사이를 자연스럽게 보간합니다. 코드를 자세히 살펴봅시다.
timeOfDay는 0부터 1까지 천천히 증가합니다. 0.001씩 증가하므로 1000 프레임(약 16초, 60fps 기준)에 걸쳐 하루가 지나갑니다.
실제 게임에서는 이 속도를 조절해 원하는 낮/밤 주기를 만들 수 있습니다. 0.5보다 작으면 밤에서 낮으로, 0.5보다 크면 낮에서 밤으로 전환됩니다.
밤에서 낮으로 갈 때는 파란색에서 노란색으로 변합니다. timeOfDay가 0일 때는 완전히 파란색, 0.5일 때는 완전히 노란색입니다.
timeOfDay * 2를 하는 이유는 00.5 범위를 01로 정규화하기 위함입니다. 실무에서는 어떻게 활용될까요?
오픈 월드 RPG 게임을 생각해봅시다. 플레이어가 모험을 하는 동안 시간이 흐릅니다.
낮에는 밝고 따뜻한 노란 빛이, 저녁에는 붉은 석양이, 밤에는 차가운 푸른 달빛이 비춥니다. 이 모든 것을 위의 패턴으로 구현할 수 있습니다.
젤다의 전설, 스카이림 같은 게임들이 이런 기법을 사용합니다. 다른 활용 사례도 많습니다.
공포 게임에서 번개가 칠 때 순간적으로 조명이 밝아졌다 어두워지는 효과, 클럽 씬에서 조명이 리듬에 맞춰 깜빡이는 효과, 횃불이 바람에 흔들리며 깜빡이는 효과 모두 조명의 동적 제어로 만듭니다. 주의할 점이 있습니다.
조명을 너무 자주 변경하면 성능에 영향을 줄 수 있습니다. 특히 그림자를 생성하는 조명은 더욱 그렇습니다.
그림자 맵을 매 프레임 다시 계산해야 하기 때문입니다. 필요한 경우에만 그림자를 업데이트하거나, 그림자 품질을 낮추는 최적화가 필요할 수 있습니다.
또한 색상 변화가 너무 급격하면 눈이 피로할 수 있습니다. lerp를 사용해 부드럽게 전환하고, 변화 속도를 적절히 조절해야 합니다.
특히 색약이나 색맹 사용자를 고려해 너무 극단적인 색상 변화는 피하는 것이 좋습니다. 김민수 씨가 코드를 실행하자 씬이 살아 움직였습니다.
해가 뜨고 지는 것처럼 조명이 변했습니다. "와, 진짜 시간이 흐르는 것 같아요!" 김민수 씨는 신기해하며 화면을 지켜봤습니다.
박선배가 미소 지으며 말했습니다. "이제 마지막으로 분위기 연출 기법을 배워볼까요?" 동적 조명은 정적인 3D 씬에 생명을 불어넣습니다.
시간, 날씨, 이벤트에 따라 조명을 변화시켜 플레이어에게 더 몰입감 있는 경험을 제공하세요.
실전 팁
💡 - 낮/밤 사이클의 속도는 게임 장르에 맞게 조절하세요. RPG는 느리게, 서바이벌은 빠르게.
- 색상 변화에 lerp를 사용하면 부드럽고 자연스러운 전환이 가능합니다.
- 조명 변화를 게임 이벤트와 연동하면 더 드라마틱한 연출이 가능합니다.
6. 분위기 연출을 위한 라이팅 기법
김민수 씨의 씬이 기술적으로는 완성되었지만, 뭔가 감성이 부족했습니다. 박선배가 말했습니다.
"이제 예술적인 부분을 배울 차례입니다. 라이팅은 과학이자 예술이에요."
라이팅 기법은 단순히 밝게 만드는 것이 아니라 감정과 분위기를 전달하는 예술입니다. 삼점 조명(Three-Point Lighting), 림 라이팅(Rim Lighting), 컬러 그레이딩 같은 기법을 활용하면 영화 같은 비주얼을 만들 수 있습니다.
마치 화가가 빛과 그림자로 그림을 그리듯, 조명으로 감정을 표현합니다.
다음 코드를 살펴봅시다.
// 삼점 조명 설정 (영화/사진 촬영 기법)
// 1. 키 라이트 (주 조명) - 가장 밝음
const keyLight = new THREE.DirectionalLight(0xffffff, 1);
keyLight.position.set(5, 5, 5);
scene.add(keyLight);
// 2. 필 라이트 (보조 조명) - 그림자를 부드럽게
const fillLight = new THREE.DirectionalLight(0x8888ff, 0.4);
fillLight.position.set(-5, 0, 2);
scene.add(fillLight);
// 3. 백 라이트 (윤곽 조명) - 피사체를 배경에서 분리
const backLight = new THREE.DirectionalLight(0xffaa00, 0.6);
backLight.position.set(0, 3, -5);
scene.add(backLight);
// 분위기를 위한 앰비언트 라이트
const ambientLight = new THREE.AmbientLight(0x404040, 0.2);
scene.add(ambientLight);
// 포그로 깊이감 추가
scene.fog = new THREE.Fog(0x000000, 10, 50);
김민수 씨는 기술적으로는 완벽한 씬을 만들었지만, AAA 게임이나 영화 같은 느낌은 나지 않았습니다. 뭔가 2% 부족했습니다.
박선배가 화면을 보더니 고개를 끄덕였습니다. "기술은 완벽해요.
이제 예술적 감각을 더할 차례입니다. 영화를 보면 단순히 밝기만 조절하는 게 아니라 빛으로 감정을 표현하죠.
게임도 마찬가지예요." 삼점 조명이란 무엇일까요? **삼점 조명(Three-Point Lighting)**은 할리우드 영화 촬영에서 기본으로 사용하는 조명 기법입니다.
세 개의 조명을 서로 다른 각도와 강도로 배치해 입체감과 분위기를 만듭니다. 마치 조각가가 조각에 빛을 비춰 형태를 드러내는 것과 같습니다.
**키 라이트(Key Light)**는 주연 배우를 비추는 메인 조명입니다. 가장 밝고 강한 조명으로, 피사체의 주요 형태와 색을 드러냅니다.
보통 45도 각도에서 비춥니다. 위 코드에서는 (5, 5, 5) 위치에 배치했습니다.
이 조명만 있으면 한쪽은 밝고 한쪽은 너무 어두워 부자연스럽습니다. **필 라이트(Fill Light)**는 그림자를 부드럽게 만듭니다.
키 라이트의 반대편에서 약한 빛을 비춰 어두운 부분을 은은하게 밝힙니다. 강도는 키 라이트의 30~50% 정도로 설정합니다.
위 코드에서는 0.4로 설정했고, 약간 푸른색(0x8888ff)을 섞어 차가운 느낌을 더했습니다. **백 라이트(Back Light)**는 피사체와 배경을 분리합니다.
피사체 뒤에서 비춰 윤곽을 밝게 만듭니다. 이렇게 하면 캐릭터가 배경에 묻히지 않고 또렷하게 보입니다.
주황색(0xffaa00)을 사용하면 따뜻한 햇빛이 뒤에서 비추는 것 같은 효과를 냅니다. 게임에서 주인공을 돋보이게 하는 핵심 기법입니다.
컬러 그레이딩은 어떻게 활용할까요? 각 조명에 다른 색상을 사용하면 극적인 분위기를 만들 수 있습니다.
예를 들어 공포 게임에서는 키 라이트를 차가운 파란색, 백 라이트를 불길한 빨간색으로 설정합니다. 판타지 게임에서는 따뜻한 금색과 보라색을 조합합니다.
색상 대비가 클수록 드라마틱합니다. **Fog(안개)**는 깊이감과 분위기를 더합니다.
Fog는 거리에 따라 색이 흐려지는 효과입니다. 10유닛부터 시작해 50유닛에서 완전히 안개에 묻힙니다.
공포 게임의 안개 낀 숲, SF 게임의 먼지 낀 우주선 내부, 판타지 게임의 신비로운 숲 등을 표현할 때 필수입니다. 실제 게임에서는 어떻게 적용될까요?
라스트 오브 어스를 생각해봅시다. 폐허가 된 도시를 탐험할 때 창문으로 들어오는 햇빛(키 라이트), 반대편의 은은한 푸른 빛(필 라이트), 먼지 속 백 라이트가 캐릭터를 감쌉니다.
이런 조명 설계가 게임의 감성을 만듭니다. 또 다른 예로 사이버펑크 장르를 봅시다.
네온사인이 가득한 미래 도시에서는 여러 PointLight를 다양한 색상(분홍, 파랑, 초록)으로 배치합니다. 각 조명이 서로 겹치며 화려하고 혼란스러운 분위기를 만듭니다.
블레이드 러너 같은 비주얼이 탄생합니다. 초보자들이 놓치기 쉬운 포인트가 있습니다.
조명을 너무 많이 추가하면 오히려 밋밋해집니다. 어둠도 중요합니다.
어두운 부분이 있어야 밝은 부분이 돋보입니다. 명암 대비를 두려워하지 마세요.
완전히 검은 그림자가 있어도 괜찮습니다. 또한 모든 조명을 흰색으로 하지 마세요.
현실 세계에서도 햇빛은 약간 노랗고, 그늘은 약간 푸릅니다. 색온도 차이를 활용하면 훨씬 사실적이고 감성적인 화면을 만들 수 있습니다.
따뜻한 색(주황, 노랑)과 차가운 색(파랑, 보라)을 대비시키는 것이 핵심입니다. 박선배가 마지막 조언을 했습니다.
"라이팅은 정답이 없어요. 영화 포스터, 게임 스크린샷을 많이 보고 분석하세요.
왜 저 조명이 저 위치에 있는지, 왜 저 색을 썼는지 생각해보세요. 그리고 직접 실험하면서 자신만의 스타일을 찾으세요." 김민수 씨가 삼점 조명을 적용하자 씬이 완전히 달라졌습니다.
같은 모델인데도 마치 영화 한 장면 같았습니다. 캐릭터의 윤곽이 빛나고, 그림자는 부드러웠으며, 전체적으로 영화적인 분위기가 났습니다.
"이제 제대로 된 게임 같아요!" 김민수 씨는 뿌듯해했습니다. 라이팅은 3D 아티스트와 개발자의 가장 강력한 도구입니다.
같은 모델과 텍스처라도 조명 하나로 평범한 씬이 걸작이 될 수 있습니다. 여러분도 다양한 조명 기법을 실험하며 자신만의 비주얼 스타일을 만들어 보세요.
실전 팁
💡 - 삼점 조명은 캐릭터, 제품 뷰어, 중요한 오브젝트에 적용하면 극적인 효과를 냅니다.
- 색온도 대비(따뜻한 색 vs 차가운 색)를 활용하면 영화 같은 느낌을 쉽게 만들 수 있습니다.
- 어둠을 두려워하지 마세요. 명암 대비가 강할수록 드라마틱합니다.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Three.js 충돌 감지 완벽 가이드
3D 웹 게임에서 캐릭터가 벽을 뚫고 지나가는 문제를 해결하는 충돌 감지 기술을 배웁니다. Bounding Box부터 물리 엔진까지 실전 예제로 완벽하게 마스터해보세요.
Three.js Raycaster 상호작용 구현 완벽 가이드
Three.js의 Raycaster를 활용하여 3D 객체와의 상호작용을 구현하는 방법을 초급자 눈높이에서 설명합니다. 마우스 클릭, 터치 이벤트, 하이라이트 효과까지 실전 예제와 함께 배워봅니다.
GSAP 애니메이션 라이브러리 완벽 가이드
웹 애니메이션의 표준, GSAP를 처음부터 제대로 배워봅니다. 기본 사용법부터 Timeline, Easing, Three.js 통합까지 실무에서 바로 쓸 수 있는 핵심 내용을 다룹니다.
Three.js 모델 불러오기 완벽 가이드
Blender에서 만든 3D 모델을 웹 브라우저로 불러오는 방법을 배웁니다. GLTFLoader 사용법부터 텍스처 처리, 성능 최적화까지 실무에서 바로 쓸 수 있는 완전한 가이드입니다.
Blender 내보내기 최적화 완벽 가이드
Blender에서 Three.js로 3D 모델을 내보낼 때 반드시 알아야 할 최적화 기법을 다룹니다. 노멀 수정부터 경량화, 포맷 선택까지 실무에서 바로 쓸 수 있는 체크리스트를 제공합니다.