CommunityProgramación y desarrollogithub.com

UtkarshBirla28/threejs-geometry

Creación y modelado de geometrías three.js: formas integradas, texto 3D, búferes y atributos de vértices, fusión, instanciación, lotes, nubes de puntos y líneas, en WebGL o con WebGPU y TSL. Úsalo al construir formas 3D o texto, datos de vértices personalizados, mallas personalizadas, mallas instanciadas o nubes de puntos.

¿Qué es threejs-geometry?

Este skill de agente de IA está diseñado para ayudar a los desarrolladores a crear y manipular geometrías en three.js, compatible con las versiones r186 y posteriores, así como con pipelines heredados como three r175. Proporciona orientación detallada sobre formas integradas (cajas, esferas, cilindros, etc.), formas avanzadas (icosaedros, dodecaedros, poliedros) y formas basadas en trayectorias (torno, extrusión, tubo), además de geometría de texto con FontLoader. Cubre la creación de BufferGeometry personalizada con atributos tipados (Float32, Uint16, Uint8), actualización de geometría en tiempo de ejecución, búferes entrelazados, y generación de bordes y mallas alámbricas. También explica el uso de Points, Lines, InstancedMesh, InstancedBufferGeometry y técnicas de fusión de geometrías, con ejemplos de código prácticos tanto para WebGLRenderer como para WebGPURenderer y TSL.

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

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Geometría de three.js

Apunta a three.js r186. Los ejemplos usan WebGLRenderer; la sección de WebGPU y TSL usa WebGPURenderer.

Anulación local (añadido 2026-11-10 por Claude para este PC; no forma parte del skill original). Nuestro pipeline de cortos animados (<ROOT>\studio, de mromidev/claude-animated-shorts) está fijado a three 0.175 (r175)

  • Remotion 4.0.290 + @react-three/fiber 9.1.2 + pmndrs postprocessing 6.37.2. Mantén three fijado: shade.ts/cardshade.ts parchea cadenas de shaders de three. Al trabajar en ese pipeline:
  • Ignora los consejos de "Errores comunes" / API que requieran r176+ (HDRLoader, Timer, PCFSoftShadowMap deprecado, setEffects, RenderPipeline, outputBufferType, mesh.pivot, Capsule height, TSL/WebGPU). Verifica primero que la API exista en r175.
  • Nunca animes con useFrame, Clock, Timer, delta time, setAnimationLoop o requestAnimationFrame. Remotion renderiza fotograma a fotograma: deriva cada valor de useCurrentFrame() / fps (y mixer.setTime(frame / fps) para clips).
  • El postprocesado es pmndrs postprocessing, no three/addons EffectComposer: no agregues un OutputPass (doble codificación sRGB).
  • Colores toon: el mapeo de tonos es NEUTRAL en nuestro compositor; ten cuidado con los valores predeterminados de ACES y el tinte de tonemapping_fragment.
  • Para otros proyectos de three.js, aplica la guía original para r186 a continuación.

Inicio Rápido

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);

// Una geometría es la forma; muchas mallas pueden compartir una instancia de geometría
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);
}

Geometrías Integradas

Formas Básicas

new THREE.BoxGeometry(1, 1, 1); // ancho, alto, profundidad, segmentosAncho, segmentosAlto, segmentosProfundidad
new THREE.SphereGeometry(1, 32, 16); // radio, segmentosAncho, segmentosAlto, phiInicio, phiLongitud, thetaInicio, thetaLongitud
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI); // media esfera (barrido phi, cortado a través de los polos)
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2); // cúpula (cortada en el ecuador)
new THREE.PlaneGeometry(10, 10, 1, 1); // ancho, alto, segmentosAncho, segmentosAlto
new THREE.CircleGeometry(1, 32); // radio, segmentos, thetaInicio, thetaLongitud
new THREE.CylinderGeometry(1, 1, 2, 32); // radioSuperior, radioInferior, altura, segmentosRadiales, segmentosAltura, abiertoExtremo, thetaInicio, thetaLongitud
new THREE.CylinderGeometry(1, 1, 2, 6); // prisma hexagonal
new THREE.ConeGeometry(1, 2, 32); // radio, altura, segmentosRadiales, segmentosAltura, abiertoExtremo, thetaInicio, thetaLongitud
new THREE.TorusGeometry(1, 0.4, 16, 100); // radio, tubo, segmentosRadiales, segmentosTubulares, arco, thetaInicio, thetaLongitud
new THREE.TorusGeometry(1, 0.4, 16, 100, Math.PI); // medio arco
new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3); // radio, tubo, segmentosTubulares, segmentosRadiales, p, q
new THREE.RingGeometry(0.5, 1, 32, 1); // radioInterior, radioExterior, segmentosTheta, segmentosPhi, thetaInicio, thetaLongitud

