Community코딩 & 개발github.com

UtkarshBirla28/threejs-geometry

Three.js 기하학(Geometry) 생성 및 조작: 내장 도형, 3D 텍스트, 버텍스 버퍼 및 속성, 병합, 인스턴싱, 배칭, 점군(Point Cloud) 및 선(Line)을 WebGL 또는 WebGPU와 TSL 상에서 다룹니다. 3D 도형이나 텍스트, 커스텀 버텍스 데이터, 커스텀 메시, 인스턴스드 메시, 또는 점군을 만들 때 사용하세요.

threejs-geometry란 무엇인가요?

이 스킬은 three.js의 기하학 시스템을 종합적으로 다루며, r186 버전을 기준으로 합니다. 내장 도형(박스, 구, 원통, 원뿔, 토러스 등)부터 고급 도형(정다면체, 라스(Lathe), 돌출(Extrude), 튜브, 텍스트)까지 생성하는 방법을 제공합니다. 또한 BufferGeometry를 직접 생성하고 커스텀 속성(위치, 법선, UV, 색상, 인덱스)을 설정하는 방법, 인스턴스드 메시(InstancedMesh)와 인스턴스드 버퍼 지오메트리(InstancedBufferGeometry)를 통한 대량 렌더링, 점군(Points)과 선(Line) 시스템, 그리고 점진적 업데이트와 경계 상자 계산 등의 고급 기법을 설명합니다. WebGLRenderer와 함께 WebGPURenderer에서의 차이점(예: LineLoop 미지원, 점 크기 문제)도 다룹니다. 사용자는 이 튜토리얼을 따라 three.js에서 3D 콘텐츠를 효과적으로 구축하고 최적화할 수 있습니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/UtkarshBirla28/animated-shorts-pipeline/tree/HEAD/.claude/skills/threejs-geometry

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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 섹션을 참조하세요.

…

[번역을 위해 잘림]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

관련 스킬