using System.Collections.Generic;
using UnityEngine;
namespace MTE
{
///
/// Create according to a
/// and assign to renderer's material.
///
/// The material must use a compatible TextureArray shader.
///
public class RuntimeTextureArrayLoader : MonoBehaviour
{
public TextureArraySettings settings;
private Texture2DArray array0, array1;
[System.NonSerialized]
private bool loaded;
private void Start()
{
if (!settings)
{
throw new System.ArgumentNullException(nameof(settings));
}
#if UNITY_EDITOR
Debug.Log($"Creating TextureArray according to TextureArraySettings {settings.name}...");
#endif
//create in-memory texture arrays
if (!CreateTextureArrays(settings, out array0, out array1))
{
Debug.LogError($"Failed to create texture array {settings.TextureArrayName}.");
return;
}
array0.name += "(temporary in-memory)";
array1.name += "(temporary in-memory)";
//assign to corresponding material properties
var meshRenderer = GetComponent();
if (!meshRenderer)
{
throw new MissingComponentException(nameof(MeshRenderer));
}
switch (settings.textureMode)
{
case TextureArrayMode.Color:
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, array0);
break;
case TextureArrayMode.ColorAndNormal:
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, array0);
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.NormalArrayPropertyName, array1);
break;
case TextureArrayMode.PBR:
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, array0);
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.RoughnessNormalAOArrayPropertyName, array1);
break;
default:
throw new System.ArgumentOutOfRangeException();
}
#if UNITY_EDITOR
Debug.Log("Finished creating TextureArray.");
#endif
loaded = true;
}
private void OnDestroy()
{
#if UNITY_EDITOR
if (loaded)
{
UnloadInEditor();
}
#endif
}
#if UNITY_EDITOR
public void LoadInEditor()
{
if (loaded)
{
return;
}
Start();
}
public void UnloadInEditor()
{
var meshRenderer = GetComponent();
if (!meshRenderer)
{
throw new MissingComponentException(nameof(MeshRenderer));
}
switch (settings.textureMode)
{
case TextureArrayMode.Color:
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, null);
break;
case TextureArrayMode.ColorAndNormal:
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, null);
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.NormalArrayPropertyName, null);
break;
case TextureArrayMode.PBR:
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName, null);
meshRenderer.sharedMaterial.SetTexture(
TextureArrayShaderPropertyNames.RoughnessNormalAOArrayPropertyName, null);
break;
default:
throw new System.ArgumentOutOfRangeException();
}
DestroyImmediate(array0);
DestroyImmediate(array1);
array0 = array1 = null;
loaded = false;
}
#endif
#region Implementation Details
private static bool CreateTextureArrays(TextureArraySettings settings,
out Texture2DArray array0, out Texture2DArray array1)
{
array0 = array1 = null;
if (settings.textureMode == TextureArrayMode.Color)
{
Texture2DArray colorArray;
CreateColorTextureArray(settings, out colorArray);
if (!colorArray)
{
Debug.LogError("Failed to create colorArray.");
return false;
}
array0 = colorArray;
}
else if (settings.textureMode == TextureArrayMode.ColorAndNormal)
{
Texture2DArray colorArray, normalArray;
CreateColorTextureArray(settings, out colorArray);
CreateNormalTextureArray(settings, out normalArray);
if (!normalArray)
{
Debug.LogError("Failed to create colorArray or normalArray");
return false;
}
array0 = colorArray;
array1 = normalArray;
}
else//TextureArrayMode.PBR
{
Texture2DArray albedoArray, roughNormalAOArray;
CreatePBRTextureArray(settings, out albedoArray, out roughNormalAOArray);
if (!roughNormalAOArray)
{
Debug.LogError("Failed to create albedoArray.");
return false;
}
if (!roughNormalAOArray)
{
Debug.LogError("Failed to create roughNormalAOArray.");
return false;
}
array0 = albedoArray;
array1 = roughNormalAOArray;
}
return true;
}
private static Texture2DArray Generate(IList textures, TextureFormat format)
{
//We use first texture size as the texture array slices' size.
// format needs to be ARGB32, RGBA32, RGB24, R8, Alpha8 or one of float formats.
Texture2DArray texture2DArray = new Texture2DArray(textures[0].width,
textures[0].height,
textures.Count,
format,
true);
for (int i = 0; i < textures.Count; i++)
{
// NOTE:
// It is able to make a Texture2DArray with "Graphics.CopyTexture()".
// However, it has a problem which is able to make Texture2DArray in Editor
// without enabling read-write settings of texture.
// And then, it causes some wrong result in build app.
// So we should make a Texture2DArray with "SetPixels()".
//
// Graphics.CopyTexture(textures[i], 0, 0, texture2DArray, i, 0);
texture2DArray.SetPixels(textures[i].GetPixels(), i);
}
texture2DArray.Apply();
return texture2DArray;
}
private static void CreateColorTextureArray(TextureArraySettings settings,
out Texture2DArray array)
{
array = null;
var layers = settings.Layers;
if (layers.Count < 2)
{
throw new System.Exception("Less than 2 layers in array settings.");
}
if (!layers.TrueForAll(l => l.Albedo != null))
{
throw new System.Exception("Some albedo texture hasn't been assigned.");
}
var albedoTextureList = new List(layers.Count);
foreach (var layer in layers)
{
var albedoTexture = layer.Albedo;
albedoTextureList.Add(albedoTexture);
}
var textureArray = Generate(albedoTextureList, TextureFormat.RGBA32);
textureArray.name = settings.TextureArrayName + TextureArraySettings.ColorArrayPostfix;
array = textureArray;
}
private static void CreateNormalTextureArray(TextureArraySettings settings,
out Texture2DArray array)
{
array = null;
var layers = settings.Layers;
if (layers.Count < 2)
{
throw new System.Exception("Less than 2 layers in array settings.");
}
if (!layers.TrueForAll(l => l.Normal != null))
{
throw new System.Exception("Some normal texture hasn't been assigned.");
}
var textureList = new List(layers.Count);
foreach (var layer in layers)
{
var texture = layer.Normal;
textureList.Add(texture);
}
var textureArray = Generate(textureList, TextureFormat.RGBA32);
textureArray.name = settings.TextureArrayName + TextureArraySettings.NormalArrayPostfix;
array = textureArray;
}
private static void CreatePBRTextureArray(TextureArraySettings settings,
out Texture2DArray albedoArray, out Texture2DArray roughNormalAOArray)
{
albedoArray = roughNormalAOArray = null;
var layers = settings.Layers;
if (layers.Count < 2)
{
throw new System.Exception("Less than 2 layers in array settings.");
}
if (!layers.TrueForAll(l => l.IsReadyForPBRTextureArray()))
{
throw new System.Exception("Some layer hasn't been fully assigned.");
}
var albedoTextureList = new List(layers.Count);
for (var layerIndex = 0; layerIndex < layers.Count; layerIndex++)
{
var layer = layers[layerIndex];
var albedoTexture = layer.Albedo;
albedoTextureList.Add(albedoTexture);
}
//create Albedo texture array
{
var textureArray = Generate(albedoTextureList, TextureFormat.RGBA32);
textureArray.name =
settings.TextureArrayName + TextureArraySettings.AlbedoArrayPostfix;
albedoArray = textureArray;
}
var roughNormalAOTextureList = new List(layers.Count);
for (var layerIndex = 0; layerIndex < layers.Count; layerIndex++)
{
var layer = layers[layerIndex];
var roughnessTexture = layer.Roughness;
var normalTexture = layer.Normal;
var aoTexture = layer.AO;
var packedTexture = MergeTextures(roughnessTexture, normalTexture, aoTexture, settings.TextureSize);
roughNormalAOTextureList.Add(packedTexture);
}
//create RoughnessNormalAO texture array
{
var textureArray = Generate(roughNormalAOTextureList, TextureFormat.RGBA32);
textureArray.name =
settings.TextureArrayName + TextureArraySettings.RoughnessNormalAOArrayPostfix;
roughNormalAOArray = textureArray;
}
}
private static Texture2D MergeTextures(Texture2D roughnessTexture, Texture2D normalTexture,
Texture2D aoTexture, int textureSize)
{
Texture2D result = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false, true);
var pixels = new Color[textureSize*textureSize];
var roughnessPixels = roughnessTexture.GetPixels();
var normalPixels = normalTexture.GetPixels();
var aoPixels = aoTexture.GetPixels();
for (var i = 0; i < pixels.Length; i++)
{
var roughness = roughnessPixels[i].r;
var normalX = normalPixels[i].r * 2 - 1;
var normalY = normalPixels[i].g * 2 - 1;
var normalZ = normalPixels[i].b * 2 - 1;
var normalVectorPossiblyNotNormalized = new Vector3(normalX, normalY, normalZ);
var normal = Vector3.Normalize(normalVectorPossiblyNotNormalized);
var normalAsRGB = normal * 0.5f + new Vector3(0.5f, 0.5f, 0.5f);
var ao = aoPixels[i].r;
pixels[i].r = roughness;
pixels[i].g = Mathf.Clamp01(normalAsRGB.x);
pixels[i].b = Mathf.Clamp01(normalAsRGB.y);
pixels[i].a = ao;
}
result.SetPixels(pixels);
result.Apply();
return result;
}
#endregion
}
}