CommunityProgramación y desarrollogithub.com

UtkarshBirla28/threejs-materials

Selección y configuración de materiales de three.js: no iluminados, difusos, especulares, PBR, toon, de puntos y de líneas; propiedades compartidas como blending y stencil, respuesta al entorno, y el equivalente en WebGPU node-material y TSL. Útil al estilizar mallas o ajustar el comportamiento de los materiales.

¿Qué es threejs-materials?

Este skill proporciona una guía completa y detallada para seleccionar y configurar todos los tipos de materiales en three.js, desde los más básicos como MeshBasicMaterial hasta los avanzados como MeshPhysicalMaterial, incluyendo el manejo de texturas, mapas de entorno y la nueva sintaxis TSL para WebGPU. La documentación está orientada a la versión r186 de three.js, con ejemplos prácticos de código que muestran cómo inicializar cada material, ajustar sus propiedades clave (color, rugosidad, metalicidad, transparencia, etc.) y combinarlos con luces y mapas. El skill también cubre aspectos cruciales como el espacio de color de las texturas (SRGBColorSpace), los canales UV para mapas de oclusión ambiental y luz, la creación de materiales personalizados con ShaderMaterial y RawShaderMaterial, y las limitaciones de grosor de línea en WebGL. Además, incluye una sección importante con una anulación local para un pipeline específico de animación que usa three.js r175 y Remotion, advirtiendo sobre cambios de API y prácticas recomendadas para ese contexto. Esto lo hace invaluable para desarrolladores que trabajan con three.js en proyectos de gráficos 3D, animaciones o efectos visuales, tanto en entornos WebGL como WebGPU.

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

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

Materiales de three.js

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

Anulación local (añadido el 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 en 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 "Errores comunes" / consejos de API que necesiten r176+ (HDRLoader, Timer, PCFSoftShadowMap deprecation, setEffects, RenderPipeline, outputBufferType, mesh.pivot, Capsule height, TSL/WebGPU). Primero verifica que la API exista en r175.
  • Nunca animes con useFrame, Clock, Timer, delta time, setAnimationLoop o requestAnimationFrame. Remotion renderiza cuadro a cuadro: deriva cada valor de useCurrentFrame() / fps (y mixer.setTime(frame / fps) para clips).
  • El post-procesado 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 por defecto ACES y el teñido de tonemapping_fragment.
  • Para otros proyectos de three.js, la guía original de r186 a continuación aplica tal como está escrita.

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

// Material PBR: la opción por defecto para superficies iluminadas y realistas
const material = new THREE.MeshStandardMaterial({
  color: 0x00aaff,
  roughness: 0.35,
  metalness: 0.1,
});
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 48, 32), material);
scene.add(mesh);
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 1)); // los materiales iluminados se renderizan en negro sin luces
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(3, 3, 3);
scene.add(light);

