Saltar al contenido

Matrices locales y globales: cuándo se actualizan

Qué hace exactamente updateMatrixWorld, qué significan matrixAutoUpdate y matrixWorldAutoUpdate, y cómo ahorrar miles de operaciones por fotograma en objetos que no se mueven.

El algoritmo, en orden

Con más detalle, lo que hace cada nodo cuando le llega la actualización es esto. Primero, si tiene la actualización local automática activa, recompone su matriz local a partir de posición, cuaternión y escala, en el orden canónico de traslación por rotación por escala. Después, si esa matriz ha cambiado o si el padre le indica que debe recalcular, multiplica la matriz de mundo de su padre por su matriz local y guarda el resultado en su matriz de mundo. Por último, propaga la llamada a todos sus hijos, indicándoles que recalculen si él mismo ha recalculado.

El renderer dispara todo esto al principio de render: llama a la actualización sobre la escena y, por separado, sobre la cámara si esta no tiene padre. Ese es el único momento del fotograma en que las matrices de mundo son fiables sin intervención tuya.

De ahí la regla que ya apareció en el nivel de espacios: cualquier lectura de posición, orientación o escala globales debe hacerse después de render, o bien forzando la actualización explícitamente antes de leer.

JS
import * as THREE from 'three';const scene = new THREE.Scene();const grupo = new THREE.Group();const hijo = new THREE.Object3D();grupo.add(hijo);scene.add(grupo);grupo.position.set(5, 0, 0);hijo.position.set(0, 2, 0);const p = new THREE.Vector3();// Antes de actualizar, la matriz de mundo sigue siendo la identidad.p.setFromMatrixPosition(hijo.matrixWorld);console.log('sin actualizar:', p.toArray());        // (0, 0, 0)scene.updateMatrixWorld(true);p.setFromMatrixPosition(hijo.matrixWorld);console.log('tras actualizar:', p.toArray());        // (5, 2, 0)

Hay un atajo que conviene conocer y que evita el error por completo: los métodos getWorldPosition, getWorldQuaternion, getWorldScale y getWorldDirection actualizan por su cuenta la cadena de matrices desde la raíz hasta el objeto antes de leer. Son seguros en cualquier momento del fotograma. Leer directamente de matrixWorld, en cambio, no lo es.

Actualizar un nodo sin recorrer toda la escena

updateMatrixWorld recorre todo el subárbol desde el nodo en que se llama. Sobre la escena entera, en una escena grande, eso es caro y casi nunca es lo que necesitas cuando solo quieres consultar un objeto.

Para ese caso existe updateWorldMatrix, con dos argumentos que indican en qué direcciones propagar: hacia los ancestros y hacia los descendientes. Llamarlo con el primero activo y el segundo desactivado actualiza exactamente la cadena que va desde la raíz hasta ese objeto, y nada más. Es lo que hacen internamente los métodos de consulta global.

JS
import * as THREE from 'three';// Actualiza solo la cadena de ancestros: lo minimo para leer este objeto.hijo.updateWorldMatrix(true, false);// Actualiza el objeto y todo su subarbol, sin tocar los ancestros.grupo.updateWorldMatrix(false, true);

La distinción importa cuando tienes un sistema que consulta posiciones globales de muchos objetos por fotograma —un sistema de audio espacial, uno de proximidad, uno de etiquetas— y estás llamando a la actualización completa una vez por consulta. La solución correcta suele ser una sola actualización de la escena al principio del fotograma y consultas directas después.

Las dos banderas y qué apaga cada una

matrixAutoUpdate controla el primer paso: si está desactivado, el nodo deja de recomponer su matriz local a partir de posición, rotación y escala. La matriz local pasa a ser responsabilidad tuya: o la escribes directamente, o llamas a updateMatrix cuando cambies las propiedades.

matrixWorldAutoUpdate controla el segundo y el tercero: si está desactivado, el recorrido automático no entra en ese nodo y su matriz de mundo no se recalcula. Es la forma de congelar un subárbol entero.

Las dos tienen sus valores por defecto en propiedades estáticas de la clase, Object3D.DEFAULT_MATRIX_AUTO_UPDATE y Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE, que se pueden cambiar globalmente. Cambiarlas afecta solo a los objetos creados después.

JS
import * as THREE from 'three';const estatico = new THREE.Mesh(  new THREE.BoxGeometry(),  new THREE.MeshNormalMaterial());estatico.position.set(3, 0, -2);estatico.rotation.y = 0.4;estatico.updateMatrix();              // se compone una vezestatico.matrixAutoUpdate = false;    // y ya no se recompone nunca mas// Si mas adelante hay que moverlo, hay que pedirlo explicitamente.function moverEstatico(objeto, x, y, z) {  objeto.position.set(x, y, z);  objeto.updateMatrix();              // sin esto, el cambio no tiene ningun efecto}

Cuánto cuesta de verdad

Merece la pena poner números para no optimizar a ciegas. El coste de actualización es lineal en el número de nodos y no depende de si son visibles, de si están dentro del volumen de la cámara ni de si tienen geometría.

Escena Nodos Operaciones de matriz por fotograma
Interfaz con un modelo 50 100
Escena de producto con detalles 800 1600
Arquitectura completa 12000 24000
Mapa con vegetación instanciada mal 60000 120000

La última fila es reconocible: alguien creó una malla por planta en lugar de usar instanciación, y la escena va lenta con la GPU al treinta por ciento. El perfil muestra tiempo en la actualización de matrices y no en el dibujado. Es un problema de CPU con una solución estructural, y desactivar la actualización automática lo alivia pero no lo resuelve: lo que hay que quitar son los sesenta mil nodos.

Esa es, de hecho, la conclusión general del tema. Las banderas de actualización sirven para afinar; la decisión que de verdad determina el coste es cuántos nodos hay en el árbol, y esa se toma al diseñar la escena. Los grupos y los pivotes, que son la razón más común para añadir nodos, son el siguiente tema.