Three.js Geometry
대상 버전: three.js r186. 예제는 WebGLRenderer를 사용합니다. WebGPU 및 TSL 섹션은 WebGPURenderer를 사용합니다.
로컬 오버라이드 (2026-11-10, 이 PC용 Claude에 의해 추가됨; 원본 스킬의 일부가 아님). 저희 애니메이션 단편 파이프라인 (
<ROOT>\studio, mromidev/claude-animated-shorts에서 가져옴)은 three 0.175 (r175)
- Remotion 4.0.290 + @react-three/fiber 9.1.2 + pmndrs postprocessing 6.37.2에 고정되어 있습니다. Three는 고정 상태를 유지하세요: shade.ts/cardshade.ts 는 three 셰이더 청크를 문자열 패치합니다. 해당 파이프라인에서 작업할 때:
- r176+가 필요한 "일반적인 실수" / API 조언(HDRLoader, Timer, PCFSoftShadowMap 폐지, setEffects, RenderPipeline, outputBufferType, mesh.pivot, Capsule
height, TSL/WebGPU)은 무시하세요. 먼저 해당 API가 r175에 존재하는지 확인하세요.- useFrame, Clock, Timer, 델타 시간, setAnimationLoop 또는 requestAnimationFrame으로 애니메이션을 만들지 마세요. Remotion은 프레임별로 렌더링합니다: 모든 값을
useCurrentFrame()/ fps (클립의 경우mixer.setTime(frame / fps))에서 파생시키세요.- 후처리는 pmndrs
postprocessing이며, three/addons EffectComposer가 아닙니다: OutputPass를 추가하지 마세요 (이중 sRGB 인코딩).- 툰 색상: 우리 컴포저에서는 톤 매핑이 NEUTRAL입니다. ACES 기본값과
tonemapping_fragment틴팅을 주의하세요.- 다른 three.js 프로젝트의 경우 아래의 원본 r186 지침이 그대로 적용됩니다.
빠른 시작
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
// 지오메트리는 형태입니다. 많은 메시가 하나의 지오메트리 인스턴스를 공유할 수 있습니다.
const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial());
scene.add(mesh);
renderer.setAnimationLoop(animate);
function animate() {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
내장 지오메트리
기본 도형
new THREE.BoxGeometry(1, 1, 1); // 너비, 높이, 깊이, 너비 분할 수, 높이 분할 수, 깊이 분할 수
new THREE.SphereGeometry(1, 32, 16); // 반지름, 너비 분할 수, 높이 분할 수, phiStart, phiLength, thetaStart, thetaLength
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI); // 반구 (phi 스윕, 극점을 통과하는 절단)
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2); // 돔 (적도에서 절단)
new THREE.PlaneGeometry(10, 10, 1, 1); // 너비, 높이, 너비 분할 수, 높이 분할 수
new THREE.CircleGeometry(1, 32); // 반지름, 세그먼트 수, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 32); // 상단 반지름, 하단 반지름, 높이, 방사형 세그먼트 수, 높이 세그먼트 수, openEnded, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 6); // 육각 기둥
new THREE.ConeGeometry(1, 2, 32); // 반지름, 높이, 방사형 세그먼트 수, 높이 세그먼트 수, openEnded, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100); // 반지름, 튜브 반지름, 방사형 세그먼트 수, 튜브 세그먼트 수, 호, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100, Math.PI); // 반호
new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3); // 반지름, 튜브 반지름, 튜브 세그먼트 수, 방사형 세그먼트 수, p, q
new THREE.RingGeometry(0.5, 1, 32, 1); // 내부 반지름, 외부 반지름, theta 세그먼트 수, phi 세그먼트 수, thetaStart, thetaLength
고급 도형
new THREE.CapsuleGeometry(0.5, 1, 4, 8, 1); // 반지름, 높이 (중간 섹션만), 캡 세그먼트 수, 방사형 세그먼트 수, 높이 세그먼트 수
// 반지름, 디테일 - 각 디테일 레벨은 모든 면을 세분화합니다.
new THREE.IcosahedronGeometry(1, 0); // 디테일 0에서 20개의 면
new THREE.DodecahedronGeometry(1, 0);
new THREE.OctahedronGeometry(1, 0);
new THREE.TetrahedronGeometry(1, 0);
// Polyhedron - 평면 정점 배열, 인덱스, 반지름, 디테일
const vertices = [1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1];
const indices = [2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1];
new THREE.PolyhedronGeometry(vertices, indices, 1, 0);
경로 기반 도형
// Lathe - Y축을 중심으로 회전하는 Vector2 프로필: 점, 세그먼트 수, phiStart, phiLength
const profile = [
new THREE.Vector2(0, 0), new THREE.Vector2(0.5, 0), new THREE.Vector2(0.5, 1), new THREE.Vector2(0, 1),
];
new THREE.LatheGeometry(profile, 32);
// Extrude - Z축 방향으로 밀어낸 2D Shape
const shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(1, 0);
shape.lineTo(1, 1);
shape.lineTo(0, 1);
new THREE.ExtrudeGeometry(shape, {
steps: 1, depth: 1, bevelEnabled: true,
bevelThickness: 0.1, bevelSize: 0.1, bevelSegments: 3,
});
// Tube - 곡선을 따라 원형 스윕: 경로, 튜브 세그먼트 수, 반지름, 방사형 세그먼트 수, 닫힘 여부
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 0, 0),
]);
new THREE.TubeGeometry(curve, 64, 0.2, 8, false);
텍스트 지오메트리
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
// three.js 저장소의 examples/fonts에서 가져온 .typeface.json 폰트
const font = await new FontLoader().loadAsync("fonts/helvetiker_regular.typeface.json");
const geometry = new TextGeometry("Hello", {
font: font,
size: 1, // 월드 단위의 텍스트 크기
depth: 0.2, // 돌출 깊이
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
direction: "ltr", // 'ltr', 'rtl' (오른쪽에서 왼쪽) 또는 'tb' (위에서 아래로)
});
geometry.center(); // 글리프를 원점에 다시 정렬
scene.add(new THREE.Mesh(geometry, new THREE.MeshNormalMaterial()));
BufferGeometry
모든 지오메트리의 기본 클래스: 정점 데이터의 타입 배열과 선택적 인덱스. 속성은 정점당 itemSize 값의 이름이 지정된 버퍼입니다. position, normal, uv 는 재질이 기본적으로 읽는 값입니다.
커스텀 BufferGeometry
const geometry = new THREE.BufferGeometry();
// 4개의 정점, 각각 3개의 float (x, y, z)
const vertices = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]);
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
// 인덱스는 두 삼각형에서 정점을 재사용합니다.
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// 법선은 조명을, UV는 텍스처 주소를 지정합니다.
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
// 선택 사항: 정점별 색상, material.vertexColors = true로 렌더링
const colors = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0]);
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
scene.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ vertexColors: true })));
속성 유형
const count = 4;
const geometry = new THREE.BufferGeometry();
// 위치, 법선, UV 및 부동 소수점 색상용 Float32
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(count * 3), 3));
// Uint16 인덱스는 정점 0에서 65534까지 주소를 지정합니다 (65535는 기본 요소 재시작 값); setIndex([...])에 일반 배열을 사용하면 Uint16 또는 Uint32를 자동으로 선택합니다.
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(count), 1));
// Uint8 색상은 normalized = true (세 번째 인수)가 필요하므로 바이트가 셰이더에 0.0-1.0으로 도달합니다.
geometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(count * 3), 3, true));
BufferGeometry 업데이트
const geometry = new THREE.PlaneGeometry(2, 2);
const positions = geometry.attributes.position;
positions.setXYZ(0, -2, -2, 0); // 정점 0 덮어쓰기
positions.getX(0); // getX / getY / getZ로 단일 구성 요소 읽기
positions.needsUpdate = true; // 변경 사항 업로드; 배열은 첫 번째 렌더링 후에 커질 수 없으므로 최대 크기를 할당하고 geometry.setDrawRange(0, n)을 사용하세요.
geometry.computeVertexNormals(); // 법선은 새 위치와 일치해야 합니다.
geometry.computeBoundingBox(); // 경계 체적은 절두체 컬링 및 레이캐스팅을 구동합니다.
geometry.computeBoundingSphere();
인터리브 버퍼
// 정점당 위치(3 floats)와 uv(2 floats)를 보유하는 하나의 버퍼
const interleavedBuffer = new THREE.InterleavedBuffer(
new Float32Array([
-1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1, // 네 정점: xyz + uv
]),
5, // stride: 정점당 float 수
);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0));
geometry.setAttribute("uv", new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3));
geometry.setIndex([0, 1, 2, 0, 2, 3]); // 네 정점 위의 두 삼각형
// BufferGeometryUtils.interleaveAttributes([position, normal, uv])는 기존 속성을 이런 식으로 패킹합니다.
가장자리 및 와이어프레임
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// 두 면의 법선이 thresholdAngle(도; 여기서는 15) 이상 차이나는 가장자리와 열린 경계 가장자리
const edges = new THREE.EdgesGeometry(boxGeometry, 15);
const edgeLines = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
// 모든 삼각형 가장자리, 대각선 포함; 두 삼각형이 공유하는 가장자리는 한 번 그려집니다.
const wireframe = new THREE.WireframeGeometry(boxGeometry);
const wireLines = new THREE.LineSegments(wireframe, new THREE.LineBasicMaterial({ color: 0xffffff }));
scene.add(edgeLines, wireLines);
점 (Points)
const count = 1000;
const positions = new Float32Array(count * 3).map(() => (Math.random() - 0.5) * 10);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
// sizeAttenuation (기본값 true)는 원근 카메라 아래에서 깊이에 따라 점 크기를 줄입니다. 점은 size * (canvas CSS height / 2) / depth CSS 픽셀에 걸쳐 있습니다.
const material = new THREE.PointsMaterial({ size: 0.1, color: 0xffffff });
const points = new THREE.Points(geometry, material);
scene.add(points);
WebGPURenderer에서 WebGPU 백엔드는 재질 크기와 관계없이 점을 1픽셀로 그립니다. 크기를 복원하는 인스턴스화된 Sprite에 대해서는 WebGPU 및 TSL 섹션을 참조하세요.
선 (Lines)
const linePoints = [
new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 0, 0),
];
const geometry = new THREE.BufferGeometry().setFromPoints(linePoints);
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// Line은 점을 순서대로 연결합니다. LineLoop는 첫 번째 점으로 다시 닫힙니다.
const line = new THREE.Line(geometry, material);
const loop = new THREE.LineLoop(geometry, material);
scene.add(line, loop);
// LineSegments는 독립적인 쌍을 그립니다: 정점 0-1, 2-3, ...
const segmentsGeometry = new THREE.BufferGeometry().setFromPoints([linePoints[0], linePoints[1], linePoints[1], linePoints[2]]);
const segments = new THREE.LineSegments(segmentsGeometry, material);
scene.add(segments);
LineLoop는 WebGPURenderer에서 지원되지 않습니다. 오류를 기록하고 아무 것도 렌더링하지 않습니다. 거기서 루프를 닫으려면 첫 번째 점을 반복하고 Line을 사용하세요.
InstancedMesh
하나의 드로우 콜로 하나의 지오메트리의 많은 복사본을 렌더링하며, 각각 고유한 변환을 가집니다.
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshBasicMaterial(); // 인스턴스 색상에 곱해짐; MeshNormalMaterial은 무시합니다.
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20);
dummy.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);
dummy.scale.setScalar(0.5 + Math.random());
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true; // 메시가 렌더링된 후 변경 시 필요; 이전에는 무해함
// setColorAt는 첫 번째 사용 시 instanceColor를 생성합니다. 루프 후에 플래그를 지정합니다.
for (let i = 0; i < count; i++) {
instancedMesh.setColorAt(i, new THREE.Color(Math.random(), Math.random(), Math.random()));
}
instancedMesh.instanceColor.needsUpdate = true;
scene.add(instancedMesh);
런타임에 인스턴스를 업데이트하고 레이캐스트하는 방법:
// mesh는 InstancedMesh입니다.
const matrix = new THREE.Matrix4();
const index = 7;
mesh.getMatrixAt(index, matrix);
matrix.setPosition(0, 2, 0); // 이 인스턴스만 이동
mesh.setMatrixAt(index, matrix);
mesh.instanceMatrix.needsUpdate = true; // setMatrixAt 호출 배치 후 플래그 지정
mesh.computeBoundingSphere(); // 인스턴스가 이동함: 절두체 컬링 및 레이캐스팅에 사용되는 경계 새로 고침
// 레이캐스팅은 어떤 인스턴스가 적중되었는지 보고합니다.
const raycaster = new THREE.Raycaster();
const hits = raycaster.intersectObject(mesh);
if (hits.length > 0) {
const instanceId = hits[0].instanceId; // 인스턴스화되지 않은 객체의 경우 undefined
}
InstancedBufferGeometry
변환 및 색상 외의 인스턴스별 속성입니다.
const count = 1000;
const geometry = new THREE.InstancedBufferGeometry();
geometry.copy(new THREE.BoxGeometry(1, 1, 1)); // 인덱스와 모든 속성 복사
geometry.instanceCount = count; // 필수: copy()는 인스턴스 수를 전달하지 않음
const offsets = new Float32Array(count * 3).map(() => Math.random() * 10);
gometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
// WebGLRenderer 및 ShaderMaterial을 사용하는 경우 버텍스 셰이더에서 `in vec3 offset;`을 직접 선언하고
// 위치에 추가하세요(threejs-shaders 참조). WebGPURenderer에서는 인스턴스별 데이터가 TSL 노드입니다.
// WebGPU 및 TSL 섹션을 참조하세요.
…
[번역을 위해 잘림]