function animate() {
  mesh.rotation.y += 0.01;
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

Resumen de tipos de material

MaterialCaso de usoIluminación
MeshBasicMaterialColor sin iluminación, texturas, wireframesNinguna
MeshLambertMaterialSuperficies mates difusasDifusa
MeshPhongMaterialPlástico brillante, especular baratoDifusa + especular
MeshStandardMaterialFlujo de trabajo PBR metal/rugosidadPBR
MeshPhysicalMaterialPBR más capa transparente, transmisión, brillo, iridiscenciaPBR
MeshToonMaterialSombreado celPasos toon
MeshMatcapMaterialAspecto iluminado horneado en una textura matcap, no necesita lucesHorneado
ShadowMaterialSuperficie transparente que solo muestra las sombras que recibeSolo sombras
PointsMaterialNubes de puntosNinguna
LineBasicMaterial / LineDashedMaterialLíneasNinguna
ShaderMaterial / RawShaderMaterialGLSL personalizado, solo WebGLRendererPersonalizada

Las texturas de datos de color (map, emissiveMap, specularMap, sheenColorMap, specularColorMap, matcap LDR, un canvas o video usado como color) necesitan texture.colorSpace = THREE.SRGBColorSpace, o los colores se renderizan descoloridos. Los mapas escalares (normal, bump, displacement, roughness, metalness, ao, alpha, toon gradientMap) mantienen el valor por defecto. Los cargadores HDR establecen su propio espacio de color.

MeshBasicMaterial

Sin cálculos de iluminación. Rápido, siempre visible. Soporta envMap con reflectivity (0 a 1) y combine (THREE.MultiplyOperation, THREE.MixOperation, THREE.AddOperation); specularMap (sRGB) enmascara la contribución del env-map.

const map = await new THREE.TextureLoader().loadAsync("color.jpg");
map.colorSpace = THREE.SRGBColorSpace;

const material = new THREE.MeshBasicMaterial({
  color: 0xffffff, // multiplicado con el map
  map,
  transparent: true,
  opacity: 0.5,
  side: THREE.DoubleSide, // FrontSide (por defecto), BackSide, DoubleSide
  fog: true, // afectado por la niebla de la escena
});

MeshLambertMaterial y MeshPhongMaterial

Lambert solo es difuso. Phong añade un brillo especular; es la alternativa barata cuando Standard es demasiado pesado.

const lambert = new THREE.MeshLambertMaterial({
  color: 0x00ff00,
  emissive: 0x111111, // color de autoiluminación
  emissiveIntensity: 1,
});

const phong = new THREE.MeshPhongMaterial({
  color: 0x0000ff,
  specular: 0x111111, // color del brillo
  shininess: 30, // más alto = brillo más nítido
  flatShading: false, // sombreado plano vs suave
});

Ambos también soportan map, emissiveMap, specularMap (sRGB; enmascara el reflejo del env-map y el brillo en Phong), normalMap, bumpMap, displacementMap, aoMap, envMap, envMapIntensity y envMapRotation.

MeshStandardMaterial (PBR)

Renderizado basado en física. Recomendado para resultados realistas.

const map = await new THREE.TextureLoader().loadAsync("color.jpg");
map.colorSpace = THREE.SRGBColorSpace;
const ormMap = await new THREE.TextureLoader().loadAsync("orm.jpg"); // glTF ORM: ao rojo, roughness verde, metalness azul; escalar: mantener por defecto

const material = new THREE.MeshStandardMaterial({
  color: 0xffffff, // multiplicado con el map
  roughness: 1.0, // 0 = espejo, 1 = completamente difuso; multiplicado por roughnessMap (verde)
  metalness: 1.0, // 0 = dieléctrico, 1 = metal; multiplicado por metalnessMap (azul), así que mantén 1 con un map
  map,
  roughnessMap: ormMap,
  metalnessMap: ormMap,
  normalMap: null, // detalle de superficie; escala con normalScale (un Vector2)
  aoMap: ormMap, // oclusión ambiental (rojo); intensidad con aoMapIntensity
  displacementMap: null, // desplazamiento de vértices; escala con displacementScale, desplazamiento con displacementBias
  emissive: 0x000000,
  emissiveIntensity: 1,
  emissiveMap: null, // sRGB
  envMap: null, // o confiar en scene.environment (ver más abajo)
  fog: true,
});

lightMap (con lightMapIntensity) hornea la luz indirecta de la misma manera que aoMap hornea la oclusión; un lightMap HDR o EXR es LinearSRGBColorSpace, que los cargadores establecen.

Canales UV: aoMap y lightMap leen el canal que elijas

Cada textura con mapeo UV muestrea el atributo seleccionado por texture.channel: 0 lee uv (por defecto), 1 lee uv1, 2 lee uv2, 3 lee uv3. aoMap y lightMap no necesitan un segundo conjunto UV: muestrean uv a menos que cambies channel. GLTFLoader carga TEXCOORD_1 como uv1 y establece channel por sí mismo.

const aoMap = await new THREE.TextureLoader().loadAsync("ao.jpg"); // escalar: mantener por defecto
const material = new THREE.MeshStandardMaterial({ aoMap }); // muestrea uv: no se necesita atributo adicional

// Alternativa para un unwrap separado (un mapa de AO o luz horneada): copia los pares u,v de tu baker en uv1
const bakedUv = new THREE.Float32BufferAttribute(mesh.geometry.attributes.position.count * 2, 2); // lleno de ceros hasta que los copies
mesh.geometry.setAttribute("uv1", bakedUv); mesh.material = material;
aoMap.channel = 1; // este material ahora muestrea uv1 en lugar de uv

MeshPhysicalMaterial (PBR avanzado)

Extiende MeshStandardMaterial con características de capa y volumen. Existen variantes de textura para la mayoría de ellas: clearcoatMap, clearcoatRoughnessMap, clearcoatNormalMap, transmissionMap, thicknessMap, iridescenceMap, iridescenceThicknessMap, sheenColorMap, sheenRoughnessMap, anisotropyMap, specularIntensityMap, specularColorMap.

const material = new THREE.MeshPhysicalMaterial({
  // Capa transparente (pintura de coche, laca)
  clearcoat: 1.0, // 0 = ninguno, 1 = capa completa
  clearcoatRoughness: 0.1,
  // Transmisión (vidrio, agua) - refracta los objetos opacos y el scene.background detrás
  transmission: 1.0, // 0 = opaco, 1 = completamente transmisivo
  thickness: 0.5, // grosor del volumen para refracción
  attenuationDistance: 1, // unidades del mundo; Infinity (por defecto) = sin absorción
  attenuationColor: new THREE.Color(0xffaa66), // la luz blanca se vuelve de este color después de attenuationDistance
  ior: 1.5, // índice de refracción, 1 a 2.333
  // Brillo (tela, terciopelo)
  sheen: 1.0,
  sheenColor: new THREE.Color(0xffffff),
  sheenRoughness: 0.5,
  // Iridiscencia (burbujas de jabón, manchas de aceite)
  iridescence: 1.0,
  iridescenceIOR: 1.3,
  iridescenceThicknessRange: [100, 400], // nanómetros
  // Anisotropía (metal cepillado); dirección con anisotropyRotation
  anisotropy: 1.0,
  // Dispersión (vidrio prismático); retroreflectividad (señales de tráfico, equipos de seguridad)
  dispersion: 0.0,
  retroreflectivity: 0.0,
  // Anulaciones especulares: specularIntensity, specularColor (más sus mapas)
});

Vidrio: transmisión con una superficie lisa y algo detrás para refractar:

const glass = new THREE.MeshPhysicalMaterial({
  color: 0xffffff,
  metalness: 0,
  roughness: 0,
  transmission: 1,
  thickness: 0.5,
  ior: 1.5,
});
// La transmisión refracta los objetos opacos y el fondo detrás; scene.environment añade sus reflejos

MeshToonMaterial

Sombreado cel. El mapa de gradiente no son datos de color: mantén su espacio de color por defecto.

const steps = new Uint8Array([120, 200]); // dos pasos de sombreado
const gradientMap = new THREE.DataTexture(steps, 2, 1, THREE.RedFormat);
gradientMap.minFilter = THREE.NearestFilter; // pasos duros; LinearFilter los mezcla
gradientMap.magFilter = THREE.NearestFilter;
gradientMap.needsUpdate = true;

const material = new THREE.MeshToonMaterial({ color: 0x00ff00, gradientMap });

MeshNormalMaterial y MeshDepthMaterial

// Normales codificadas como RGB: depuración y apariencias ilustrativas planas
const normals = new THREE.MeshNormalMaterial({ flatShading: false, wireframe: false });

// Profundidad como escala de grises (BasicDepthPacking, por defecto); RGBADepthPacking la empaqueta para leerla de vuelta en un shader
const depth = new THREE.MeshDepthMaterial();

PointsMaterial

Para nubes de puntos. size está en píxeles; con sizeAttenuation (por defecto, solo cámaras en perspectiva) se multiplica por la mitad de la altura del canvas y se divide por la profundidad del punto en espacio de vista.

const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.Float32BufferAttribute([0, 0, 0, 1, 0.5, 0, 0.5, 1, 0], 3));

