장면을 이루는 네 가지
시간 기반 속도까지 Canvas 2D로 배운 뒤 Three.js로 넘어왔다. 빈 장면에 상자 하나를 놓는 데 필요한 것은 네 가지였다.
| 이름 | 역할 | |---|---| | Scene | 물체와 빛을 담는 공간 | | PerspectiveCamera | 어디서, 얼마나 넓게 보는가 | | Mesh | 모양(geometry) + 표면(material) | | Light | 빛 |
여기에 WebGLRenderer가 "이 장면을 이 카메라 시점으로 실제 픽셀에 그려라"를 실행한다.
빛을 빼먹으면 MeshStandardMaterial은 검게 나온다. 이 재질은 빛을 받아 밝기를 계산하기 때문에, 빛이 없으면 계산 결과도 0이다. 모양이 없어서가 아니라 비칠 것이 없어서 안 보이는 것이다.
애니메이션 루프는 이전 학습을 그대로 이어받았다. 직전 프레임과의 시간 차는 THREE.Clock이 대신 재 준다.
const clock = new THREE.Clock();
function loop() {
requestAnimationFrame(loop);
const dt = clock.getDelta(); // 직전 프레임과의 차 (초)
box.rotation.y += 1 * dt; // 초당 1 라디안
renderer.render(scene, camera);
}
dt 메모에서 직접 계산했던 값을 라이브러리가 같은 의미로 제공한다.
그리드가 있어야 구분된다
물체의 rotation.y를 돌리는 것과 카메라의 position.x를 옮기는 것은 둘 다 "상자의 다른 면이 보인다"는 결과를 낸다. 상자만 보고 있으면 구분이 어렵다.
처음에는 이렇게 답했다.
물체 로테이션은 제자리 회전이라면, 카메라 포지션은 위치가 이동하면서 다른 면이 보이고 있어.
무엇이 움직였는지는 맞지만, 화면만 보고 구분하는 방법은 아니었다. 그래서 바닥에 GridHelper와 AxesHelper를 깔고 다시 봤다.
아래 버튼을 누르면 3D 장면을 불러옵니다. 바닥 그리드를 기준으로 보세요.
물체 회전만 움직이면 바닥 그리드는 가만히 있습니다. 카메라 좌우를 움직이면 그리드까지 함께 움직입니다. 상자만 보지 말고 그리드를 보세요.
lookAt을 끄고 카메라를 좌우로 크게 옮기면, 카메라가 원래 보던 방향을 유지한 채 미끄러져 상자가 화면 밖으로 나갑니다.
여기서 기억할 것물체를 돌리면 그리드는 가만히 있고, 카메라를 옮기면 그리드도 같이 움직인다.
box.rotation.y는 그 물체의 변환만 바꾼다. 그리드와 축은 제자리에 있다. 카메라를 옮기면 장면 전체와의 관계가 바뀌므로 배경까지 전부 달라진다. 카메라는 장면 안의 물체가 아니라 장면을 보는 위치이기 때문이다.
이건 디버깅에 그대로 쓰인다. 화면이 이상할 때 물체가 움직인 것인지 카메라가 움직인 것인지 헷갈리면, 기준이 되는 고정물을 깔아 두고 그쪽을 보면 된다. Three.js가 GridHelper·AxesHelper 같은 것을 기본으로 제공하는 이유이기도 하다.
lookAt은 위치를 바꾸지 않는다
카메라가 항상 상자를 향하게 하던 lookAt(0, 0, 0)을 끄고 position.x를 크게 옮기면, 상자가 화면 밖으로 빠져나간다.
카메라가 상자를 안 보고 현재 바라보는 방향을 유지하면서 오른쪽으로 이동했어.
카메라는 위치(position)와 방향(rotation)을 따로 가진다. position.x만 바꾸면 방향은 손대지 않은 그대로라서, 원래 보던 쪽을 향한 채 옆으로 미끄러진다.
lookAt(x, y, z)은 그 점을 향하도록 방향을 계산해 주는 함수다. 위치는 바꾸지 않는다. 그리고 카메라가 움직일 때마다 향해야 할 각도가 달라지므로 매 프레임 호출해야 한다.
camera.position.x = value;
camera.lookAt(0, 0, 0); // 위치를 바꾼 뒤 방향을 다시 계산
한 번만 부르고 끝내면 그 순간의 방향에 고정되어, 카메라를 옮기는 순간 다시 빗나간다. 다음 프레임을 예약하지 않으면 멈춘 그림이 남는 것과 같은 구조다.
이번에 확인한 것은 예제 조작과 관찰, 그리고 그에 대한 설명이다. 직접 장면을 처음부터 구성하거나 성능을 측정한 것은 아니다.
file://에서 모듈이 막힌 이유
예제를 파일 탐색기에서 바로 열었더니 화면이 비고 콘솔에 CORS 오류가 떴다.
Access to script at 'file:///.../three.module.js' from origin 'null'
has been blocked by CORS policy
브라우저는 file: URL을 각각 별개의 보안 출처(unique origin)로 취급한다. 그래서 <script type="module">이 상대 경로로 다른 파일을 불러오는 요청이 교차 출처 요청이 되어 막힌다. 코드 문제가 아니라 여는 방법 문제이고, 로컬 HTTP 서버로 열면 해결된다.
고약한 점은 화면에 아무 설명도 안 나온다는 것이다. 모듈 스크립트는 import가 실패하면 본문이 아예 실행되지 않으므로, 자기 실패를 알리는 코드조차 돌지 못한다. 그래서 안내는 일반 스크립트에 따로 뒀다.
<script>
if (location.protocol === 'file:') showHint();
</script>
메모에 남겼다. 다음에 확인할 질문은 "상자의 모양·표면·셰이더에 전달하는 값은 각각 어디서 정해지는가"다.