What is a material properties database?

MaterialPropertyBlock en Unity: La Guía Definitiva

16/12/2021

Valoración: 4.73 (9874 votos)

En el mundo del desarrollo de videojuegos con Unity, la optimización es un pilar fundamental. Cada decisión que tomamos, desde la lógica del juego hasta la forma en que se renderizan los gráficos, tiene un impacto directo en el rendimiento. Uno de los cuellos de botella más comunes y a menudo pasados por alto es la gestión de materiales. ¿Alguna vez has necesitado que cientos de objetos en tu escena, que usan el mismo material base, tengan un color, brillo o textura ligeramente diferente? La solución más intuitiva podría llevarte por un camino de bajo rendimiento. Aquí es donde entra en juego una herramienta increíblemente poderosa pero a veces subestimada: el MaterialPropertyBlock.

What are material property blocks (MPBS)?
Material property blocks (MPBs) allow us a way around that which we can use to change properties on a per object basis without Important to mention here is that if you’re using one of Unity’s new scriptable render pipelines the performance slowdown might be way less and you can even make the performance worse by using property blocks.

Este artículo es una inmersión profunda en qué son los MaterialPropertyBlocks (MPBs), por qué son cruciales para la optimización, cómo resuelven problemas complejos como el manejo de arrays de datos en shaders y las consideraciones modernas que debes tener en cuenta al usarlos con los Scriptable Render Pipelines (URP y HDRP). Prepárate para desbloquear un nuevo nivel de control y eficiencia en tus proyectos.

Índice de Contenido

El Dilema del Rendimiento: Instancia vs. Optimización

Imagina un escenario común: tienes un bosque lleno de árboles. Todos usan el mismo material para las hojas, pero quieres que cada árbol tenga una variación sutil de color para que el bosque se vea más natural. La primera idea que podría venir a la mente es acceder al material del renderer de cada árbol y cambiar su color. Algo como esto:

GetComponent<Renderer>().material.SetColor("_Color", nuevoColor);

Aunque este código funciona, esconde un grave problema de rendimiento. Cuando accedes a .material, Unity crea en segundo plano una nueva instancia del material original. Si haces esto para 500 árboles, acabas de crear 500 materiales nuevos en memoria. Esto tiene dos consecuencias negativas principales:

  • Aumento del uso de memoria: Cada nueva instancia de material consume recursos.
  • Ruptura del batching: El renderizado por lotes (batching) es una técnica clave de optimización en la que Unity agrupa objetos que comparten el mismo material para dibujarlos en una sola llamada (draw call). Al crear 500 materiales diferentes, impides que Unity pueda agrupar estos 500 árboles. El resultado es un aumento drástico de las draw calls, lo que impacta severamente en la CPU y, por ende, en los fotogramas por segundo (FPS).

Aquí es donde el MaterialPropertyBlock se convierte en nuestro héroe. Es una solución diseñada específicamente para este problema, permitiendo variaciones por objeto sin el coste de crear nuevas instancias de material.

¿Qué es y Cómo Funciona un MaterialPropertyBlock (MPB)?

Un MaterialPropertyBlock es, en esencia, un pequeño bloque de datos que se asocia a un renderer específico. Este bloque contiene un conjunto de propiedades (colores, valores flotantes, vectores, texturas, etc.) que sobreescribirán los valores por defecto del material que ese renderer está usando, pero solo para ese objeto en particular. Los demás objetos que usan el mismo material compartido no se verán afectados, y lo más importante, no se crea ninguna instancia de material nueva.

What is a material properties database?
This database provides detailed explanations and formulas for key material properties including elasticity, conductivity, thermal expansion, and more. Comprehensive list of material properties and physical characteristics for students, engineers, researchers, and everyone interested in learning about material science.

