using System; using System.Collections.Generic; using UnityEngine; using System.Linq; using System.Threading.Tasks; namespace VRMShaders { public class TextureFactory : IDisposable { private readonly ITextureDeserializer _textureDeserializer; private readonly IReadOnlyDictionary _externalMap; private readonly Dictionary _temporaryTextures = new Dictionary(); private readonly Dictionary _textureCache = new Dictionary(); /// /// Importer が動的に生成した Texture /// public IReadOnlyDictionary ConvertedTextures => _textureCache; /// /// 外部から渡された、すでに存在する Texture (ex. Extracted Editor Asset) /// public IReadOnlyDictionary ExternalTextures => _externalMap; public TextureFactory(ITextureDeserializer textureDeserializer, IReadOnlyDictionary externalTextures) { _textureDeserializer = textureDeserializer; _externalMap = externalTextures; } public void Dispose() { foreach (var kv in _temporaryTextures) { DestroyResource(kv.Value); } _temporaryTextures.Clear(); _textureCache.Clear(); } /// /// 所有権(Dispose権)を移譲する /// /// public void TransferOwnership(Func take) { var transferredAssets = new HashSet(); foreach (var x in _textureCache) { if (take(x.Value)) { transferredAssets.Add(x.Key); } } foreach (var key in transferredAssets) { _textureCache.Remove(key); } } /// /// テクスチャ生成情報を基に、テクスチャ生成を行う。 /// SubAssetKey が同じ場合はキャッシュを返す。 /// public async Task GetTextureAsync(TextureDescriptor texDesc) { var subAssetKey = texDesc.SubAssetKey; if (_externalMap != null && _externalMap.TryGetValue(subAssetKey, out var externalTexture)) { return externalTexture; } if (_textureCache.TryGetValue(subAssetKey, out var cachedTexture)) { return cachedTexture; } switch (texDesc.TextureType) { case TextureImportTypes.NormalMap: { // no conversion. Unity's normal map is same with glTF's. // // > contrary to Unity’s usual convention of using Y as “up” // https://docs.unity3d.com/2018.4/Documentation/Manual/StandardShaderMaterialParameterNormalMap.html var data0 = await texDesc.Index0(); var rawTexture = await _textureDeserializer.LoadTextureAsync(data0, texDesc.Sampler.EnableMipMap, ColorSpace.Linear); rawTexture.name = subAssetKey.Name; rawTexture.SetSampler(texDesc.Sampler); _textureCache.Add(subAssetKey, rawTexture); return rawTexture; } case TextureImportTypes.StandardMap: { Texture2D metallicRoughnessTexture = default; Texture2D occlusionTexture = default; if (texDesc.Index0 != null) { var data0 = await texDesc.Index0(); metallicRoughnessTexture = await _textureDeserializer.LoadTextureAsync(data0, texDesc.Sampler.EnableMipMap, ColorSpace.Linear); } if (texDesc.Index1 != null) { var data1 = await texDesc.Index1(); occlusionTexture = await _textureDeserializer.LoadTextureAsync(data1, texDesc.Sampler.EnableMipMap, ColorSpace.Linear); } var combinedTexture = OcclusionMetallicRoughnessConverter.Import(metallicRoughnessTexture, texDesc.MetallicFactor, texDesc.RoughnessFactor, occlusionTexture); combinedTexture.name = subAssetKey.Name; combinedTexture.SetSampler(texDesc.Sampler); _textureCache.Add(subAssetKey, combinedTexture); DestroyResource(metallicRoughnessTexture); DestroyResource(occlusionTexture); return combinedTexture; } case TextureImportTypes.sRGB: { var data0 = await texDesc.Index0(); var rawTexture = await _textureDeserializer.LoadTextureAsync(data0, texDesc.Sampler.EnableMipMap, ColorSpace.sRGB); rawTexture.name = subAssetKey.Name; rawTexture.SetSampler(texDesc.Sampler); _textureCache.Add(subAssetKey, rawTexture); return rawTexture; } case TextureImportTypes.Linear: { var data0 = await texDesc.Index0(); var rawTexture = await _textureDeserializer.LoadTextureAsync(data0, texDesc.Sampler.EnableMipMap, ColorSpace.Linear); rawTexture.name = subAssetKey.Name; rawTexture.SetSampler(texDesc.Sampler); _textureCache.Add(subAssetKey, rawTexture); return rawTexture; } default: throw new ArgumentOutOfRangeException(); } throw new NotImplementedException(); } private static void DestroyResource(UnityEngine.Object o) { if (Application.isPlaying) { UnityEngine.Object.Destroy(o); } else { UnityEngine.Object.DestroyImmediate(o); } } } }