1. Escena, cámara, renderer
Secciones
Ajustes de lectura
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
Código: la página de la lección src/01-scene/main.ts, el mismo cubo con el renderer antiguo en src/01-scene-webgl/main.ts, el grafo de escena sin renderer en scripts/l01-scene-graph.ts, y la sonda que abre una página en Chromium headless e imprime lo que esta informa, scripts/probe.mjs.
Qué es three.js, para un desarrollador WPF, MonoGame o JavaFX
Sección titulada «Qué es three.js, para un desarrollador WPF, MonoGame o JavaFX»three.js es una biblioteca JavaScript que dibuja escenas 3D en un elemento <canvas>. Ocupa el lugar de la capa 3D retenida de WPF, por encima de la API de la GPU: describes objetos, una cámara y luces, y la biblioteca los convierte en buffers de GPU, shaders y draw calls. Por debajo, el navegador ofrece dos API de GPU: WebGL 2, basada en OpenGL ES 3.0, y WebGPU, una API más reciente diseñada para funcionar sobre Direct3D 12, Metal y Vulkan.
| WPF 3D | MonoGame | JavaFX 3D | three.js | |
|---|---|---|---|---|
| La superficie | Viewport3D en el árbol visual |
la ventana de un Game |
SubScene en el grafo de escena |
un <canvas> que crea el renderer |
| Qué dibuja | el motor de composición de WPF | tu método Draw, a través de GraphicsDevice |
Prism, el pipeline gráfico de JavaFX | un objeto renderer: WebGPURenderer o WebGLRenderer |
| La cámara | PerspectiveCamera, campo de visión horizontal |
una matriz de proyección que construyes tú | PerspectiveCamera, campo de visión vertical por defecto |
PerspectiveCamera, campo de visión vertical |
| Cuándo dibuja | cuando algo cambia | en cada frame, desde el bucle del juego | en cada pulso | en cada frame que renderiza tu bucle |
| Ejes | dextrógiros, Y hacia arriba | dextrógiros, Y hacia arriba | Y hacia abajo | dextrógiros, Y hacia arriba |
La última fila importa cuando portas código. La documentación de WPF describe los mismos ejes que three.js: X hacia la derecha, Y hacia arriba y Z hacia el observador. La Matrix de MonoGame también es dextrógira. JavaFX 3D conserva su convención 2D, con el eje Y apuntando hacia abajo.
Las versiones son las actuales a 16 de septiembre de 2026: three.js r186, publicado en npm como three 0.186.0, con las declaraciones de tipos de @types/three 0.186.0, servido y empaquetado por Vite 8.3.0 y verificado con TypeScript 7.0.2. three.js numera sus versiones: r186 es la 186.ª, y en 2026 salió una nueva cada dos o tres meses. La API cambia entre ellas, y la guía de migración enumera lo que se rompe.
Ejecutar las escenas del curso
Sección titulada «Ejecutar las escenas del curso»El proyecto del curso en code/threejs tiene una página HTML por escena. Vite las sirve durante el desarrollo y las empaqueta para producción, como en el curso de React (Vite). Playwright las abre en Chromium headless para check.sh, que necesita su propia compilación de Chromium.
Set-Location code\threejsnpm cinpx playwright install chromium chromium-headless-shellnpm run dev# check.sh es un script de Bash: ejecútalo desde Git Bashbash check.shcd code/threejsnpm ci# --with-deps también instala las bibliotecas del sistema que necesita Chromium (sudo)npx playwright install --with-deps chromium chromium-headless-shellnpm run devbash check.shcd code/threejsnpm cinpx playwright install chromium chromium-headless-shellnpm run devbash check.shnpm run dev sirve las páginas en el puerto 5188: abre http://localhost:5188/01-scene.html. Para empezar un proyecto propio, three es un paquete npm corriente, y @types/three da a TypeScript sus tipos:
npm install three@0.186.0npm install --save-dev @types/three@0.186.0Tres objetos: un renderer, una escena y una cámara
Sección titulada «Tres objetos: un renderer, una escena y una cámara»Toda página three.js crea los mismos tres objetos. Este es el comienzo de la página de la lección (src/01-scene/main.ts, líneas 1-21); el módulo probe.ts que importa es el punto de enganche de pruebas del curso, descrito más abajo:
// Lección 1: una escena, una cámara, un renderer, un bucle de animación y un canvas que sigue a la ventanaimport * as THREE from 'three/webgpu';import { backendName, forceWebGL, probing, report } from '../probe.ts';
const renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL });renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));renderer.setSize(window.innerWidth, window.innerHeight);document.body.append(renderer.domElement);// ?linear: escribe valores lineales en el canvas, lo que elimina el pase de salida (la lección 3 explica por qué existe)if (new URLSearchParams(location.search).has('linear')) renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
const scene = new THREE.Scene();scene.background = new THREE.Color(0x1e2127);
// Campo de visión vertical de 50°, la relación de aspecto de la ventana, y se dibuja todo lo que está entre 0.1 y 100 unidadesconst camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);camera.position.set(0, 1.5, 4);camera.lookAt(0, 0, 0);
const cube = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial());scene.add(cube);- El renderer posee el
<canvas>, enrenderer.domElement, y el dispositivo de GPU.setSizefija el tamaño del canvas en píxeles CSS, ysetPixelRatiolo multiplica para el buffer de dibujo: en una pantalla al 200 %, un canvas de 800 por 450 dibuja 1600 por 900 píxeles. El curso limita la proporción a 2, porque cada paso hacia arriba multiplica los píxeles que hay que sombrear. - La escena es la raíz de un árbol de objetos, como
Viewport3D.Childrenen WPF.Scene,Mesh,Group, las luces y las cámaras extienden todasObject3D, que lleva una posición, una rotación, una escala y una lista de hijos. - La cámara también es un
Object3D, pero no necesita estar en la escena:renderer.render(scene, camera)recibe ambas.PerspectiveCamerarecibe un campo de visión vertical en grados, una relación de aspecto, y las distancias cercana y lejana entre las que se dibujan los objetos. - Una malla es una geometría y un material.
MeshNormalMaterialcolorea cada cara según la dirección hacia la que mira y no necesita luz, lo que lo convierte en el material “hola mundo”. La lección 2 construye materiales de verdad.