Formas Avanzadas

new THREE.CapsuleGeometry(0.5, 1, 4, 8, 1); // radio, altura (solo la sección media), segmentosTapa, segmentosRadiales, segmentosAltura

// radio, detalle - cada nivel de detalle subdivide cada cara
new THREE.IcosahedronGeometry(1, 0); // 20 caras en detalle 0
new THREE.DodecahedronGeometry(1, 0);
new THREE.OctahedronGeometry(1, 0);
new THREE.TetrahedronGeometry(1, 0);

// Poliedro - matriz plana de vértices, índices, radio, detalle
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);

Formas Basadas en Trayectorias

// Torno - un perfil Vector2 rotado alrededor del eje Y: puntos, segmentos, phiInicio, phiLongitud
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);

// Extrusión - una forma 2D empujada a lo largo de Z
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,
});

// Tubo - un barrido circular a lo largo de una curva: trayectoria, segmentosTubulares, radio, segmentosRadiales, cerrado
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);

Geometría de Texto

import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";

// Una fuente .typeface.json, de examples/fonts en el repositorio de three.js
const font = await new FontLoader().loadAsync("fonts/helvetiker_regular.typeface.json");
const geometry = new TextGeometry("Hola", {
  font: font,
  size: 1, // tamaño del texto en unidades del mundo
  depth: 0.2, // profundidad de extrusión
  curveSegments: 12,
  bevelEnabled: true,
  bevelThickness: 0.03,
  bevelSize: 0.02,
  direction: "ltr", // 'ltr', 'rtl' (de derecha a izquierda) o 'tb' (de arriba a abajo)
});

goometry.center(); // recentra los glifos en el origen
scene.add(new THREE.Mesh(geometry, new THREE.MeshNormalMaterial()));

BufferGeometry

La clase base de toda geometría: arrays tipados de datos de vértices más un índice opcional. Los atributos son búferes nombrados de valores itemSize por vértice; position, normal y uv son los que los materiales leen por defecto.

BufferGeometry Personalizada

const geometry = new THREE.BufferGeometry();

// Cuatro vértices, tres flotantes cada uno (x, y, z)
const vertices = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]);
goometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));

// Los índices reutilizan vértices entre los dos triángulos
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
goometry.setIndex(new THREE.BufferAttribute(indices, 1));

// Las normales impulsan la iluminación, las UVs direccionan las texturas
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
goometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
goometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
// Colores opcionales por vértice, renderizados con material.vertexColors = true
const colors = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0]);
goometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

scene.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ vertexColors: true })));

Tipos de Atributos

const count = 4;
const geometry = new THREE.BufferGeometry();
// Float32 para posiciones, normales, UVs y colores de punto flotante
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(count * 3), 3));
// Los índices Uint16 direccionan vértices de 0 a 65534 (65535 es el valor de reinicio de primitiva); setIndex([...]) con un array plano elige Uint16 o Uint32 automáticamente
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(count), 1));
// Los colores Uint8 necesitan normalized = true (tercer argumento) para que los bytes lleguen al shader como 0.0-1.0
geometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(count * 3), 3, true));

Actualización de una BufferGeometry

const geometry = new THREE.PlaneGeometry(2, 2);
const positions = geometry.attributes.position;
positions.setXYZ(0, -2, -2, 0); // sobrescribe el vértice 0
positions.getX(0); // lee componentes individuales con getX / getY / getZ
positions.needsUpdate = true; // sube el cambio; un array no puede crecer después de su primer renderizado, así que asigna el máximo y usa geometry.setDrawRange(0, n)
goometry.computeVertexNormals(); // las normales deben coincidir con las nuevas posiciones
goometry.computeBoundingBox(); // los volúmenes delimitadores impulsan el recorte de frustum y el raycasting
goometry.computeBoundingSphere();

Búferes Entrelazados

