티스토리 뷰

반응형

three.js 장면 카메라 렌더러 기초 교량 BIM 바이브코딩 2편 대표 이미지

지난 1편에서는 Claude에게 말로 설명해서 three.js 교량 모델을 한 번에 띄워봤습니다. 결과는 나왔지만, 코드가 80줄 가까이 되다 보니 "이게 어떻게 돌아가는 거지?" 하는 부분이 많았을 겁니다.

AI가 코드를 써준다고 해도, 기본 구조를 알고 있어야 원하는 걸 정확히 요청하고, 틀린 부분을 고칠 수 있습니다. 구조계산 프로그램을 쓸 때도 입력 구조를 알아야 결과를 검토할 수 있는 것과 같습니다.

그래서 2편에서는 교량을 잠시 내려놓고, 박스 하나를 화면에 띄우는 가장 작은 예제로 three.js의 뼈대를 하나씩 뜯어보겠습니다. 이 글을 다 읽고 나면 1편 코드의 대부분을 읽을 수 있게 됩니다.

(자바스크립트와 three.js를 왜 배워야 하는지가 궁금하다면 토목 엔지니어가 자바스크립트와 Three.js를 배워야 하는 이유를 먼저 읽어보세요.)

 

이번 편에서 만들 것
 

▲ 실제로 동작하는 데모입니다. 마우스로 드래그하면 회전, 휠로 확대·축소, 오른쪽 버튼으로 이동할 수 있습니다.

가로 2, 높이 1, 깊이 1짜리 박스가 천천히 회전하고, 빨강·초록·파랑 선으로 X·Y·Z 좌표축이 표시되는 화면입니다. 단순해 보이지만, three.js로 만드는 모든 3D 화면은 이 구조를 그대로 확장한 것입니다.

 

three.js의 3요소: 장면, 카메라, 렌더러

three.js로 화면에 무언가를 보여주려면 반드시 세 가지가 필요합니다. 사진 촬영에 비유하면 이해가 쉽습니다.

three.js 3요소 장면 카메라 렌더러 구조도

요소 사진 촬영 비유 구조해석 프로그램 비유 코드
장면 (Scene) 피사체를 올려놓는 무대 절점·부재가 들어 있는 모델 공간 new THREE.Scene()
카메라 (Camera) 어디서 어떤 렌즈로 찍을지 뷰 방향, 줌, 투시/평행 투영 설정 new THREE.PerspectiveCamera()
렌더러 (Renderer) 찍은 사진을 인화하는 장치 모델을 모니터에 그려주는 그래픽 엔진 new THREE.WebGLRenderer()

마지막에 renderer.render(scene, camera) 한 줄이 "이 장면을, 이 카메라로 찍어서, 화면에 그려라"라는 명령입니다. 움직이는 화면은 이 명령을 1초에 수십 번 반복하는 것입니다.

 

전체 코드

먼저 전체 코드를 보고, 아래에서 번호(①~⑧)별로 설명하겠습니다. 1편과 마찬가지로 메모장에 붙여넣고 box.html로 저장한 뒤 더블클릭하면 브라우저에서 바로 실행됩니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>three.js 박스 하나 띄우기</title>
  <style>
    body { margin: 0; overflow: hidden; }
  </style>
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js"
      }
    }
  </script>
</head>
<body>
<script type="module">
import * as THREE from 'three';

// ① 장면(Scene): 물체를 올려놓는 무대
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f3f6);

// ② 카메라(Camera): 무대를 바라보는 눈
const camera = new THREE.PerspectiveCamera(
  50,                                // 시야각(도)
  window.innerWidth / window.innerHeight, // 화면 가로세로 비율
  0.1,                               // 이보다 가까운 것은 안 그림
  100                                // 이보다 먼 것은 안 그림
);
camera.position.set(2.5, 2, 4);      // 카메라 위치 (x, y, z)
camera.lookAt(0, 0, 0);              // 원점을 바라보기