const material = new THREE.PointsMaterial({
  color: 0xffffff,
  size: 0.1,
  sizeAttenuation: true, // escala con la distancia
  alphaTest: 0.5, // con un map o alphaMap: descarta fragmentos por debajo de este alfa
});

const points = new THREE.Points(geometry, material);
scene.add(points);

LineBasicMaterial y LineDashedMaterial

WebGL y WebGPU siempre dibujan primitivas de línea de 1 píxel de ancho; linewidth, linecap y linejoin solo afectan al SVGRenderer. Para líneas gruesas con WebGLRenderer usa Line2 con LineGeometry y LineMaterial de three/addons/lines/ (linewidth en píxeles); LineMaterial es un ShaderMaterial, así que WebGPURenderer necesita Line2 de three/addons/lines/webgpu/Line2.js, que usa Line2NodeMaterial.

const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.Float32BufferAttribute([-1, 0, 0, 1, 0, 0], 3));

const solid = new THREE.LineBasicMaterial({ color: 0xffffff }); // siempre 1 px

const dashed = new THREE.LineDashedMaterial({ color: 0xffffff, dashSize: 0.5, gapSize: 0.25 });
const line = new THREE.Line(geometry, dashed);
line.computeLineDistances(); // requerido, o la línea se dibuja sólida
scene.add(line);

