top of page

Channel Combinator, una pequeña herramienta útil para tus proyectos en unity



Muchas veces que me he referido a optimización de videojuegos y toco el tema de memoria #RAM y de las memorias de la #GPU, he hablado de la combinación de canales para crear una única textura que se pueda aprovechar para todo tipo de efectos con solo una unica textura.


Los que me han seguido mucho tiempo habrán notado que me repito constantemente sobre esta recomendación, en ocasiones sé que es muy difícil de seguir porque no contamos con herramientas de dibujo, ser preciso a veces consume tiempo o simplemente porque estamos absortos en el desarrollo del videojuego en sí.


Esta vez les enseño a hacer una pequeña herramienta para el editor para acabar con este pequeño problema y de paso descansar un poco de programar shaders.


Texture Channel Combinator.cs


El objetivo de este script es poder tomar más de 2 texturas y escoger un canal de cada uno para combinarlos en una nueva textura. Para poder hacerlo:

  • Se debe permitir habilitar la lectura y escritura de cada textura que se desea modificar.

  • Todas las texturas deben compartir el mismo tipo de wrap o de envoltorio, esto es simplemente que todas deben poder repetirse o no, esto es opcional.

  • De ser posible es necesario que tengan el mismo tamaño en píxeles.

Y te preguntarás ¿cuál es la utilidad de este script?, es simple, si has trasteado bastante con texturas y shaders, te habrás dado cuenta de que muchas texturas como altura, rugosidad, dureza y muchas otras como cavitación, oclusión ambiental, ruido, entre otras. Son todas en escala de grises y es un desperdicio repetir 3 y 4 veces la misma información por cada canal, así que la propuesta de este script es poder poner en una misma textura por ejemplo la dureza, la rugosidad y la oclusión ambiental en una sola textura. Esto ayuda a reducir el uso de memoria en CPU y GPU para evitar tirones y obtener tasas de fps más estables.


Ignore la función start, solo está allí para hacer pruebas, la función principal de este script es ApplyCombination, que toma como parámetros una lista de bytes que obtendremos de los archivos del sistema.


Luego lo que hará dentro la función es convertir esos bytes en texturas, para luego combinar sus canales y retornar el resultado que es una textura escrita en bytes en los archivos del sistema.

using System.IO;
using System.Collections.Generic;
using UnityEngine;

public class TextureChannelCombinator : MonoBehaviour {

public List<Texture2D> incomeTextures;
public Texture2D outcomeTextures;
public int width;

private void Start()    {
    outcomeTextures = MergeChannels(incomeTextures, width);
    }
   
public static List<Texture2D> ConvertBytesToTextures(List<byte[]> dataBytes, int size) {
    List<Texture2D> results = new List<Texture2D>();
    foreach (byte[] data in dataBytes)        {
    
        // load the RChannel map
        Texture2D channel = new Texture2D(size, size,         TextureFormat.ARGB32, true);
        channel.LoadImage(data);
        // apply the diffuse texture
        //channel.Resize(size, size);
        channel.Apply();
        results.Add(channel);
        }
        
    return results;
    }
    
/// <summary>
/// Toma el canal R de la primera textura y lo combina con el canal G de otra textura y el canal B de otra textura.
/// </summary>

public static Texture2D MergeChannels (List<Texture2D> dataTextures, int size = 512) {
    size = Mathf.ClosestPowerOfTwo(size);
    List<Color[]> colorList = new List<Color[]>();
    
    foreach (Texture2D tex in dataTextures) {
        colorList.Add(tex.GetPixels(0, 0, size, size));
    }
    
    for (int j = 0; j < colorList[0].Length; j++)        {
        //colorList[i][0].r = colorList[][0].r;//colorList.Count-1 - i
        if(colorList.Count > 1)colorList[0][j].g = colorList[1][j].r;
        if (colorList.Count > 2) colorList[0][j].b = colorList[2][j].r;
        else colorList[0][j].b = 0;
        if (colorList.Count > 3) colorList[0][j].a = colorList[3][j].r;
        else colorList[0][j].a = 255;
    }
    
    Texture2D newTexture = new Texture2D(size, size, TextureFormat.ARGB32, false);
    newTexture.SetPixels(colorList[0]);
    newTexture.Apply();
    return newTexture;
}

public static void ApplyCombination(List<byte[]> dataBytes, string savePath, int size)    {

    List<Texture2D> dataTextures = ConvertBytesToTextures(dataBytes, size);
    // combine texture with it's normal map, saves it to a file
    Texture2D newTexture = MergeChannels(dataTextures,size);
    File.WriteAllBytes(savePath, newTexture.EncodeToPNG());
    }
}