El flujo de trabajo es sencillo:

  1. Creas un nuevo objeto MaterialPropertyBlock.
  2. Pueblas este bloque con los datos que quieres sobreescribir usando métodos como SetColor(), SetFloat(), SetVector(), SetTexture(), etc.
  3. Asignas este bloque al renderer del objeto deseado mediante renderer.SetPropertyBlock(miBloque).

Veamos un ejemplo práctico para cambiar el color de un objeto:

// C# Script en Unity Renderer miRenderer; MaterialPropertyBlock props; void Awake() { miRenderer = GetComponent<Renderer>(); props = new MaterialPropertyBlock(); } void Start() { // Obtenemos las propiedades actuales para no sobreescribir otras si existieran. miRenderer.GetPropertyBlock(props); // Establecemos el nuevo color en el bloque. props.SetColor("_Color", Color.red); // Aplicamos el bloque de propiedades al renderer. miRenderer.SetPropertyBlock(props); }

Con este código, el objeto se volverá rojo, pero Unity seguirá considerando que usa el mismo material compartido que otros objetos, permitiendo que el batching funcione correctamente. Si quieres resetear las propiedades y volver a las del material original, simplemente llama a miRenderer.SetPropertyBlock(null);.

Manejando Datos Complejos: Arrays y Scriptable Objects

El verdadero poder de los MPB se revela cuando trabajamos con datos más complejos. Una pregunta común es cómo pasar arrays de datos a un shader, por ejemplo, un array de posiciones o de colores. Los materiales estándar y sus inspectores no ofrecen una forma directa y sencilla de asignar un array a una propiedad del shader. Intentar crear un MaterialPropertyDrawer personalizado para esto a menudo resulta en un callejón sin salida, ya que la clase MaterialProperty que recibe no está diseñada para manejar arrays de forma nativa.

Sin embargo, ¡el MaterialPropertyBlock sí soporta arrays! Puedes usar métodos como SetFloatArray(), SetVectorArray() o SetMatrixArray() para enviar conjuntos de datos completos al shader de una sola vez. Esto es extremadamente útil para técnicas avanzadas de shading, como pintar vegetación de forma procedural o pasar datos de esqueletos para instancing.

Imaginemos que tienes un ScriptableObject que contiene arrays de datos que definen ciertas características visuales. El flujo para conectarlo sería:

  1. Define tu Shader: Asegúrate de que tu shader declare los uniforms como arrays. Por ejemplo: half4 _misPuntos[64];.
  2. Crea tu ScriptableObject: Define una clase que herede de ScriptableObject para almacenar tus arrays.
  3. Crea un Componente Controlador: Desarrolla un script MonoBehaviour que tenga una referencia a tu ScriptableObject.
  4. Conecta todo con un MPB: En el MonoBehaviour, lee los arrays del ScriptableObject y úsalos para poblar un MaterialPropertyBlock. Luego, aplica ese bloque al renderer.
// Ejemplo conceptual de la lógica en el MonoBehaviour public MiDataAsset datos; private Renderer rend; private MaterialPropertyBlock mpb; void OnEnable() { rend = GetComponent<Renderer>(); mpb = new MaterialPropertyBlock(); // Pasamos el array de vectores desde el ScriptableObject al MPB mpb.SetVectorArray("_misPuntos", datos.puntosVectoriales); // Aplicamos el bloque rend.SetPropertyBlock(mpb); }

De esta forma, aunque no puedes arrastrar directamente el ScriptableObject a un campo de array en el inspector del material, puedes crear un flujo de trabajo robusto y eficiente que conecte tus datos con el shader a nivel de objeto.

Does materialpropertyblock support arrays?
There is MaterialPropertyBlock which does support arrays, but I didn't figure out how this can be used to create a custom property drawer. So, how can I create a material property and corresponding property drawer that takes my custom scriptable object as type?

Tabla Comparativa: Métodos de Modificación de Materiales

Para clarificar las diferencias, aquí tienes una tabla que resume los distintos enfoques:

MétodoUso PrincipalImpacto en RendimientoAfecta a
renderer.materialModificar un solo objeto de forma permanente.Negativo. Crea una instancia de material, rompe el batching.Únicamente al objeto específico.
renderer.sharedMaterialModificar todos los objetos que usan el material.Nulo. No crea instancias.A todos los objetos que comparten ese material. ¡Cuidado en el editor, modifica el asset!
renderer.SetPropertyBlockModificar propiedades por objeto sin coste de instanciación.Positivo. Ideal para variaciones masivas, preserva el batching (con matices).Únicamente al objeto específico, de forma temporal.

¡Cuidado! MPBs y los Scriptable Render Pipelines (URP/HDRP)

Durante años, la regla de oro fue: "usa siempre MaterialPropertyBlock para variaciones por objeto". Sin embargo, con la llegada de los Scriptable Render Pipelines (SRP) como URP y HDRP, esta afirmación necesita un matiz importante. Los SRP introdujeron un nuevo sistema de batching llamado SRP Batcher. Este sistema es extremadamente rápido, pero funciona bajo una premisa diferente: agrupa llamadas que usan la *misma variante de shader* y agrupa todas las propiedades de material en grandes búferes de GPU.

El uso de un MaterialPropertyBlock puede, en ciertos casos, romper el lote del SRP Batcher. Esto se debe a que el MPB introduce una "ruta de código lenta" donde el motor tiene que buscar y aplicar estas sobreescrituras por objeto. El resultado paradójico es que, en un proyecto que depende fuertemente del SRP Batcher, usar un MPB podría ser más lento que no usarlo. La recomendación actual es clara: debes hacer profiling. Mide el rendimiento de tu escena con y sin MPBs y comprueba qué enfoque funciona mejor para tu caso de uso específico. A menudo, para un número muy grande de variaciones, los MPBs siguen siendo la mejor opción, pero ya no es una verdad absoluta.

Preguntas Frecuentes (FAQ)

¿Cuándo debo usar un MaterialPropertyBlock?
Úsalo cuando necesites que muchos objetos que comparten un mismo material tengan propiedades únicas (como color, intensidad de emisión, etc.) y la creación de una instancia de material para cada uno sea prohibitiva para el rendimiento. Ejemplos clásicos son variaciones de color en una multitud de NPCs, diferentes niveles de humedad en rocas de un escenario, o el parpadeo individual de luces en una ciudad.
¿Un MPB puede cambiar la textura de un objeto?
Sí, absolutamente. Puedes usar miBloque.SetTexture("_MainTex", miNuevaTextura); para sobreescribir la textura principal o cualquier otra propiedad de textura definida en el shader para un objeto específico.
¿Usar un MPB es siempre más rápido que crear una instancia de material?
En el pipeline de renderizado tradicional (Built-in), casi siempre sí. En los pipelines modernos (URP/HDRP), la respuesta es "depende". Puede interferir con el SRP Batcher, por lo que es crucial medir el rendimiento en tu proyecto específico para tomar la decisión correcta.
¿Cómo "reseteo" o elimino las propiedades de un MPB de un objeto?
Para eliminar las sobreescrituras y hacer que el objeto vuelva a usar los valores por defecto de su material compartido, simplemente llama a renderer.SetPropertyBlock(null).

En conclusión, el MaterialPropertyBlock es una herramienta esencial en el arsenal de cualquier desarrollador de Unity que se preocupe por el rendimiento. Ofrece una solución elegante y eficiente para un problema muy común, permitiendo una gran riqueza visual sin sacrificar fotogramas por segundo. Aunque su relación con los nuevos pipelines de renderizado requiere un análisis más cuidadoso, comprender su funcionamiento y saber cuándo aplicarlo sigue siendo una habilidad clave para crear juegos optimizados y visualmente impactantes.

Si quieres conocer otros artículos parecidos a MaterialPropertyBlock en Unity: La Guía Definitiva puedes visitar la categoría Juegos.

Subir