ShaderMaterial (solo WebGLRenderer)

GLSL personalizado con uniforms de three.js inyectados por ti. Siempre compila como GLSL ES 3.00: texture(), textureLod() y dFdx funcionan, y texture2D / gl_FragColor siguen siendo alias; deja glslVersion sin establecer. ShaderMaterial, RawShaderMaterial y onBeforeCompile no tienen ruta para WebGPURenderer; allí, usa materiales nodo y TSL.

const timer = new THREE.Timer();
timer.connect(document); // pausa cuando la pestaña está oculta

const material = new THREE.ShaderMaterial({
  uniforms: { time: { value: 0 } },
  vertexShader: `
    varying vec2 vUv;
    uniform float time;
    void main() {
      vUv = uv;
      vec3 pos = position;
      pos.z += sin(pos.x * 10.0 + time) * 0.1;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    uniform float time;
    void main() {
      vec3 col = 0.5 + 0.5 * cos(time + vUv.xyx * 3.0);
      gl_FragColor = vec4(col, 1.0);
    }
  `,
});

function animate() {
  timer.update(); // lo primero en cada fotograma, sin argumento
  material.uniforms.time.value = timer.getElapsed();
  renderer.render(scene, camera);
}

Declarados por ti en el shader: modelMatrix, modelViewMatrix, projectionMatrix, viewMatrix, normalMatrix, cameraPosition, y los atributos position, normal, uv (además de instanceMatrix en un InstancedMesh, que aplicas tú mismo: projectionMatrix * modelViewMatrix * instanceMatrix * vec4(pos, 1.0), y color con vertexColors). Los conjuntos uv extra y tangent no están declarados: añade in vec2 uv1; tú mismo.

RawShaderMaterial (solo WebGLRenderer)

