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 (ymixer.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)