Página en vivo: ejecutarla aquí
Abrir a pantalla completa. Pruébalo: el control cubes del panel recarga la página con ?cubes=: ponlo en 3 para la escena del ejercicio 1. Abierta a pantalla completa, la página muestra en el título de la pestaña el backend que eligió, la respuesta al ejercicio 3.
La captura de pantalla es el tercer frame de la página, tomada con node scripts/probe.mjs 01-scene --shot out/cube.png, con WebGPU en la máquina del autor.
WebGPURenderer y su alternativa
Sección titulada «WebGPURenderer y su alternativa»three.js tiene dos renderers. WebGLRenderer, importado desde 'three', ha sido el renderer durante más de una década. WebGPURenderer, importado desde 'three/webgpu', es el nuevo: habla con WebGPU cuando el navegador lo ofrece, y con WebGL 2 en caso contrario. La página WebGPURenderer del manual lo llama el “renderer de nueva generación”, todavía en estado experimental, y dice que WebGLRenderer sigue mantenido pero sin grandes funciones nuevas, ya que el proyecto se centra en WebGPURenderer. El curso usa WebGPURenderer desde la primera lección: los materiales de nodos de la lección 6 y el posprocesado de la lección 7 solo existen ahí.
La elección ocurre en el constructor (WebGPURenderer.js en r186, líneas 57-73):
if ( parameters.forceWebGL ) {
BackendClass = WebGLBackend;
} else {
BackendClass = WebGPUBackend;
parameters.getFallback = () => {
warn( 'WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' );
return new WebGLBackend( parameters );
};
}El dispositivo WebGPU se solicita de forma asíncrona, así que un renderer no está listo cuando su constructor termina. renderer.init() devuelve una promesa que se resuelve en cuanto un backend funciona, y setAnimationLoop llama a init() por sí mismo antes de arrancar el bucle (Renderer.js, líneas 2067-2073). El await renderer.init() al final de la página solo lo espera para escribir el backend en el título de la pestaña.
Qué backend se ejecuta depende del navegador y de la máquina, no del código. scripts/probe.mjs abre la página con ?probe; tras tres frames, la página informa del backend, de la GPU que respondió y de los contadores del renderer. En la máquina del autor (Windows 11, una NVIDIA GeForce RTX 5080), en el modo headless de Chromium 153:
$ node scripts/probe.mjs 01-scene{ "backend": "WebGPU", "gpu": "nvidia, blackwell", "frame": 4, "render": { "calls": 6, "drawCalls": 2, "triangles": 13 }, "memory": { "geometries": 2, "textures": 2, "programs": 4 }, "canvas": [800, 450], "pixelRatio": 1, "coordinateSystem": "WebGPU"}Con ?webgl, la página pasa forceWebGL: true. Los contadores son los mismos; ahora se llega a la GPU a través de ANGLE, que Chromium en Windows usa para ejecutar WebGL sobre Direct3D 11:
$ node scripts/probe.mjs 01-scene --webgl{ "backend": "WebGL 2", "gpu": "ANGLE (NVIDIA, NVIDIA GeForce RTX 5080 (0x00002C02) Direct3D11 vs_5_0 ps_5_0, D3D11)", ... "coordinateSystem": "WebGL"}Playwright incluye dos navegadores headless. chromium ejecuta el nuevo modo headless de Chromium, el usado arriba. chromium-headless-shell es una compilación más ligera, y en la misma máquina no tiene ningún adaptador WebGPU: el navegador imprime un aviso, three.js imprime el suyo, y la página se ejecuta en WebGL 2 a través de SwiftShader, una GPU emulada en la CPU:
$ PROBE_CHANNEL=chromium-headless-shell node scripts/probe.mjs 01-sceneconsole.warning: No available adapters.console.warning: THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.{ "backend": "WebGL 2", "gpu": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)", ...}Los runners de GitHub no tienen GPU. En cada uno de ellos, check.sh imprimió:
| Imagen del runner | Modo headless de Chromium | Headless shell de Chromium |
|---|---|---|
ubuntu-24.04 |
WebGL 2 tras el aviso de alternativa, en SwiftShader Device (Subzero) |
lo mismo |
windows-2025-vs2026 |
WebGL 2 tras el aviso de alternativa, en SwiftShader Device (Subzero) |
lo mismo |
macos-26-arm64 |
WebGPU, adaptador apple; con ?webgl, ANGLE Metal Renderer: Apple Paravirtual device |
WebGL 2 tras el aviso de alternativa, en SwiftShader Device (LLVM 10.0.0) |
El runner de macOS es una máquina virtual con una GPU Apple paravirtualizada, y Chromium ofrece WebGPU ahí; los runners de Linux y Windows no tienen GPU, e incluso el modo headless completo de Chromium recurre a SwiftShader (ejecución 35115118564).
El código no cambió, y todos los contadores fueron idénticos en todos ellos; solo unos pocos colores de píxel de la lección 3 difirieron, en una unidad. Esa es la promesa de la alternativa, y también su trampa: una página puede ejecutarse en WebGL 2 durante meses sin que nadie lo note. Registra el backend, como hace el curso en el título de la página.
coordinateSystem es el único campo que sigue al backend. El clip space de WebGL va de −1 a 1 en profundidad, el de WebGPU de 0 a 1, y el renderer construye la matriz de proyección de la cámara para el backend sobre el que se ejecuta. Tu código no lo ve, salvo que construya matrices de proyección por su cuenta.
El bucle de animación
Sección titulada «El bucle de animación»const timer = new THREE.Timer();timer.connect(document);let frames = 0;
renderer.setAnimationLoop((time) => { timer.update(time); // Sondeo: los mismos ángulos en todas las máquinas, sea cual sea la tasa de frames cube.rotation.y = probing ? frames * 0.25 : timer.getElapsed(); cube.rotation.x = cube.rotation.y / 2; renderer.render(scene, camera); frames++;setAnimationLoop llama al callback antes de cada frame que pinta el navegador, con time en milisegundos, como requestAnimationFrame, que usa por debajo. Hay tres razones para preferirlo: espera a init(), se pausa cuando la pestaña está oculta porque el navegador deja de pintar, y en WebXR (lección 11) es el visor, no la página, quien marca los frames, algo que setAnimationLoop gestiona y requestAnimationFrame no. setAnimationLoop(null) lo detiene.
No hay una separación entre Update y Draw como en MonoGame: el callback modifica la escena y luego renderiza. No se dibuja nada salvo que el callback llame a render(); a diferencia de WPF, three.js no guarda ninguna imagen de la escena, y no vuelve a dibujar por sí solo cuando un objeto cambia.
Timer mide el tiempo entre frames. Sustituyó a Clock, obsoleto desde r183, cuyo constructor ahora avisa: Clock: This module has been deprecated. Please use THREE.Timer instead. Un Timer se actualiza una vez por frame con la marca de tiempo del bucle, de modo que cada llamada a getDelta() en el mismo frame devuelve el mismo valor, y connect(document) hace que ignore el tiempo pasado en una pestaña oculta.
La sonda no usa el reloj: fija el ángulo a partir del número de frame, así que el cubo está en la misma posición en todas las máquinas, sea cual sea la tasa de frames.
Lo que cuenta renderer.info
Sección titulada «Lo que cuenta renderer.info»renderer.info es el único profiler que three.js trae integrado, y los contadores de arriba ya cuentan una historia. Hay un cubo, 12 triángulos, y sin embargo el renderer informa de 2 draw calls y 13 triángulos por frame:
render.drawCallsyrender.trianglescuentan el último frame. El renderer los reinicia al principio de cada frame de animación (Animation.js), así que la página los lee justo después derender(), dentro del bucle.render.callscuenta las llamadas arender()desde el inicio: 6 en 3 frames, 2 por frame.framevale 4 tras 3 frames renderizados, porque el bucle avanza una vez duranteinit(), antes de que se asigne el callback.
El segundo draw call, con su único triángulo, es el pase de salida. WebGPURenderer dibuja la escena en un render target interno en color lineal, y luego dibuja ese target sobre el canvas con un triángulo que cubre la pantalla, convirtiendo a sRGB y aplicando el tone mapping por el camino. El código fuente del renderer lo dice: “Unlike in WebGLRenderer, this is done in a separate render pass and not inline to achieve more correct results” (Renderer.js, líneas 1553-1561). La geometría adicional, las dos texturas adicionales (el color y la profundidad del render target) y los programas de shader adicionales vienen de ahí. Con ?linear, la página fija renderer.outputColorSpace en lineal, no hace falta ninguna conversión, y el pase desaparece:
$ node scripts/probe.mjs 01-scene --query linear{ "backend": "WebGPU", "gpu": "nvidia, blackwell", "frame": 4, "render": { "calls": 3, "drawCalls": 1, "triangles": 12 }, "memory": { "geometries": 1, "textures": 0, "programs": 2 }, ...}Los colores quedan entonces mal, lo que explica la lección 3. El mismo cubo con WebGLRenderer, desde 'three', convierte los colores dentro del shader de cada material: un draw call, sin render target. Su objeto info tiene otra forma, sin drawCalls, y calls cuenta los draw calls del último frame:
$ node scripts/probe.mjs 01-scene-webgl{ "backend": "WebGLRenderer", "render": { "frame": 3, "calls": 1, "triangles": 12 }, "memory": { "geometries": 1, "textures": 0, "programs": 1 }}El grafo de escena, sin renderer
Sección titulada «El grafo de escena, sin renderer»El núcleo de three.js funciona en Node.js: los objetos, las matemáticas y la serialización no necesitan GPU. scripts/l01-scene-graph.ts coloca una clavija de afinación dentro de una guitarra desplazada 2 unidades a lo largo de X y girada un cuarto de vuelta alrededor de Y, y luego proyecta unos cuantos puntos con una cámara a 5 unidades:
$ node scripts/l01-scene-graph.tslocal position of the peg: [ 1, 0, 0 ]matrixWorld before any update: [ 0, 0, 0 ]getWorldPosition(): [ 2, 0, -1 ][0,0,0] -> NDC [0,0,0.962][1,0,0] -> NDC [0.241,0,0.962][0,1,0] -> NDC [0,0.429,0.962][0,0,-95] -> NDC [0,0,1]toJSON: { metadata: { version: 4.7, type: 'Object', generator: 'Object3D.toJSON' }, geometries: 1, materials: 1, children: [ 'Group guitar' ]}position,rotationyscaleson locales respecto al padre, igual que unTransform3Dde WPF es local respecto a su modelo. Un cuarto de vuelta alrededor de Y lleva el eje X local a −Z en el mundo, así que la clavija acaba en (2, 0, −1).matrixWorldes una caché. Fijar una posición no cambia nada más hasta que algo recalcula las matrices:renderer.render()lo hace para toda la escena,getWorldPosition()para un objeto. El código que leematrixWorldantes del primer render lee la identidad.project(camera)da coordenadas normalizadas de dispositivo: X e Y de −1 a 1 a lo ancho de la pantalla, y una profundidad que no es lineal. El origen, a 5 unidades, ya está en 0.962, y el plano lejano, a 100 unidades, en 1. La mayor parte del rango de profundidad se va a los objetos cercanos al plano cercano, por esonearno debería ser más pequeño de lo que la escena necesita. En Node.js, la cámara usa el clip space de WebGL.toJSON()escribe la escena en el formato JSON de three.js, con las geometrías y los materiales listados una sola vez y referenciados por UUID. Es como el editor de threejs.org/editor guarda las escenas, no un formato de intercambio: la lección 4 usa glTF para eso.
Los tipos de ese JSON están mal en @types/three 0.186.0. Declaran children como un array de cadenas y ningún geometries ni materials en el nivel superior, mientras que Object3D.toJSON() escribe los hijos como objetos y añade ambos arrays. El script convierte el resultado a su propia interfaz; sin esa conversión, tsc rechaza código que funciona (errors/l01_tojson_types.ts):
errors/l01_tojson_types.ts(7,18): error TS2339: Property 'geometries' does not exist on type 'SceneJSON'.errors/l01_tojson_types.ts(8,56): error TS2339: Property 'type' does not exist on type 'string'.@types/three no lo escribe el equipo de three.js: se desarrolla en three-types/three-ts-types y se publica a través de DefinitelyTyped. Su versión sigue la de three.js, pero es una descripción separada de la biblioteca, y puede equivocarse.
Redimensionar
Sección titulada «Redimensionar»window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight);});Dos cosas deben seguir a la ventana. La relación de aspecto de la cámara, o el cubo se estira; y la cámara guarda en caché su matriz de proyección, así que updateProjectionMatrix() debe seguir a cualquier cambio de fov, aspect, near o far. Después, el canvas y su buffer de dibujo, mediante setSize. Un canvas dentro de un layout, y no a ventana completa, debería vigilar su propio tamaño con un ResizeObserver en lugar del evento resize de la ventana.
Lo que pesa el renderer
Sección titulada «Lo que pesa el renderer»vite build empaqueta las páginas para producción. La configuración de Vite del curso pone cada archivo de build de three.js en un chunk propio, así que los tamaños se pueden leer directamente:
dist/assets/three.core-p_ik4R10.js 249.85 kB │ gzip: 64.99 kBdist/assets/three.module-CFLRzNqX.js 352.34 kB │ gzip: 84.70 kBdist/assets/three.webgpu-DyXHSfxk.js 581.15 kB │ gzip: 162.14 kBLa página con WebGLRenderer carga three.core y three.module: 602 kB, 150 kB comprimidos. La página con WebGPURenderer carga three.core y three.webgpu: 831 kB, 227 kB comprimidos, para un cubo que gira. El sistema de nodos y ambos backends van dentro, los use la escena o no. Vite avisa de cualquier chunk de más de 500 kB: (!) Some chunks are larger than 500 kB after minification.
En GuitarAlchemist/ga
Sección titulada «En GuitarAlchemist/ga»El ThreeFretboard.tsx de GA crea un WebGPURenderer con samples: 8, espera a init(), y captura un error para recurrir a WebGLRenderer, en el commit 05c8eda:
await renderer.init();
// Check again after async init if (!isMounted) { renderer.dispose(); return; }
console.log('✅ ThreeFretboard: Using WebGPU renderer with 8x MSAA'); setIsWebGPU(true); } catch (error) { console.warn('ThreeFretboard: WebGPU initialization failed, falling back to WebGL:', error); renderer = null; }Las mediciones de esta lección dicen qué ocurre en una máquina sin adaptador WebGPU: init() no lanza ninguna excepción, porque WebGPURenderer ya ha cambiado a su backend WebGL 2. El catch no se ejecuta, la consola dice “Using WebGPU renderer”, y dos líneas más abajo (línea 201) el componente llama a setIsWebGPU(true) para cualquier renderer. Las sombras solo se activan para un WebGLRenderer (línea 208), así que un WebGPURenderer que se ejecuta sobre WebGL 2 no tiene ninguna. Preguntar al renderer, renderer.backend.isWebGPUBackend, daría la respuesta real. Si se aceptan 8 muestras en cada backend está por verificar: la especificación de WebGPU permite que el número de muestras de una textura sea 1 o 4.
Dos puntos más del mismo archivo y del resto de los componentes React de GA:
- La proporción de píxeles es
Math.min(window.devicePixelRatio * 3, 6)(línea 204): en una pantalla normal, tres veces más píxeles en cada dirección de los que muestra el canvas, nueve veces el trabajo de sombreado. - No hay listener de redimensionado: el ancho y el alto son dependencias del efecto de React, así que un tamaño nuevo vuelve a construir toda la escena.
- De los 139 archivos bajo
srcque importan three.js, 33 usanrequestAnimationFramey 3 usansetAnimationLoop; 29 crean unWebGLRenderery 8 unWebGPURenderer; 15 crean unClock, obsoleto desde r183. GA pide three.js 0.180, dondeClockaún no estaba obsoleto.
Estos hallazgos están en el diario.
Experimento GA
Sección titulada «Experimento GA»El experimento 15, una GPU débil, en la lección 14, renderiza un mástil en SwiftShader: con el pixel ratio de 3 de GA, un fotograma tarda 208 ms, y un controlador que baja el pixel ratio a 0.655 lo devuelve a 60 fotogramas por segundo. También muestra que powerPreference no puede elegir la GPU en Windows.
Puntos clave
Sección titulada «Puntos clave»- Una página three.js es un renderer, una escena y una cámara; no se dibuja nada salvo que el bucle llame a
render(). WebGPURendererdethree/webgpuelige WebGPU o WebGL 2 en tiempo de ejecución. El código no cambia, así que registra el backend: los navegadores headless y las máquinas sin GPU recurren a la alternativa en silencio, salvo por un aviso en la consola.- Usa
setAnimationLoopyTimer, norequestAnimationFrameyClock. renderer.infocuenta por frame y se reinicia en cada frame: conWebGPURenderer, un draw call y un triángulo corresponden al pase de salida.- Las posiciones son locales respecto al padre;
matrixWorldes una caché querender()actualiza. @types/threees un proyecto separado y puede no coincidir con la biblioteca.
Ejercicios
Sección titulada «Ejercicios»- Predice
render.drawCalls,render.trianglesymemory.geometriescuando la página muestra tres cubos que comparten una geometría y un material (?cubes=3). Compruébalo connode scripts/probe.mjs 01-scene --query cubes=3. - En
scripts/l01-scene-graph.ts, la guitarra también está escalada por 2. ¿Dónde está la clavija en el espacio del mundo? ¿Y qué posición local pondría la clavija en el origen del mundo, sin cambiar su padre? Compruébalo conscripts/l01-exercises.ts. - Abre la página en tu propio navegador con
npm run dev, y luego, en sus herramientas de desarrollo, ejecutanavigator.gpuyawait navigator.gpu?.requestAdapter(). ¿Qué backend debería elegir la página? Compáralo con el título de la pestaña.
Solución 1
$ node scripts/probe.mjs 01-scene --query cubes=3{ "backend": "WebGPU", "gpu": "nvidia, blackwell", "frame": 4, "render": { "calls": 6, "drawCalls": 4, "triangles": 37 }, "memory": { "geometries": 2, "textures": 2, "programs": 4 }, ...}Cada malla es un draw call, comparta lo que comparta: 3 para los cubos y 1 para el pase de salida, 36 triángulos más 1. La GPU guarda una sola copia de la geometría compartida, así que geometries sigue valiendo 2, y el material se compila una vez. Compartir ahorra memoria y compilación, no draw calls; la lección 8 usa instancing para dibujar muchas copias en una sola llamada.
Solución 2
$ node scripts/l01-exercises.tspeg in world space, guitar scaled by 2: [ 2, 0, -2 ]world (0, 0, 0) in the guitar's local space: [ 0, 0, -1 ]La escala se aplica antes que la rotación y la traslación: (1, 0, 0) pasa a (2, 0, 0), luego a (0, 0, −2) tras el cuarto de vuelta, y después a (2, 0, −2). worldToLocal aplica la inversa de la matrixWorld del padre: fijar peg.position en (0, 0, −1) pone la clavija en el origen.
Solución 3
La respuesta depende de tu máquina. Si navigator.gpu es undefined, el navegador no tiene WebGPU en absoluto; si requestAdapter() se resuelve en null, tiene la API pero ningún adaptador, como el headless shell de arriba. En ambos casos el título dice Lesson 1 — WebGL 2. El estado de implementación de WebGPU enumera qué navegadores y sistemas lo activan por defecto; yo solo comprobé Chromium 153 en las máquinas de arriba (por verificar en otros).
Fuentes
Sección titulada «Fuentes»- Manual de three.js: Creating a scene, Fundamentals, Responsive design, WebGPURenderer
- Documentación de three.js:
WebGPURenderer,Renderer,Object3D,PerspectiveCamera,Timer, y la guía de migración - Código fuente de three.js en r186:
WebGPURenderer.js,Renderer.js,Info.js - WebGPU, W3C, y la API WebGPU de MDN
- Microsoft: Información general sobre gráficos 3D; MonoGame:
Matrix; JavaFX:PerspectiveCamera - Playwright: Browsers