No se antepone ninguna declaración (solo #version de glslVersion, las definiciones SHADER_TYPE y SHADER_NAME, y tus defines), pero el renderizador aún establece projectionMatrix, modelViewMatrix, viewMatrix, normalMatrix, modelMatrix y cameraPosition: decláralos en el shader y nunca los pases en uniforms: una entrada con el mismo nombre se sobrescribe en cada dibujo (modelMatrix, modelViewMatrix, normalMatrix) o reemplaza el valor de la cámara (projectionMatrix, viewMatrix, cameraPosition).

const material = new THREE.RawShaderMaterial({
  glslVersion: THREE.GLSL3, // sin ello el shader es GLSL ES 1.00, que no tiene texture()
  vertexShader: `
    precision highp float;
    in vec3 position;
    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    precision highp float;
    uniform vec3 color;
    out vec4 fragColor;
    void main() {
      fragColor = vec4(color, 1.0);
    }
  `,
  uniforms: { color: { value: new THREE.Color(0x00ff00) } },
});

ShaderMaterial con onBeforeCompile (WebGLRenderer)

onBeforeCompile es una función retrollamada en ShaderMaterial o en un material integrado que recibe el shader antes de la compilación y te permite parchear sus cadenas. Ejemplo: MeshBasicMaterial con un reemplazo de #define .... Si manipulas shader.vertexShader o shader.fragmentShader, actualiza la variable shader.uniforms cuando añadas uniforms adicionales (el three integrado no puede inferirlos de las nuevas cadenas). No es compatible con WebGPURenderer; usa node materials y TSL.

const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
material.onBeforeCompile = (shader) => {
  shader.uniforms.time = { value: 0 }; // añade un uniform
  shader.fragmentShader = shader.fragmentShader.replace(
    `#include <color_fragment>`,
    `
      #include <color_fragment>
      diffuseColor.rgb *= 0.5 + 0.5 * sin(vUv.x * 10.0 + uniforms.time.value);
    `
  );
};

Materiales comunes compartidos

Blending

const material = new THREE.MeshBasicMaterial({
  transparent: false,
  blending: THREE.NormalBlending, // NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending
  blendEquation: THREE.AddEquation, // cuando blending = CustomBlending: AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation
  blendSrc: THREE.SrcAlphaFactor, // ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor
  blendDst: THREE.OneMinusSrcAlphaFactor,
  blendEquationAlpha: null, // por defecto igual que blendEquation; nulo = deshabilitado
  blendSrcAlpha: null,
  blendDstAlpha: null,
  premultipliedAlpha: false,
  alphaTest: 0, // descarta fragmentos por debajo de este alfa
  alphaToCoverage: false, // MSAA: muestra parcialmente cubiertos los fragmentos borde alfa
});

Stencil

const material = new THREE.MeshBasicMaterial({
  stencilWrite: true,
  stencilRef: 0,
  stencilFunc: THREE.AlwaysStencilFunc, // NeverStencilFunc, LessStencilFunc, EqualStencilFunc, LessEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc, GreaterEqualStencilFunc, AlwaysStencilFunc
  stencilFail: THREE.KeepStencilOp, // ZeroStencilOp, KeepStencilOp, ReplaceStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp, InvertStencilOp
  stencilZFail: THREE.KeepStencilOp,
  stencilZPass: THREE.KeepStencilOp,
  stencilWriteMask: 0xff,
});

Profundidad

const material = new THREE.MeshBasicMaterial({
  depthWrite: true,
  depthTest: true,
  depthFunc: THREE.LessEqualDepth, // NeverDepth, LessDepth, EqualDepth, LessEqualDepth, GreaterDepth, NotEqualDepth, GreaterEqualDepth, AlwaysDepth
  depthPacking: THREE.BasicDepthPacking, // BasicDepthPacking o RGBADepthPacking; solo en MeshDepthMaterial
});

Lado y sombras

const material = new THREE.MeshStandardMaterial({
  side: THREE.FrontSide, // FrontSide (por defecto), BackSide, DoubleSide
  shadowSide: null, // FrontSide, BackSide, DoubleSide o null (heredar de side)
  colorWrite: true,
  toneMapped: true,
  polygonOffset: false,
  polygonOffsetFactor: 0, // cuando polygonOffset es true: mueve la profundidad del fragmento por polygonOffsetFactor * DZ + r * polygonOffsetUnits (r depende de la implementación)
  polygonOffsetUnits: 0,
  forceSinglePass: false, // para DoubleSide en WebGLRenderer: renderizar ambas caras en un solo pase en lugar de dos; limita el blend, pero es más rápido
});

Respuesta al entorno

El envMap de un material anula la vista previa del entorno por material. scene.environment es la envMap por defecto para todos los materiales PBR de la escena - no es necesario asignar envMap a cada uno. Si un material establece envMap, se usa en lugar de scene.environment para ese material escénico. MeshPhysicalMaterial y MeshStandardMaterial usan imagen de entorno HDR; MeshBasicMaterial y MeshPhongMaterial usan LDR. Remapéalos con PMREMGenerator.

const rgbeLoader = new THREE.RGBELoader();
rgbeLoader.load("venice_sunset_1k.hdr", (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping;
  scene.environment = texture; // Textura HDR ya PMREM por RGBELoader
  scene.background = texture; // o texture con THREE.LinearToneMapping aplicado
  scene.backgroundIntensity = 0.5;
  scene.backgroundBlurriness = 0.2;
  scene.backgroundRotation = new THREE.Euler(0, Math.PI, 0);
  scene.environmentIntensity = 1;
  scene.environmentRotation = new THREE.Euler(0, Math.PI, 0);
});

Para cargar un envMap LDR, se necesita PMREMGenerator:

const loader = new THREE.TextureLoader();
loader.load("venice_sunset_ldr.jpg", (texture) => {
  texture.colorSpace = THREE.SRGBColorSpace;
  texture.mapping = THREE.EquirectangularReflectionMapping;
  const pmremGenerator = new THREE.PMREMGenerator(renderer);
  const envMap = pmremGenerator.fromEquirectangular(texture).texture;
  texture.dispose(); // no se necesita la equirrectangular original
  pmremGenerator.dispose();
  scene.environment = envMap;
});

LOD de material con envMap y el bug de MeshPhysicalMaterial en r186

Los LOD de MeshPhysicalMaterial encapsulan MeshStandardMaterial, no MeshPhysicalMaterial - por lo tanto ignoran las propiedades físicas como clearcoat y transmission. Para evitarlo, envuelve el LOD en objetos manuales:

const lod = new THREE.LOD();
const group = new THREE.Group(); // no LOD; el group invisibiliza sus hijos
for (let i = 0; i < 3; i++) {
  const mesh = new THREE.Mesh(geometry, physicalMaterial.clone());
  group.add(mesh);
  lod.addLevel(group, i * 10); // distancia en unidades del mundo
}

Materiales TSL (WebGPU + NodeMaterial)

WebGPU y la ruta NodeMaterial de WebGL soportan sombreado definido con TSL, el lenguaje de sombreado de nodos de three.js: crea Fn() que llaman a builtins de three/tsl y devuelven el sombreador final. Los materiales de nodo integrados (MeshBasicNodeMaterial, MeshStandardNodeMaterial, etc.) usan TSL internamente y exponen material.colorNode, material.opacityNode, material.positionNode para anulaciones sin parchear cadenas GLSL, pero sigue siendo TSL escribir o anular sus sombreadores.

import { Fn, float, uv, sin, add, mul, vec3, positionLocal, uniform, timerLocal, normalize, modelWorldMatrix, cameraPosition, positionWorld, dot, pow, vec4, clamp, normalWorld, lightPositionWorld, sub, length, max, diffuseColor, lightingModel, shadowModel, emissiveColor, output } from 'three/tsl';
import { NodeMaterial } from 'three'; // soportado tanto en WebGL como WebGPU

// Materiales de nodo (también funcionan en WebGL si el renderizador lo soporta: renderer.backend.isWebGLBackend === true)
const { MeshBasicNodeMaterial, MeshStandardNodeMaterial } = THREE;

// UV animadas en un material básico
const time = timerLocal();

const uvNode = uv();
const animatedUv = uvNode.add(time.mul(0.1)); // desplazar UV con el tiempo

const material = new MeshBasicNodeMaterial();
material.colorNode = animatedUv.xyy; // usar UV como color
material.opacityNode = float(0.8); // alpha constante

Sombreadores TSL personalizados

Define un Fn que devuelva el color final del fragmento. El timerLocal devuelve nanosegundos desde que comenzó el renderizador, los demás valores como uv, positionLocal y normalWorld son automáticos.

// Sombreador de fragmento personalizado
const myFragmentFn = Fn(() => {
  const time = timerLocal();
  const uvNode = uv();
  const r = sin(uvNode.x.mul(10).add(time)).mul(0.5).add(0.5);
  const g = sin(uvNode.y.mul(10).add(time.mul(1.5))).mul(0.5).add(0.5);
  const b = sin(uvNode.x.add(uvNode.y).mul(10).add(time.mul(0.8))).mul(0.5).add(0.5);
  return vec3(r, g, b);
});

const material = new MeshBasicNodeMaterial();
material.colorNode = myFragmentFn();

Línea TSL: Line2NodeMaterial

Para líneas anchas en WebGPU, usa los módulos webgpu en lugar de los predeterminados:

import { Line2 } from 'three/addons/lines/webgpu/Line2.js';
import { LineMaterial } from 'three/addons/lines/webgpu/Line2NodeMaterial.js'; // nombre de la exportación

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute([-1, 0, 0, 1, 0, 0], 3));
const material = new LineMaterial({ color: 0xff0000, linewidth: 10 }); // en píxeles
const line = new Line2(geometry, material);
line.computeLineDistances();
scene.add(line);