// ③ 렌더러(Renderer): 카메라에 보이는 장면을 화면에 그려주는 장치
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// ④ 물체(Mesh) = 형상(Geometry) + 재질(Material)
const geometry = new THREE.BoxGeometry(2, 1, 1);   // 가로 2, 높이 1, 깊이 1
const material = new THREE.MeshStandardMaterial({ color: 0x3f6e9e });
const box = new THREE.Mesh(geometry, material);
scene.add(box);

// ⑤ 조명: MeshStandardMaterial은 빛이 있어야 보인다
scene.add(new THREE.AmbientLight(0xffffff, 0.6));  // 전체를 은은하게
const light = new THREE.DirectionalLight(0xffffff, 2); // 햇빛처럼 한 방향
light.position.set(5, 8, 4);
scene.add(light);

// ⑥ 좌표축 표시 (빨강 X, 초록 Y, 파랑 Z)
const axes = new THREE.AxesHelper(2);
axes.setColors(0xff0000, 0x00aa00, 0x0000ff);
scene.add(axes);

// ⑦ 매 프레임마다 조금씩 돌리면서 다시 그리기
function animate() {
  box.rotation.y += 0.01;
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

// ⑧ 창 크기가 바뀌면 비율 다시 맞추기
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>

 

코드 한 줄씩 읽기

 

준비: three.js 불러오기 (importmap)

코드 위쪽의 <script type="importmap"> 부분은 "three라는 이름을 쓰면 이 인터넷 주소에서 가져와라"라는 약속입니다. 그래서 아래에서 import * as THREE from 'three' 한 줄로 three.js 전체를 THREE라는 이름으로 쓸 수 있게 됩니다. 버전(0.186.1)을 주소에 고정해 두면, 나중에 three.js가 업데이트되어도 이 코드는 그대로 동작합니다.

 

① 장면 (Scene)

빈 무대를 하나 만드는 줄입니다. scene.background로 배경색을 정합니다. 색은 0xf0f3f6처럼 16진수로 쓰는데, CSS의 #f0f3f6과 같은 색입니다.

 

② 카메라 (Camera)

three.js에서 가장 많이 쓰는 카메라는 원근감이 있는 PerspectiveCamera입니다. 괄호 안의 네 숫자가 각각 의미가 있습니다.

순서 값 의미 잘못 넣으면
1 50 시야각(FOV, 도). 렌즈의 화각 너무 크면 물체가 찌그러져 보임
2 가로/세로 화면 비율 물체가 옆으로 늘어나거나 눌려 보임
3 0.1 near: 이보다 가까운 것은 안 그림 카메라 가까운 물체가 잘려 보임
4 100 far: 이보다 먼 것은 안 그림 큰 교량 모델의 먼 쪽이 사라짐

시야각에 따라 같은 물체도 이렇게 다르게 보입니다. 세 장 모두 박스가 비슷한 크기로 보이도록 카메라 거리를 조정했습니다.

three.js PerspectiveCamera 시야각 25도 50도 90도 비교

교량처럼 긴 구조물을 볼 때는 시야각을 너무 크게 잡으면 앞쪽 경간이 과장되어 보입니다. 보통 40~60도 정도가 자연스럽습니다.

그다음 camera.position.set(2.5, 2, 4)로 카메라를 놓고, camera.lookAt(0, 0, 0)으로 원점을 바라보게 합니다. 카메라의 위치와 바라보는 점, 이 두 가지만 정하면 시점이 결정됩니다.

 

③ 렌더러 (Renderer)

WebGLRenderer는 그래픽카드를 이용해 3D를 그려주는 장치입니다. antialias: true는 모서리의 계단 현상을 부드럽게 해주는 옵션입니다. setSize로 그림 크기를 브라우저 창 크기에 맞추고, appendChild로 그 그림판(canvas)을 웹 페이지에 붙입니다.

 

④ 물체 (Mesh) = 형상 + 재질

three.js에서 눈에 보이는 물체는 Mesh이고, Mesh는 항상 형상(Geometry)과 재질(Material)의 조합입니다. 구조 모델로 치면 "단면 형상"과 "재료 물성"을 따로 정의한 뒤 부재에 할당하는 것과 같은 구조입니다.

자주 쓰는 형상 만드는 법 교량 모델에서 쓰임
BoxGeometry 가로, 높이, 깊이 바닥판, 코핑, 직사각형 교각
CylinderGeometry 윗면 반지름, 아랫면 반지름, 높이 원형 교각, 말뚝
ExtrudeGeometry 2D 단면을 그려서 길이 방향으로 밀기 I형 거더, 박스 거더 (5편에서 다룸)

 

⑤ 조명

여기서 처음 하는 분들이 가장 많이 막힙니다. 재질 종류에 따라 조명이 없으면 물체가 아예 안 보일 수 있습니다.

MeshBasicMaterial과 MeshStandardMaterial 조명 유무 비교

재질 조명 필요 특징
MeshBasicMaterial 필요 없음 단색으로 칠해짐. 면 구분이 안 돼서 입체감이 없음
MeshStandardMaterial 필요함 빛을 받는 방향에 따라 밝기가 달라져 입체감이 있음. 금속감·거칠기 표현 가능

예제에서는 두 종류의 조명을 썼습니다. AmbientLight는 모든 면을 고르게 밝혀서 그림자 쪽이 새까맣게 되는 것을 막고, DirectionalLight는 햇빛처럼 한 방향에서 비춰서 면마다 밝기 차이를 만듭니다. 두 개를 같이 쓰는 것이 가장 무난한 조합입니다.

 

⑥ 좌표축과 three.js 좌표계

AxesHelper는 원점에 X(빨강), Y(초록), Z(파랑) 축을 그려주는 도우미입니다. 개발할 때 켜두면 방향을 헷갈리지 않습니다.

여기서 구조 엔지니어가 꼭 알아야 할 차이가 있습니다.

three.js Y-up 좌표계와 구조해석 프로그램 Z-up 좌표계 비교

three.js는 Y축이 위쪽입니다. 반면 MIDAS 같은 구조해석 프로그램은 대부분 Z축이 위쪽이죠. 해석 모델의 절점 좌표를 그대로 가져와 그리면 교량이 옆으로 누워 버립니다. 좌표를 가져올 때는 (x, y, z) → (x, z, -y)처럼 바꿔주거나, 모델 전체를 X축 기준으로 -90도 회전시켜야 합니다. 이 부분은 나중에 해석 결과를 3D로 띄울 때 다시 다루겠습니다.

단위는 three.js에 따로 정해진 것이 없습니다. 1을 1m로 볼지 1mm로 볼지는 만드는 사람이 정하면 됩니다. 이 연재에서는 1 = 1m로 통일하겠습니다.

 

⑦ 애니메이션 루프

renderer.setAnimationLoop(animate)는 "화면을 새로 그릴 때마다 animate 함수를 실행하라"는 뜻입니다. 보통 1초에 60번 정도 실행됩니다. animate 안에서 box.rotation.y += 0.01로 박스를 Y축(위쪽 축) 기준으로 조금씩 돌리고, 다시 그립니다. 회전 단위는 도가 아니라 라디안이라서, 0.01은 약 0.57도입니다.

 

⑧ 창 크기 대응

브라우저 창 크기를 바꿨을 때 화면 비율이 틀어지지 않도록 카메라 비율과 렌더러 크기를 다시 맞춰주는 부분입니다. 비율을 바꾼 뒤에는 꼭 camera.updateProjectionMatrix()를 호출해야 적용됩니다.

 

숫자를 바꿔보며 실험하기

코드를 이해하는 가장 빠른 방법은 직접 숫자를 바꿔보는 것입니다. 하나씩 바꾸고 저장한 뒤 새로고침해 보세요.

바꿀 곳 바꿔볼 값 일어나는 일
BoxGeometry(2, 1, 1) (10, 0.3, 2) 길고 얇은 판 모양. 바닥판처럼 보임 (화면 밖으로 나가면 카메라를 멀리)
camera.position.set(2.5, 2, 4) (0, 6, 0.01) 위에서 내려다보는 평면도 시점
PerspectiveCamera(50, ...) 20 또는 100 줌인 / 광각 왜곡
color: 0x3f6e9e 0xb8bcc2 콘크리트 같은 회색
rotation.y += 0.01 rotation.x += 0.01 앞으로 구르듯 회전
DirectionalLight(0xffffff, 2) 이 줄과 아래 두 줄 삭제 입체감이 사라지고 면 구분이 안 됨

 

화면이 안 나올 때 체크리스트
증상 원인 해결
화면이 하얗기만 함 코드 오류로 실행이 멈춤 F12 → Console 탭의 빨간 오류 메시지를 확인하고, 그대로 복사해서 Claude에게 붙여넣기
물체가 새까맣게 보임 조명 없이 StandardMaterial 사용 ⑤ 조명 코드 확인
배경만 보이고 물체가 없음 카메라가 물체 안에 있거나 다른 곳을 바라봄 camera.position을 멀리, lookAt을 물체 위치로
한글이 깨짐 파일 인코딩 문제 메모장 저장 시 인코딩을 UTF-8로
"Failed to resolve module" 오류 인터넷 연결 끊김 또는 importmap 오타 인터넷 연결과 importmap 주소 확인

 

Claude에게 이렇게 물어보세요

코드를 읽다가 모르는 부분이 나오면, 그 부분만 복사해서 이렇게 질문하면 됩니다.

아래 three.js 코드를 구조 엔지니어가 이해하기 쉽게 한 줄씩 설명해줘. 구조해석 프로그램에 비유할 수 있는 부분은 비유해서 설명해줘.
(코드 붙여넣기)

"구조 엔지니어에게 설명하듯"처럼 내 배경을 알려주면 설명의 수준과 비유가 확 달라집니다. 이런 프롬프트 요령은 4편에서 따로 정리하겠습니다.

 

정리
  • three.js 화면은 장면 + 카메라 + 렌더러 세 가지로 이루어진다.
  • 보이는 물체는 Mesh = 형상(Geometry) + 재질(Material).
  • StandardMaterial은 조명이 없으면 안 보인다.
  • three.js는 Y축이 위. 구조해석 프로그램(Z-up)과 다르니 좌표 변환에 주의.
  • 단위는 정하기 나름. 이 연재는 1 = 1m.

 

다음 편 예고

지금은 박스가 혼자 돌기만 하고 마우스로 움직일 수 없습니다. 3편에서는 OrbitControls로 마우스 회전·확대·이동을 붙이고, 조명 종류와 그림자를 다뤄서 1편의 교량을 훨씬 보기 좋게 만들어 보겠습니다.

따라 하다가 막히는 부분은 댓글로 남겨주세요.

 

관련 글

     → [교량 BIM 바이브코딩 1편] three.js 입문: Claude 바이브코딩으로 3D 교량 만들기

     → [교량 BIM 바이브코딩 3편] OrbitControls·조명·그림자

     → [교량 BIM 바이브코딩 4편] AI 코딩 프롬프트 작성법: Claude에게 3D 모델 정확히 요청하기

     → [교량 BIM 바이브코딩 5편] three.js ExtrudeGeometry: I형 거더 단면 3D 모델링

     → [교량 BIM 바이브코딩 6편] three.js 파라메트릭 모델링: 슬라이더로 바뀌는 교량

     → [교량 BIM 바이브코딩 7편] three.js 교량 BIM 모델: 클릭하면 속성이 나오는 부재 조립

     → [교량 BIM 바이브코딩 8편] three.js 곡선교 모델링: 곡선 반경 R과 편경사로 휘는 교량

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/10   »
일 월 화 수 목 금 토
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31