El catálogo de efectos y su coste
Bloom, SSAO, profundidad de campo, contornos y antialiasing: qué hace cada uno por dentro, sus parámetros reales y lo que cuesta cada uno.
Efectos de luz y sombra
UnrealBloomPass
Aísla los píxeles cuya luminancia supera un umbral, los desenfoca a varias escalas y los suma de vuelta sobre la imagen. El nombre viene del algoritmo de Unreal Engine.
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';const bloom = new UnrealBloomPass( new THREE.Vector2( innerWidth, innerHeight ), // resolution 0.6, // strength 0.4, // radius 0.85 // threshold);El orden de los parámetros es ( resolution, strength, radius, threshold ), con la resolución primero, que es una fuente constante de confusión porque otros passes la ponen en otro sitio.
Por dentro construye cinco niveles de mipmap, cada uno a la mitad de resolución que el anterior, y en cada uno aplica un desenfoque gaussiano separable con kernels de 6, 10, 14, 18 y 22 muestras respectivamente. Eso son diez render targets más uno para el pase de extracción de brillo, todos en media precisión. La suma final pondera los cinco niveles según radius: valores bajos concentran el resplandor cerca de la fuente, valores altos lo extienden.
El coste es moderado precisamente por la pirámide: el nivel más caro es el primero, a media resolución, y cada nivel siguiente cuesta la cuarta parte del anterior. La serie converge, así que el total ronda un tercio del coste de un solo pase a resolución completa.
threshold merece atención. Con valores de 8 bits el umbral tiene que ser menor que 1 para que algo lo supere, y el resultado siempre es sucio porque los brillos ya venían recortados. Con los buffers de media precisión del composer, en cambio, un umbral de 1.0 o superior es perfectamente razonable y produce un bloom limpio que solo afecta a lo que de verdad es más brillante que el blanco. Es el argumento práctico más fuerte a favor de HDR en la cadena.
Su documentación advierte de algo importante: el bloom espera que el tone mapping esté activo en el renderer, porque sin él los valores sumados se recortan al llegar a pantalla y el efecto se ve como manchas planas.
needsSwap vale false: escribe en readBuffer.
SSAOPass
Oclusión ambiental en espacio de pantalla. Reconstruye la posición de cada píxel a partir del buffer de profundidad, toma un conjunto de muestras en una hemiesfera orientada por la normal, y cuenta cuántas caen detrás de geometría.
import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';const ssao = new SSAOPass( escena, camara, innerWidth, innerHeight, 32 );ssao.kernelRadius = 8;ssao.minDistance = 0.005;ssao.maxDistance = 0.1;Firma real: ( scene, camera, width = 512, height = 512, kernelSize = 32 ). Aquí la resolución va en tercera y cuarta posición, no en la primera.
Es el pass más caro del catálogo, y por tres motivos acumulados. Primero, necesita volver a renderizar la escena dos veces para generar los buffers de profundidad y normales, lo que duplica el coste de geometría de tu frame. Segundo, ejecuta kernelSize muestras por píxel, cada una con una lectura de textura dependiente y por tanto con mala localidad de caché. Tercero, el resultado es ruidoso y necesita un pase de desenfoque adicional para ser presentable.
Sus tres parámetros interactúan de forma poco intuitiva. kernelRadius está en unidades de mundo, así que depende por completo de la escala de tu escena: en una escena de un metro, 8 significa que cada punto busca oclusión en un radio de ocho metros y el resultado es una mancha gris. minDistance y maxDistance recortan el rango de profundidad que se considera oclusión y sirven para evitar los halos oscuros alrededor de las siluetas.
Para depurar, tiene un modo de salida:
ssao.output = SSAOPass.OUTPUT.SSAO; // 0 Default, 1 SSAO, 2 Blur, 3 Depth, 4 NormalneedsSwap vale false.
Lente y selección
BokehPass
Profundidad de campo. Usa el buffer de profundidad para calcular, en cada píxel, el radio del círculo de confusión, y desenfoca en consecuencia.
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';const bokeh = new BokehPass( escena, camara, { focus: 8.0, // distancia enfocada, en unidades de mundo aperture: 0.00015, // apertura: mas alta, menos profundidad de campo maxblur: 0.01 // radio maximo del desenfoque} );// Ajustar en caliente:bokeh.uniforms.focus.value = 5.0;El tercer parámetro no es opcional: el constructor lo desreferencia sin comprobarlo, así que llamarlo sin él lanza. Los valores por defecto documentados son focus: 1, aperture: 0.025 y maxblur: 1, pero en la práctica hay que ajustarlos a la escala de la escena, y aperture se mueve en rangos muy pequeños que sorprenden la primera vez.
Como el SSAO, necesita un render adicional de la escena para la profundidad. Su coste de imagen es alto porque muestrea un vecindario amplio con radio variable, lo que impide toda optimización basada en un kernel fijo.
Un aviso de comportamiento: el desenfoque por profundidad en espacio de pantalla no puede saber qué hay detrás de un objeto enfocado, así que produce siluetas duras donde debería haber sangrado. Es una limitación de la técnica, no del pass.
OutlinePass
Dibuja contornos alrededor de objetos seleccionados, con soporte para bordes ocultos.
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';const contorno = new OutlinePass( new THREE.Vector2( innerWidth, innerHeight ), // resolution primero escena, camara, [ objetoSeleccionado ]);contorno.edgeStrength = 3.0;contorno.edgeGlow = 0.0;contorno.edgeThickness = 1.0;contorno.pulsePeriod = 0;contorno.visibleEdgeColor.set( 0xffffff );contorno.hiddenEdgeColor.set( 0x190a05 );contorno.downSampleRatio = 2;Firma: ( resolution, scene, camera, selectedObjects ). Otra vez resolución primero, al contrario que SSAOPass.
Renderiza los objetos seleccionados a una máscara, detecta sus bordes y los compone. Mantiene cuatro render targets propios. Su coste depende sobre todo de cuántos objetos hay seleccionados: con la lista vacía el pass no hace prácticamente nada, lo cual es la optimización obvia cuando no hay selección activa. downSampleRatio a 2 significa que el trabajo de bordes se hace a media resolución, que casi siempre basta.
needsSwap vale false.
Antialiasing por software
Antialiasing por software, necesario porque el multisampling del contexto no funciona sobre render targets.
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';const smaa = new SMAAPass(); // sin argumentos en r184Ojo con esto: en r184 el constructor de SMAAPass no acepta parámetros. Las versiones antiguas tomaban ancho y alto; ahora se ignoran silenciosamente y la resolución llega por setSize, que el composer llama solo. Código copiado de un tutorial antiguo con new SMAAPass( innerWidth, innerHeight ) funciona por accidente, pero conviene limpiarlo.
SMAA detecta bordes, calcula pesos de mezcla consultando dos texturas precalculadas de área y búsqueda, y aplica la mezcla. Es de calidad claramente superior a FXAA, conserva mejor los detalles finos y cuesta unos tres pases de pantalla completa más la memoria de sus dos texturas de datos.
FXAA es un único pase que estima el borde a partir de la luminancia local y aplica un desenfoque direccional. Es más barato y más borroso.
La diferencia que de verdad decide dónde va cada uno es el espacio de color en el que operan, y aparece en la documentación de sus ficheros. SMAAPass dice literalmente que opera en linear-srgb y debe ejecutarse antes de OutputPass. OutputPass dice que un pass que necesite entrada en sRGB, como FXAA, debe ir después. Es la única pareja del catálogo cuyo orden relativo está documentado de forma explícita, y no es casualidad: es donde más gente se equivoca.
Tabla de coste
| Pass | Renders extra de escena | Buffers propios | Coste relativo | needsSwap |
|---|---|---|---|---|
RenderPass |
— | 0 | el de tu escena | false |
ShaderPass |
0 | 0 | 1 pase | true |
OutputPass |
0 | 0 | 1 pase | true |
FXAAPass |
0 | 0 | 1 pase | true |
SMAAPass |
0 | 2 + 2 texturas | 3 pases | true |
UnrealBloomPass |
0 | 11 | 1 a 2 pases | false |
OutlinePass |
1 a 2 | 4 | medio, según selección | false |
BokehPass |
1 | 1 | alto | true |
SSAOPass |
2 | 3 | el más alto | false |