Trabajar con efectos de post-procesado (tsl)

Con WebGPURenderer (que habilita el soporte de post-procesado con rtt), puedes definir pases de post-procesado usando TSL para definir el sombreador de fragmento del pase:

import { PostProcessing } from 'three/addons/postprocessing/PostProcessing.js';
import { output, Fn, uv, texture, vec4 } from 'three/tsl';

const postProcessing = new PostProcessing(renderer);

const pass = postProcessing.addPass(({ camera, scene, renderer }) => {
  // renderiza la escena en un color attachment
  // y devuelve un Fn para el sombreado final del píxel
});

// O para un paso de post-procesado simple sin escena:
const customPass = postProcessing.addPass({
  width: window.innerWidth,
  height: window.innerHeight,
  colorFormat: THREE.RGBAFormat,
  // ...
});
customPass.setSize(window.innerWidth, window.innerHeight);

Errores comunes

Sin luces

MeshStandardMaterial, MeshPhysicalMaterial, MeshLambertMaterial, MeshPhongMaterial y MeshToonMaterial renderizan negros sin luces en la escena. Añade al menos una AmbientLight o HemisphereLight.

Textura sin SRGBColorSpace

Una textura de color cargada (color.jpg, matcap, specularMap) renderiza descolorida si su colorSpace sigue siendo LinearSRGBColorSpace (el valor por defecto de Texture). Los cargadores de texturas HDR (RGBELoader, EXRLoader) establecen su propio colorSpace; otras texturas de datos de color necesitan texture.colorSpace = THREE.SRGBColorSpace.