// Un búfer que contiene posición (3 flotantes) y uv (2 flotantes) por vértice
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, // cuatro vértices: xyz + uv
  ]),
  5, // stride: flotantes por vértice
);
const geometry = new THREE.BufferGeometry();
goometry.setAttribute("position", new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0));
goometry.setAttribute("uv", new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3));
goometry.setIndex([0, 1, 2, 0, 2, 3]); // dos triángulos sobre los cuatro vértices
// BufferGeometryUtils.interleaveAttributes([position, normal, uv]) empaqueta los atributos existentes de esta manera

Bordes y Mallas Alámbricas

const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// Bordes donde las normales de las dos caras difieren al menos en thresholdAngle (grados; aquí 15), más bordes de límite abiertos
const edges = new THREE.EdgesGeometry(boxGeometry, 15);
const edgeLines = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
// Cada borde de triángulo, diagonales incluidas; un borde compartido por dos triángulos se dibuja una vez
const wireframe = new THREE.WireframeGeometry(boxGeometry);
const wireLines = new THREE.LineSegments(wireframe, new THREE.LineBasicMaterial({ color: 0xffffff }));
scene.add(edgeLines, wireLines);

Puntos

const count = 1000;
const positions = new Float32Array(count * 3).map(() => (Math.random() - 0.5) * 10);
const geometry = new THREE.BufferGeometry();
goometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
// sizeAttenuation (por defecto true) reduce los puntos con la profundidad bajo una cámara perspectiva: un punto abarca size * (altura CSS del lienzo / 2) / profundidad píxeles CSS
const material = new THREE.PointsMaterial({ size: 0.1, color: 0xffffff });
const points = new THREE.Points(geometry, material);
scene.add(points);

Bajo WebGPURenderer, el backend WebGPU dibuja puntos a un píxel independientemente del tamaño del material; consulta la sección de WebGPU y TSL para el Sprite instanciado que restaura los tamaños.

Líneas

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 conecta los puntos en orden; LineLoop cierra de vuelta al primer punto
const line = new THREE.Line(geometry, material);
const loop = new THREE.LineLoop(geometry, material);
scene.add(line, loop);

// LineSegments dibuja pares independientes: vértice 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 no es compatible con WebGPURenderer: registra un error y no renderiza nada. Para cerrar un bucle allí, repite el primer punto y usa Line.

InstancedMesh

Una llamada de dibujo para muchas copias de una geometría, cada una con su propia transformación.

const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshBasicMaterial(); // multiplica por colores de instancia; MeshNormalMaterial los ignora
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; // necesario después de cambios una vez que la malla se ha renderizado; inofensivo antes

// setColorAt crea instanceColor en el primer uso; márcalo después del bucle
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);

En tiempo de ejecución, actualiza y haz raycasting a las instancias así:

// mesh aquí es un InstancedMesh
const matrix = new THREE.Matrix4();
const index = 7;
mesh.getMatrixAt(index, matrix);
matrix.setPosition(0, 2, 0); // mueve solo esta instancia
mesh.setMatrixAt(index, matrix);
mesh.instanceMatrix.needsUpdate = true; // marca después de cualquier lote de llamadas setMatrixAt
mesh.computeBoundingSphere(); // las instancias se movieron: actualiza los límites usados por el recorte de frustum y el raycasting

// Raycasting informa qué instancia fue alcanzada
const raycaster = new THREE.Raycaster();
const hits = raycaster.intersectObject(mesh);
if (hits.length > 0) {
  const instanceId = hits[0].instanceId; // undefined para un objeto no instanciado
}

InstancedBufferGeometry

Atributos por instancia más allá de la transformación y el color.

const count = 1000;
const geometry = new THREE.InstancedBufferGeometry();
goometry.copy(new THREE.BoxGeometry(1, 1, 1)); // copia el índice y todos los atributos
goometry.instanceCount = count; // requerido: copy() no lleva cuenta de instancias

const offsets = new Float32Array(count * 3).map(() => Math.random() * 10);
goometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));

// Con WebGLRenderer y un ShaderMaterial, declara `in vec3 offset;` en el shader de
// vértices tú mismo y agrégalo a la posición (consulta threejs-shaders). Bajo WebGPURenderer,
// los datos por instancia son un nodo TSL - consulta la sección de WebGPU y TSL
...

(El contenido se ha truncado para la traducción)

Individual skills in this repo

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

Skills relacionados