Texture Channel Combinator Editor.cs


Esta clase editor de unity, se encarga de mostrar de forma más organizada lo que necesitamos para la combinación de texturas, lo que hace esta clase es simplemente, permitirnos escribir una dirección de ruta para nuestra textura final, nos da 4 botones para tomar las rutas donde se encuentran las imágenes.


Un enum selector, para escoger el tamaño final al que saldrá el resultado y por último el botón para ejecutar el resultado cuando todos los datos necesarios estén rellenos.


using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public enum Sizes
{
    x512 = 0,
    x1024 = 1,
    x2048 = 2,
    x4096 = 3,
    x8192 = 4
}

//[CustomEditor(typeof(TextureChannelCombinator))]
public class TextureChannelCombinatorEditor : EditorWindow {

public string savePath;
public string filename, channelRString, channelGString, channelBString, channelAString;
private bool groupEnabled, optionals;
private Sizes sizes;
            
[MenuItem("Window/Channel combiner")]
static void Init()    {
// Get existing open window or if none, make a new one:
    TextureChannelCombinatorEditor window = GetWindow(typeof(TextureChannelCombinatorEditor)) as TextureChannelCombinatorEditor;
    window.Show();
}

private void OnGUI()    {
    GUILayout.Label("Base Setting", EditorStyles.boldLabel);
    savePath = EditorGUILayout.TextField("Save Path", savePath);
    EditorGUILayout.HelpBox("Use esta ventana para mezclar canales de     texturas, ¿por qué hacerlo?: " +
        "\n - sirve para ahorrar memoria" +
        "\n - Use texturas en formato PNG" +
        "\n - hace mas manejable el proyecto al usar menos texturas" +
        "\n - Uselo en texturas en escala de grises" +
        "\n - tamaño mayor o igual a 512", MessageType.Info);
        
    if (GUILayout.Button("ChR: " + channelRString)) channelRString = EditorUtility.OpenFilePanel("Open Channel R Texture", "", "png");
    
    if (GUILayout.Button("ChG: " + channelGString)) channelGString = EditorUtility.OpenFilePanel("Open Channel G Texture", "", "png");
    
    if (GUILayout.Button("ChB: " + channelBString)) channelBString = EditorUtility.OpenFilePanel("Open Channel B Texture", "", "png");
    
    if (GUILayout.Button("ChA: " + channelAString)) channelAString = EditorUtility.OpenFilePanel("Open Channel A Texture", "", "png");
    
    optionals = (channelGString != "" || channelGString != "" || channelGString != "");
    
    groupEnabled = channelRString != "" && optionals && savePath != "";
    
    EditorGUILayout.BeginToggleGroup("Optional Settings", groupEnabled);
    
    sizes = (Sizes)EditorGUILayout.EnumPopup("Select Size", sizes);
    
    if (GUILayout.Button("Apply Combination"))        {
        if (channelRString != "" && channelBString != "" && savePath != "")            {
            List<byte[]> dataBytes = new List<byte[]>                {
                File.ReadAllBytes(channelRString),
                File.ReadAllBytes(channelGString),
                File.ReadAllBytes(channelBString),
                File.ReadAllBytes(channelAString)
            };
            
    int size = 512;
        
    switch (sizes) {
        case Sizes.x512:
            size = 512;
            break;
        case Sizes.x1024:
            size = 1024;
            break;
        case Sizes.x2048:
            size = 2048;
            break;
        case Sizes.x4096:
            size = 4096;
            break;
        case Sizes.x8192:
            size = 8192;
            break;
        }
        
    TextureChannelCombinator.ApplyCombination(dataBytes, savePath, size);
    }
    else Debug.LogWarning("Please fill data correctly");
    }
}
}

Este no es un tutorial, porque no estoy enseñando nada, solo estoy dejando el código a disposición y explicando lo que hace para que pueda modificarlo fácilmente.


Tenga en cuenta que esta pequeña herramienta aún está en beta y puede contener errores, por lo menos no contiene errores de los que sea consciente aun.


Puede modificarlo como guste, pronto subiré el archivo a una página de gitHub para actualizarlo.



53 visualizaciones0 comentarios

Entradas Recientes

Ver todo
bottom of page