La familia de node materials
Las diecisiete clases que trae r184, qué relación tienen con los materiales clásicos, y el mecanismo por el que un slot vacío no cuesta nada.
El catálogo
three/webgpu exporta diecisiete clases de material de nodos:
| Node material | Equivalente clásico |
|---|---|
NodeMaterial |
la base, sin equivalente directo |
MeshBasicNodeMaterial |
MeshBasicMaterial |
MeshLambertNodeMaterial |
MeshLambertMaterial |
MeshPhongNodeMaterial |
MeshPhongMaterial |
MeshStandardNodeMaterial |
MeshStandardMaterial |
MeshPhysicalNodeMaterial |
MeshPhysicalMaterial |
MeshToonNodeMaterial |
MeshToonMaterial |
MeshNormalNodeMaterial |
MeshNormalMaterial |
MeshMatcapNodeMaterial |
MeshMatcapMaterial |
MeshSSSNodeMaterial |
sin equivalente, dispersión subsuperficial |
PointsNodeMaterial |
PointsMaterial |
SpriteNodeMaterial |
SpriteMaterial |
LineBasicNodeMaterial |
LineBasicMaterial |
LineDashedNodeMaterial |
LineDashedMaterial |
Line2NodeMaterial |
el material de líneas gruesas de los addons |
ShadowNodeMaterial |
ShadowMaterial |
VolumeNodeMaterial |
sin equivalente, renderizado volumétrico |
La correspondencia con los nombres clásicos es literal: añade Node antes de Material. Y el constructor acepta el mismo objeto de parámetros:
import * as THREE from 'three/webgpu';const material = new THREE.MeshPhysicalNodeMaterial( { color: 0x0000ff, metalness: 0.9, roughness: 0.5, clearcoat: 1.0, clearcoatRoughness: 0.1, normalMap: mapaNormal, normalScale: new THREE.Vector2( 0.15, 0.15 )} );Migrar un proyecto sin tocar shaders es, por tanto, una sustitución de nombres. Si el material clásico funcionaba, el de nodos funciona igual.
Dos ausencias que conviene notar. MeshDepthNodeMaterial y MeshDistanceNodeMaterial no existen: en el sistema de nodos, la generación de shadow maps y de profundidad se resuelve por otra vía, con los slots de sombra de la clase base. Y InstancedPointsNodeMaterial tampoco está en el catálogo de r184.
Los slots de la clase base
NodeMaterial declara veintitrés propiedades que terminan en Node, todas inicializadas a null. Las que se usan a diario son estas:
| Slot | Qué sustituye |
|---|---|
colorNode |
el color difuso base |
positionNode |
la posición del vértice |
normalNode |
la normal de la superficie |
opacityNode |
la opacidad |
alphaTestNode |
el umbral del alpha test |
envNode |
la contribución del entorno |
aoNode |
la oclusión ambiental |
depthNode |
la profundidad escrita |
outputNode |
el color final, tras iluminación y salida |
fragmentNode |
el fragment shader entero |
vertexNode |
el vertex shader entero |
Y las de menor uso: lightsNode, backdropNode, backdropAlphaNode, maskNode, maskShadowNode, geometryNode, mrtNode, contextNode, castShadowNode, receivedShadowNode, castShadowPositionNode y receivedShadowPositionNode.
Cuidado con los dos últimos, porque el nombre corto que uno espera —shadowPositionNode— no existe. Son dos slots distintos, uno para cuando el objeto proyecta sombra y otro para cuando la recibe.
Los slots de cada subclase
Cada material añade lo suyo. MeshStandardNodeMaterial declara tres:
emissiveNode, metalnessNode, roughnessNodeMeshPhysicalNodeMaterial hereda esos tres y añade diecisiete más, uno por cada capa del modelo físico:
clearcoatNode, clearcoatRoughnessNode, clearcoatNormalNode,sheenNode, sheenRoughnessNode,iridescenceNode, iridescenceIORNode, iridescenceThicknessNode,specularIntensityNode, specularColorNode, iorNode,transmissionNode, thicknessNode,attenuationDistanceNode, attenuationColorNode,dispersionNode, anisotropyNodeY MeshBasicNodeMaterial no declara ninguno propio: se conforma con los de la base, que es coherente con que su modelo de iluminación sea el más simple.
Por qué un slot vacío no cuesta nada
El mecanismo es una línea repetida por todo NodeMaterial.js, siempre con la misma forma:
let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;const metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness;let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness;Cada slot es un ternario evaluado en tiempo de construcción del grafo, en JavaScript. Si el slot es null, se usa el accesor por defecto, que lee la propiedad correspondiente del material. Si tiene un nodo, se usa ese.
No hay ninguna rama en el shader generado. No hay coste en tiempo de ejecución. El material sin slots asignados produce exactamente el mismo grafo que produciría si el sistema de slots no existiera. Y un material con un slot asignado produce un grafo donde ese subárbol está sustituido, y nada más.
Compara esto con lo que hacía falta en el sistema clásico para conseguir lo mismo: encontrar el chunk correcto, sustituir una cadena, declarar la clave de caché, y esperar que la próxima versión no renombre nada.