概要
複数の ReflectionProbe のベイク済み Cubemap texture を CubemapArray にまとめます.
実装
エディタ拡張にしました.
using UnityEngine;
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.Rendering;
using System.Collections.Generic;
public class ProbeToCubemapArray : ScriptableWizard
{
public ReflectionProbe[] probes;
[MenuItem("Utils/Probe To Cubemap Array")]
static void Open()
{
DisplayWizard<ProbeToCubemapArray>("Probe To Cubemap Array");
}
void OnWizardCreate()
{
List<Cubemap> cubes = new (probes.Length);
foreach (var p in probes)
{
if (p != null && p.mode == ReflectionProbeMode.Baked && p.bakedTexture != null)
{
cubes.Add((Cubemap)p.bakedTexture);
}
}
if (cubes.Count == 0) return;
CubemapArray cube_array = CubemapToCubemapArray(cubes.ToArray());
var src_path = AssetDatabase.GetAssetPath(cubes[0]);
var path = Path.GetDirectoryName(src_path) + "\\" + Path.GetFileNameWithoutExtension(src_path) + "-CubemapArray.asset";
path = AssetDatabase.GenerateUniqueAssetPath(path);
AssetDatabase.CreateAsset(cube_array, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"Saved as \"{path}\"");
}
public static CubemapArray CubemapToCubemapArray(Cubemap[] cubes)
{
var cube_array = new CubemapArray(cubes[0].width, cubes.Length, TextureFormat.BC6H, cubes[0].mipmapCount, false);
for (int i = 0; i < cube_array.cubemapCount; i++)
{
bool readable = cubes[i].isReadable;
if (!readable) SetReadable(cubes[i], true);
// BC6H は mipmap が自動生成されないので,すべてコピーする.
for (int mip = 0; mip < cube_array.mipmapCount; mip++)
{
for (int f = 0; f < 6; f++)
{
cube_array.SetPixelData(cubes[i].GetPixelData<byte>(mip, (CubemapFace)f), mip, (CubemapFace)f, i);
}
}
if (!readable) SetReadable(cubes[i], false);
}
cube_array.Apply(false, true);
cube_array.filterMode = FilterMode.Trilinear;
cube_array.anisoLevel = 0;
cube_array.wrapMode = TextureWrapMode.Clamp;
return cube_array;
}
static void SetReadable(Texture texture, bool isReadable)
{
if (texture.isReadable == isReadable)
{
return;
}
var path = AssetDatabase.GetAssetPath(texture);
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (null == importer)
{
return;
}
importer.isReadable = isReadable;
importer.SaveAndReimport();
}
}
#endif
解説
ReflectionProbe をベイクすると,生成される Cubemap は BC6H というフォーマットになります(HDRの場合).圧縮率が高く優秀なので,CubemapArray も BC6H で作成します.
Texture2D.ReadPixels() は BC6H に対応していないようなので,Get/SetPixel 系の関数で地道にコピーしていきます.その際,Get/SetPixel するテクスチャは readable である必要があります.
Texture.Apply(true) を実行すると mipmap が自動生成されますが,BC6H は mipmap の自動生成ができないため,すべての mipmap をコピーしていきます.
CubemapArray は拡張子を .asset として保存します.ファイルサイズは単純に元の Cubemap のサイズの総和になるようです.
雑記
CubemapArray を調べてみると,情報が非常に少なかったです.それもそのはずで,Culling が効くように適切に Scene を構成していればそもそも CubemapArray は必要ありません.ではなぜ CubemapArray が必要になったのか,という話は,また CubemapArray の使い方の記事でしようと思います.