Sin normalMap

Usar normales planas sin normalMap (es decir, sin detalle de superficie normal-map) produce facetas planas visibles; especialmente notable en iluminación PBR. Para superficies suaves sin mapa, normalMap puede ser null.

Sin mapeo de tonos

Por defecto tres.js usa NoToneMapping, que renderiza colores lineales HDR sin comprimir. Ajusta renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0; para un resultado agradable a la vista.

Sin antialiasing

Crea el renderizador con antialias: true. O usa SMAA de post-procesado para un antialiasing de mayor calidad.

Líneas finas

LineBasicMaterial y LineDashedMaterial dibujan siempre de 1 píxel de ancho. Para líneas más gruesas, usa Line2 de three/addons/lines/ (WebGL) o three/addons/lines/webgpu/Line2.js (WebGPU).

MeshPhysicalMaterial: propiedades ignoradas en LOD

Los LOD envuelven MaterialPhysicalMaterial para replicar MeshStandardMaterial; las propiedades físicas se pierden. Usa un group de mallas físicas como se muestra arriba.

falta un normalMap del activo

Si un activo 3D tiene un normalMap pero el material no lo tiene, renderiza facetas planas visibles. Aplica la textura al slot correcto: material.normalMap = map;.

Materiales de cámara: puntos y líneas con profundidad fija

Cuando necesites puntos/líneas del tamaño de píxeles de la pantalla, por ejemplo un retículo 3D que no escala con la distancia, establece sizeAttenuation: false en PointsMaterial, o una línea con Line2 manteniendo la posición de la cámara.

const material = new THREE.PointsMaterial({
  color: 0xffffff,
  size: 10, // píxeles, no unidades del mundo
  sizeAttenuation: false,
});

Tabla de códigos de retorno del cargador

Tipo de texturacolorSpaceEjemplo
Color (map, emissiveMap, ...)SRGBColorSpacecolor.jpg
Escalar (roughnessMap, metalnessMap, normalMap, ...)LinearSRGBColorSpace (por defecto)orm.jpg (solo verde/azul), ao.jpg (rojo)
HDR (envMap)LinearSRGBColorSpace (establecido por el cargador)venice_sunset_1k.hdr
LDR envMapSRGBColorSpace (configuración manual)venice_sunset_ldr.jpg

Individual skills in this repo

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

Skills relacionados