무한 터널

Infinite tunnel

카메라가 링들을 통과하며 계속 앞으로 나아가지만, 위치를 나머지 연산으로 되감아 영원히 끝나지 않는 것처럼 보이게 하는 기법.

다른 이름: Endless flythroughLooping corridorModulo camera travel
···
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x03040a);
scene.fog = new THREE.FogExp2(0x03040a, 0.05);
const camera = new THREE.PerspectiveCamera(70, 1, 0.05, 60);
camera.position.set(0, 0, 0);

const RINGS = 26;
const SPACING = 2.2;
const group = new THREE.Group();
const ringMeshes = [];
for (let i = 0; i < RINGS; i++) {
  const hue = i / RINGS;
  const mat = new THREE.MeshBasicMaterial({ color: new THREE.Color().setHSL(hue, 0.9, 0.6), wireframe: true, transparent: true });
  const ring = new THREE.Mesh(new THREE.TorusGeometry(1.6, 0.03, 8, 24), mat);
  ring.position.z = -i * SPACING;
  ring.rotation.z = i * 0.35;
  group.add(ring);
  ringMeshes.push(ring);
}
scene.add(group);
const totalLen = RINGS * SPACING;
// 링 반지름(1.6)이 카메라와의 거리보다 커지면 그 링은 화면 규격을 벗어날 만큼 거대해져
// 가장자리 몇 조각만 화면 구석에 어지럽게 걸린다. 투명도만 낮추면 그 조각이 옅게라도 남으므로,
// 아예 렌더링에서 뺀다(visible = false) — 어차피 그만큼 가까우면 카메라를 그 링이 감싸고
// 지나가는 순간이라 안 보여도 어색하지 않다.
const HIDE_DIST = 2.0;

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 z = (time * 3.2) % totalLen;
  camera.position.z = -z;
  camera.lookAt(0, 0, camera.position.z - 5);
  ringMeshes.forEach((r, i) => {
    r.rotation.z += 0.004 * (i % 2 === 0 ? 1 : -1);
    const d = Math.abs(camera.position.z - r.position.z);
    r.visible = d > HIDE_DIST;
  });
  renderer.render(scene, camera);
});

무한히 이어지는 터널을 만들 때 실제로 무한히 많은 지오메트리를 만들 필요는 없습니다. 이 데모는 네온 색 고리(TorusGeometry) 26개를 일정한 간격(SPACING)으로 한 줄에 세워두고, 카메라의 z 위치만 계속 전진시키다가 전체 길이(totalLen = RINGS × SPACING)를 넘어서면 나머지 연산(%)으로 처음 자리로 되돌립니다: z = (time * speed) % totalLen.

되감기는 순간 카메라가 순간이동하지만, fog(scene.fog = new THREE.FogExp2(...))가 저 앞의 링들을 배경색 속으로 흐릿하게 지워버리기 때문에 "어디서 잘렸는지" 눈에 띄지 않습니다 — fog 항목에서 다룬 것과 같은 트릭입니다. 링마다 다른 회전 속도와 색상(HSL의 hue를 링 순서에 따라 돌림)을 줘서 통과할 때 리듬감 있게 스쳐 지나가는 느낌을 만듭니다.

이 "위치를 나머지 연산으로 되감기" 패턴은 터널뿐 아니라 무한 스크롤 배경, 엔드리스 러너 게임의 바닥/장애물 재활용에도 그대로 씁니다 — 지오메트리를 새로 만들거나 지우지 않고 좌표만 되돌리는 것이 핵심입니다.

언제 쓰나

인트로 시퀀스, 엔드리스 러너 배경, 데이터/포트폴리오를 훑고 지나가는 "워프" 연출에.