diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/ImporterContext.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/ImporterContext.cs index 4569d0c59..d4a77b18e 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/ImporterContext.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/ImporterContext.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using UnityEngine; using System.Threading.Tasks; using System.Text; +using VRMShaders; namespace UniGLTF { @@ -44,7 +45,7 @@ namespace UniGLTF IEnumerable<(string, UnityEngine.Object)> externalObjectMap = null) { m_parser = parser; - m_textureFactory = new TextureFactory(GLTF, Storage, externalObjectMap); + m_textureFactory = new TextureFactory(externalObjectMap); m_materialFactory = new MaterialFactory(m_parser, externalObjectMap); } @@ -283,7 +284,7 @@ namespace UniGLTF /// Root ヒエラルキーで使っているリソース /// /// - public virtual void TransferOwnership(TakeOwnershipFunc take) + public virtual void TransferOwnership(Func take) { var list = new List(); foreach (var mesh in Meshes) diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/GltfTextureEnumerator.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/GltfTextureEnumerator.cs index 35506de87..5eff6a21c 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/GltfTextureEnumerator.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/GltfTextureEnumerator.cs @@ -56,7 +56,7 @@ namespace UniGLTF if (m.emissiveTexture != null) { var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(m.emissiveTexture); - yield return TextureFactory.CreateSRGB(parser, m.emissiveTexture.index, offset, scale); + yield return GltfTextureImporter.CreateSRGB(parser, m.emissiveTexture.index, offset, scale); } // normal diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/MaterialFactory.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/MaterialFactory.cs index 7e81d1522..322a4f04b 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/MaterialFactory.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/MaterialFactory.cs @@ -99,9 +99,15 @@ namespace UniGLTF /// /// 所有権(Dispose権)を移譲する + /// + /// 所有権を移動する関数。 + /// + /// * 所有権が移動する。return true => ImporterContext.Dispose の対象から外れる + /// * 所有権が移動しない。return false => Importer.Context.Dispose でDestroyされる + /// /// /// - public void TransferOwnership(TakeOwnershipFunc take) + public void TransferOwnership(Func take) { var list = new List(); foreach (var x in m_materials) diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/PBRMaterialItem.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/PBRMaterialItem.cs index b17721f16..9cb3827f3 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/PBRMaterialItem.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/PBRMaterialItem.cs @@ -47,7 +47,7 @@ namespace UniGLTF public static TextureImportParam BaseColorTexture(GltfParser parser, glTFMaterial src) { var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.pbrMetallicRoughness.baseColorTexture); - return TextureFactory.CreateSRGB(parser, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale); + return GltfTextureImporter.CreateSRGB(parser, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale); } public static TextureImportParam StandardTexture(GltfParser parser, glTFMaterial src) @@ -60,7 +60,7 @@ namespace UniGLTF roughnessFactor = src.pbrMetallicRoughness.roughnessFactor; } var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.pbrMetallicRoughness.metallicRoughnessTexture); - return TextureFactory.CreateStandard(parser, + return GltfTextureImporter.CreateStandard(parser, src.pbrMetallicRoughness?.metallicRoughnessTexture?.index, src.occlusionTexture?.index, offset, scale, @@ -71,14 +71,14 @@ namespace UniGLTF public static TextureImportParam NormalTexture(GltfParser parser, glTFMaterial src) { var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.normalTexture); - return TextureFactory.CreateNormal(parser, src.normalTexture.index, offset, scale); + return GltfTextureImporter.CreateNormal(parser, src.normalTexture.index, offset, scale); } public static async Task CreateAsync(IAwaitCaller awaitCaller, GltfParser parser, int i, GetTextureAsyncFunc getTexture) { if (getTexture == null) { - getTexture = (IAwaitCaller _awaitCaller, glTF _gltf, TextureImportParam _param) => Task.FromResult(null); + getTexture = (TextureImportParam _param) => Task.FromResult(null); } var material = new Material(Shader.Find(ShaderName)); @@ -106,7 +106,7 @@ namespace UniGLTF if (src.pbrMetallicRoughness.baseColorTexture != null && src.pbrMetallicRoughness.baseColorTexture.index != -1) { var param = BaseColorTexture(parser, src); - material.mainTexture = await getTexture(awaitCaller, parser.GLTF, param); + material.mainTexture = await getTexture(param); // Texture Offset and Scale MaterialFactory.SetTextureOffsetAndScale(material, "_MainTex", param.Offset, param.Scale); @@ -116,7 +116,7 @@ namespace UniGLTF { material.EnableKeyword("_METALLICGLOSSMAP"); - var texture = await getTexture(awaitCaller, parser.GLTF, standardParam); + var texture = await getTexture(standardParam); if (texture != null) { material.SetTexture(TextureImportParam.METALLIC_GLOSS_PROP, texture); @@ -141,7 +141,7 @@ namespace UniGLTF { material.EnableKeyword("_NORMALMAP"); var param = NormalTexture(parser, src); - var texture = await getTexture(awaitCaller, parser.GLTF, param); + var texture = await getTexture(param); if (texture != null) { material.SetTexture(TextureImportParam.NORMAL_PROP, texture); @@ -154,7 +154,7 @@ namespace UniGLTF if (src.occlusionTexture != null && src.occlusionTexture.index != -1) { - var texture = await getTexture(awaitCaller, parser.GLTF, standardParam); + var texture = await getTexture(standardParam); if (texture != null) { material.SetTexture(TextureImportParam.OCCLUSION_PROP, texture); @@ -180,8 +180,8 @@ namespace UniGLTF if (src.emissiveTexture != null && src.emissiveTexture.index != -1) { var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.emissiveTexture); - var param = TextureFactory.CreateSRGB(parser, src.emissiveTexture.index, offset, scale); - var texture = await getTexture(awaitCaller, parser.GLTF, param); + var param = GltfTextureImporter.CreateSRGB(parser, src.emissiveTexture.index, offset, scale); + var texture = await getTexture(param); if (texture != null) { material.SetTexture("_EmissionMap", texture); diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/UnlitMaterialItem.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/UnlitMaterialItem.cs index 96b4f6d7c..42cb2e67c 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/UnlitMaterialItem.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/UnlitMaterialItem.cs @@ -12,7 +12,7 @@ namespace UniGLTF { if (getTexture == null) { - getTexture = (_x, _y, _z) => Task.FromResult(default); + getTexture = (_) => Task.FromResult(default); } var src = parser.GLTF.materials[i]; @@ -23,7 +23,7 @@ namespace UniGLTF if (src.pbrMetallicRoughness.baseColorTexture != null) { var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.pbrMetallicRoughness.baseColorTexture); - material.mainTexture = await getTexture(awaitCaller, parser.GLTF, TextureFactory.CreateSRGB(parser, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale)); + material.mainTexture = await getTexture(GltfTextureImporter.CreateSRGB(parser, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale)); // Texture Offset and Scale MaterialFactory.SetTextureOffsetAndScale(material, "_MainTex", offset, scale); diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/TakeOwnershipFunc.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/TakeOwnershipFunc.cs deleted file mode 100644 index 4cb91856c..000000000 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/TakeOwnershipFunc.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace UniGLTF -{ - /// - /// 所有権を移動する関数。 - /// - /// * 所有権が移動する。return true => ImporterContext.Dispose の対象から外れる - /// * 所有権が移動しない。return false => Importer.Context.Dispose でDestroyされる - /// - /// - /// 対象のオブジェクト - /// 所有権が移動したらtrue - public delegate bool TakeOwnershipFunc(UnityEngine.Object o); -} diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/GltfTextureImporter.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/GltfTextureImporter.cs new file mode 100644 index 000000000..5f6ee9521 --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/GltfTextureImporter.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UnityEngine; +using VRMShaders; + + +namespace UniGLTF +{ + public delegate Task GetTextureAsyncFunc(TextureImportParam param); + + /// + /// glTFTexture を TextureImportParam に変換する + /// + public static class GltfTextureImporter + { + static Byte[] ToArray(ArraySegment bytes) + { + if (bytes.Array == null) + { + return new byte[] { }; + } + else if (bytes.Offset == 0 && bytes.Count == bytes.Array.Length) + { + return bytes.Array; + } + else + { + Byte[] result = new byte[bytes.Count]; + Buffer.BlockCopy(bytes.Array, bytes.Offset, result, 0, result.Length); + return result; + } + } + + public static TextureImportParam CreateSRGB(GltfParser parser, int textureIndex, Vector2 offset, Vector2 scale) + { + var name = CreateNameExt(parser.GLTF, textureIndex, TextureImportTypes.sRGB); + var sampler = CreateSampler(parser.GLTF, textureIndex); + GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, textureIndex))); + return new TextureImportParam(name, offset, scale, sampler, TextureImportTypes.sRGB, default, default, getTextureBytesAsync, default, default, default, default, default); + } + + public static TextureImportParam CreateNormal(GltfParser parser, int textureIndex, Vector2 offset, Vector2 scale) + { + var name = CreateNameExt(parser.GLTF, textureIndex, TextureImportTypes.NormalMap); + var sampler = CreateSampler(parser.GLTF, textureIndex); + GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, textureIndex))); + return new TextureImportParam(name, offset, scale, sampler, TextureImportTypes.NormalMap, default, default, getTextureBytesAsync, default, default, default, default, default); + } + + public static TextureImportParam CreateStandard(GltfParser parser, int? metallicRoughnessTextureIndex, int? occlusionTextureIndex, Vector2 offset, Vector2 scale, float metallicFactor, float roughnessFactor) + { + TextureImportName name = default; + + GetTextureBytesAsync getMetallicRoughnessAsync = default; + SamplerParam sampler = default; + if (metallicRoughnessTextureIndex.HasValue) + { + name = CreateNameExt(parser.GLTF, metallicRoughnessTextureIndex.Value, TextureImportTypes.StandardMap); + sampler = CreateSampler(parser.GLTF, metallicRoughnessTextureIndex.Value); + getMetallicRoughnessAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, metallicRoughnessTextureIndex.Value))); + } + + GetTextureBytesAsync getOcclusionAsync = default; + if (occlusionTextureIndex.HasValue) + { + if (string.IsNullOrEmpty(name.GltfName)) + { + name = CreateNameExt(parser.GLTF, occlusionTextureIndex.Value, TextureImportTypes.StandardMap); + } + sampler = CreateSampler(parser.GLTF, occlusionTextureIndex.Value); + getOcclusionAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, occlusionTextureIndex.Value))); + } + + return new TextureImportParam(name, offset, scale, sampler, TextureImportTypes.StandardMap, metallicFactor, roughnessFactor, getMetallicRoughnessAsync, getOcclusionAsync, default, default, default, default); + } + + public static TextureImportName CreateNameExt(glTF gltf, int textureIndex, TextureImportTypes textureType) + { + if (textureIndex < 0 || textureIndex >= gltf.textures.Count) + { + throw new ArgumentOutOfRangeException(); + } + var gltfTexture = gltf.textures[textureIndex]; + if (gltfTexture.source < 0 || gltfTexture.source >= gltf.images.Count) + { + throw new ArgumentOutOfRangeException(); + } + var gltfImage = gltf.images[gltfTexture.source]; + return new TextureImportName(textureType, gltfTexture.name, gltfImage.GetExt(), gltfImage.uri); + } + + public static SamplerParam CreateSampler(glTF gltf, int index) + { + var gltfTexture = gltf.textures[index]; + if (gltfTexture.sampler < 0 || gltfTexture.sampler >= gltf.samplers.Count) + { + // default + return new SamplerParam + { + FilterMode = FilterMode.Bilinear, + WrapModes = new (SamplerWrapType, TextureWrapMode)[] { }, + }; + } + + var gltfSampler = gltf.samplers[gltfTexture.sampler]; + return new SamplerParam + { + WrapModes = GetUnityWrapMode(gltfSampler).ToArray(), + FilterMode = ImportFilterMode(gltfSampler.minFilter), + }; + } + + public static IEnumerable<(SamplerWrapType, TextureWrapMode)> GetUnityWrapMode(glTFTextureSampler sampler) + { + if (sampler.wrapS == sampler.wrapT) + { + switch (sampler.wrapS) + { + case glWrap.NONE: // default + yield return (SamplerWrapType.All, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + yield return (SamplerWrapType.All, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return (SamplerWrapType.All, TextureWrapMode.Repeat); + break; + + case glWrap.MIRRORED_REPEAT: + yield return (SamplerWrapType.All, TextureWrapMode.Mirror); + break; + + default: + throw new NotImplementedException(); + } + } + else + { + switch (sampler.wrapS) + { + case glWrap.NONE: // default + yield return (SamplerWrapType.U, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + yield return (SamplerWrapType.U, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return (SamplerWrapType.U, TextureWrapMode.Repeat); + break; + + case glWrap.MIRRORED_REPEAT: + yield return (SamplerWrapType.U, TextureWrapMode.Mirror); + break; + + default: + throw new NotImplementedException(); + } + switch (sampler.wrapT) + { + case glWrap.NONE: // default + yield return (SamplerWrapType.V, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + yield return (SamplerWrapType.V, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return (SamplerWrapType.V, TextureWrapMode.Repeat); + break; + + case glWrap.MIRRORED_REPEAT: + yield return (SamplerWrapType.V, TextureWrapMode.Mirror); + break; + + default: + throw new NotImplementedException(); + } + } + } + + public static FilterMode ImportFilterMode(glFilter filterMode) + { + switch (filterMode) + { + case glFilter.NEAREST: + case glFilter.NEAREST_MIPMAP_LINEAR: + case glFilter.NEAREST_MIPMAP_NEAREST: + return FilterMode.Point; + + case glFilter.NONE: + case glFilter.LINEAR: + case glFilter.LINEAR_MIPMAP_NEAREST: + return FilterMode.Bilinear; + + case glFilter.LINEAR_MIPMAP_LINEAR: + return FilterMode.Trilinear; + + default: + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM/Runtime/IO/VRMTextureParam.cs.meta b/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/GltfTextureImporter.cs.meta similarity index 83% rename from Assets/VRM/Runtime/IO/VRMTextureParam.cs.meta rename to Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/GltfTextureImporter.cs.meta index 43d5a7e6d..fd3606f57 100644 --- a/Assets/VRM/Runtime/IO/VRMTextureParam.cs.meta +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/GltfTextureImporter.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 58277bcba49b13142acfa67d40041970 +guid: c9128dde9a44e1f43a808330ebfba145 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/TextureFactory.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/TextureFactory.cs deleted file mode 100644 index 29cbc287c..000000000 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/TextureFactory.cs +++ /dev/null @@ -1,447 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using System.Linq; -using System.Threading.Tasks; -using VRMShaders; - - -namespace UniGLTF -{ - [Flags] - public enum TextureLoadFlags - { - None = 0, - Used = 1, - External = 1 << 1, - } - - public struct TextureLoadInfo - { - public readonly Texture2D Texture; - public readonly TextureLoadFlags Flags; - public bool IsUsed => Flags.HasFlag(TextureLoadFlags.Used); - public bool IsExternal => Flags.HasFlag(TextureLoadFlags.External); - - public bool IsSubAsset => IsUsed && !IsExternal; - - public TextureLoadInfo(Texture2D texture, bool used, bool isExternal) - { - Texture = texture; - var flags = TextureLoadFlags.None; - if (used) - { - flags |= TextureLoadFlags.Used; - } - if (isExternal) - { - flags |= TextureLoadFlags.External; - } - Flags = flags; - } - } - - public delegate Task GetTextureAsyncFunc(IAwaitCaller awaitCaller, glTF gltf, TextureImportParam param); - public class TextureFactory : IDisposable - { - glTF m_gltf; - IStorage m_storage; - - public readonly Dictionary ExternalMap; - - public TextureFactory(glTF gltf, IStorage storage, IEnumerable<(string, UnityEngine.Object)> externalMap) - { - m_gltf = gltf; - m_storage = storage; - - if (externalMap != null) - { - ExternalMap = externalMap - .Select(kv => (kv.Item1, kv.Item2 as Texture2D)) - .Where(kv => kv.Item2 != null) - .ToDictionary(kv => kv.Item1, kv => kv.Item2); - } - } - - public void Dispose() - { - Action destroy = UnityResourceDestroyer.DestroyResource(); - foreach (var kv in m_textureCache) - { - if (!kv.Value.IsExternal) - { -#if VRM_DEVELOP - // Debug.Log($"Destroy {kv.Value.Texture}"); -#endif - destroy(kv.Value.Texture); - } - } - m_textureCache.Clear(); - } - - /// - /// 所有権(Dispose権)を移譲する - /// - /// - public void TransferOwnership(TakeOwnershipFunc take) - { - var keys = new List(); - foreach (var x in m_textureCache) - { - if (x.Value.IsUsed && !x.Value.IsExternal) - { - // マテリアルから参照されていて - // 外部のAssetからロードしていない。 - if (take(x.Value.Texture)) - { - keys.Add(x.Key); - } - } - } - foreach (var x in keys) - { - m_textureCache.Remove(x); - } - } - - /// - /// - /// - /// - /// - /// - Dictionary m_textureCache = new Dictionary(); - - public IEnumerable Textures => m_textureCache.Values; - - static Byte[] ToArray(ArraySegment bytes) - { - if (bytes.Array == null) - { - return new byte[] { }; - } - else if (bytes.Offset == 0 && bytes.Count == bytes.Array.Length) - { - return bytes.Array; - } - else - { - Byte[] result = new byte[bytes.Count]; - Buffer.BlockCopy(bytes.Array, bytes.Offset, result, 0, result.Length); - return result; - } - } - - async Task GetOrCreateBaseTexture(IAwaitCaller awaitCaller, TextureImportParam param, GetTextureBytesAsync getTextureBytesAsync, RenderTextureReadWrite colorSpace, bool used) - { - var name = param.GltfName; - if (m_textureCache.TryGetValue(name, out TextureLoadInfo cacheInfo)) - { - return cacheInfo; - } - - // not found. load new - var imageBytes = await getTextureBytesAsync(); - - // - // texture from image(png etc) bytes - // - var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false, colorSpace == RenderTextureReadWrite.Linear); - texture.name = name; - if (imageBytes != null) - { - texture.LoadImage(imageBytes); - } - - SetSampler(texture, param); - - cacheInfo = new TextureLoadInfo(texture, used, false); - m_textureCache.Add(name, cacheInfo); - return cacheInfo; - } - - public static void SetSampler(Texture2D texture, TextureImportParam param) - { - if (texture == null) - { - return; - } - - foreach (var (key, value) in param.Sampler.WrapModes) - { - switch (key) - { - case SamplerWrapType.All: - texture.wrapMode = value; - break; - - case SamplerWrapType.U: - texture.wrapModeU = value; - break; - - case SamplerWrapType.V: - texture.wrapModeV = value; - break; - - case SamplerWrapType.W: - texture.wrapModeW = value; - break; - - default: - throw new NotImplementedException(); - } - } - - texture.filterMode = param.Sampler.FilterMode; - } - - /// - /// テクスチャーをロード、必要であれば変換して返す。 - /// 同じものはキャッシュを返す - /// - /// 変換の有無を判断する: METALLIC_GLOSS_PROP - /// METALLIC_GLOSS_PROPの追加パラメーター - /// gltf の texture index - /// - public async Task GetTextureAsync(IAwaitCaller awaitCaller, glTF gltf, TextureImportParam param) - { - // - // ExtractKey で External とのマッチングを試みる - // - // Normal => GltfName - // Standard => ConvertedName - // sRGB => GltfName - // Linear => GltfName - // - if (param.Index0 != null && ExternalMap != null) - { - if (ExternalMap.TryGetValue(param.ExtractKey, out Texture2D external)) - { - return external; - } - } - - switch (param.TextureType) - { - case TextureImportTypes.NormalMap: - // Runtime/SubAsset 用に変換する - { - if (!m_textureCache.TryGetValue(param.ConvertedName, out TextureLoadInfo info)) - { - var baseTexture = await GetOrCreateBaseTexture(awaitCaller, param, param.Index0, RenderTextureReadWrite.Linear, false); - var converted = NormalConverter.Import(baseTexture.Texture); - converted.name = param.ConvertedName; - info = new TextureLoadInfo(converted, true, false); - m_textureCache.Add(converted.name, info); - } - return info.Texture; - } - - case TextureImportTypes.StandardMap: - // 変換する - { - if (!m_textureCache.TryGetValue(param.ConvertedName, out TextureLoadInfo info)) - { - TextureLoadInfo baseTexture = default; - if (param.Index0!=null) - { - baseTexture = await GetOrCreateBaseTexture(awaitCaller, param, param.Index0, RenderTextureReadWrite.Linear, false); - } - TextureLoadInfo occlusionBaseTexture = default; - if (param.Index1!=null) - { - occlusionBaseTexture = await GetOrCreateBaseTexture(awaitCaller, param, param.Index1, RenderTextureReadWrite.Linear, false); - } - var converted = OcclusionMetallicRoughnessConverter.Import(baseTexture.Texture, param.MetallicFactor, param.RoughnessFactor, occlusionBaseTexture.Texture); - converted.name = param.ConvertedName; - info = new TextureLoadInfo(converted, true, false); - m_textureCache.Add(converted.name, info); - } - return info.Texture; - } - - default: - { - var baseTexture = await GetOrCreateBaseTexture(awaitCaller, param, param.Index0, RenderTextureReadWrite.sRGB, true); - return baseTexture.Texture; - } - } - - throw new NotImplementedException(); - } - - public static TextureImportParam CreateSRGB(GltfParser parser, int textureIndex, Vector2 offset, Vector2 scale) - { - var name = CreateNameExt(parser.GLTF, textureIndex, TextureImportTypes.sRGB); - var sampler = CreateSampler(parser.GLTF, textureIndex); - GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, textureIndex))); - return new TextureImportParam(name, offset, scale, sampler, TextureImportTypes.sRGB, default, default, getTextureBytesAsync, default, default, default, default, default); - } - - public static TextureImportParam CreateNormal(GltfParser parser, int textureIndex, Vector2 offset, Vector2 scale) - { - var name = CreateNameExt(parser.GLTF, textureIndex, TextureImportTypes.NormalMap); - var sampler = CreateSampler(parser.GLTF, textureIndex); - GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, textureIndex))); - return new TextureImportParam(name, offset, scale, sampler, TextureImportTypes.NormalMap, default, default, getTextureBytesAsync, default, default, default, default, default); - } - - public static TextureImportParam CreateStandard(GltfParser parser, int? metallicRoughnessTextureIndex, int? occlusionTextureIndex, Vector2 offset, Vector2 scale, float metallicFactor, float roughnessFactor) - { - TextureImportName name = default; - - GetTextureBytesAsync getMetallicRoughnessAsync = default; - SamplerParam sampler = default; - if (metallicRoughnessTextureIndex.HasValue) - { - name = CreateNameExt(parser.GLTF, metallicRoughnessTextureIndex.Value, TextureImportTypes.StandardMap); - sampler = CreateSampler(parser.GLTF, metallicRoughnessTextureIndex.Value); - getMetallicRoughnessAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, metallicRoughnessTextureIndex.Value))); - } - - GetTextureBytesAsync getOcclusionAsync = default; - if (occlusionTextureIndex.HasValue) - { - if(string.IsNullOrEmpty(name.GltfName)){ - name = CreateNameExt(parser.GLTF, occlusionTextureIndex.Value, TextureImportTypes.StandardMap); - } - sampler = CreateSampler(parser.GLTF, occlusionTextureIndex.Value); - getOcclusionAsync = () => Task.FromResult(ToArray(parser.GLTF.GetImageBytesFromTextureIndex(parser.Storage, occlusionTextureIndex.Value))); - } - - return new TextureImportParam(name, offset, scale, sampler, TextureImportTypes.StandardMap, metallicFactor, roughnessFactor, getMetallicRoughnessAsync, getOcclusionAsync, default, default, default, default); - } - - public static TextureImportName CreateNameExt(glTF gltf, int textureIndex, TextureImportTypes textureType) - { - if (textureIndex < 0 || textureIndex >= gltf.textures.Count) - { - throw new ArgumentOutOfRangeException(); - } - var gltfTexture = gltf.textures[textureIndex]; - if (gltfTexture.source < 0 || gltfTexture.source >= gltf.images.Count) - { - throw new ArgumentOutOfRangeException(); - } - var gltfImage = gltf.images[gltfTexture.source]; - return new TextureImportName(textureType, gltfTexture.name, gltfImage.GetExt(), gltfImage.uri); - } - - public static SamplerParam CreateSampler(glTF gltf, int index) - { - var gltfTexture = gltf.textures[index]; - if (gltfTexture.sampler < 0 || gltfTexture.sampler >= gltf.samplers.Count) - { - // default - return new SamplerParam - { - FilterMode = FilterMode.Bilinear, - WrapModes = new (SamplerWrapType, TextureWrapMode)[] { }, - }; - } - - var gltfSampler = gltf.samplers[gltfTexture.sampler]; - return new SamplerParam - { - WrapModes = GetUnityWrapMode(gltfSampler).ToArray(), - FilterMode = ImportFilterMode(gltfSampler.minFilter), - }; - } - - public static IEnumerable<(SamplerWrapType, TextureWrapMode)> GetUnityWrapMode(glTFTextureSampler sampler) - { - if (sampler.wrapS == sampler.wrapT) - { - switch (sampler.wrapS) - { - case glWrap.NONE: // default - yield return (SamplerWrapType.All, TextureWrapMode.Repeat); - break; - - case glWrap.CLAMP_TO_EDGE: - yield return (SamplerWrapType.All, TextureWrapMode.Clamp); - break; - - case glWrap.REPEAT: - yield return (SamplerWrapType.All, TextureWrapMode.Repeat); - break; - - case glWrap.MIRRORED_REPEAT: - yield return (SamplerWrapType.All, TextureWrapMode.Mirror); - break; - - default: - throw new NotImplementedException(); - } - } - else - { - switch (sampler.wrapS) - { - case glWrap.NONE: // default - yield return (SamplerWrapType.U, TextureWrapMode.Repeat); - break; - - case glWrap.CLAMP_TO_EDGE: - yield return (SamplerWrapType.U, TextureWrapMode.Clamp); - break; - - case glWrap.REPEAT: - yield return (SamplerWrapType.U, TextureWrapMode.Repeat); - break; - - case glWrap.MIRRORED_REPEAT: - yield return (SamplerWrapType.U, TextureWrapMode.Mirror); - break; - - default: - throw new NotImplementedException(); - } - switch (sampler.wrapT) - { - case glWrap.NONE: // default - yield return (SamplerWrapType.V, TextureWrapMode.Repeat); - break; - - case glWrap.CLAMP_TO_EDGE: - yield return (SamplerWrapType.V, TextureWrapMode.Clamp); - break; - - case glWrap.REPEAT: - yield return (SamplerWrapType.V, TextureWrapMode.Repeat); - break; - - case glWrap.MIRRORED_REPEAT: - yield return (SamplerWrapType.V, TextureWrapMode.Mirror); - break; - - default: - throw new NotImplementedException(); - } - } - } - - public static FilterMode ImportFilterMode(glFilter filterMode) - { - switch (filterMode) - { - case glFilter.NEAREST: - case glFilter.NEAREST_MIPMAP_LINEAR: - case glFilter.NEAREST_MIPMAP_NEAREST: - return FilterMode.Point; - - case glFilter.NONE: - case glFilter.LINEAR: - case glFilter.LINEAR_MIPMAP_NEAREST: - return FilterMode.Bilinear; - - case glFilter.LINEAR_MIPMAP_LINEAR: - return FilterMode.Trilinear; - - default: - throw new NotImplementedException(); - } - } - - } -} diff --git a/Assets/VRM/Runtime/IO/VRMTextureParam.cs b/Assets/VRM/Runtime/IO/MToonTextureParam.cs similarity index 72% rename from Assets/VRM/Runtime/IO/VRMTextureParam.cs rename to Assets/VRM/Runtime/IO/MToonTextureParam.cs index 666fb2ba4..17c1aad79 100644 --- a/Assets/VRM/Runtime/IO/VRMTextureParam.cs +++ b/Assets/VRM/Runtime/IO/MToonTextureParam.cs @@ -3,19 +3,20 @@ using UniGLTF; using UnityEngine; using VRMShaders; + namespace VRM { - public static class VRMTextureParam + public static class MToonTextureParam { public static TextureImportParam Create(GltfParser parser, int index, Vector2 offset, Vector2 scale, string prop, float metallicFactor, float roughnessFactor) { switch (prop) { case TextureImportParam.NORMAL_PROP: - return TextureFactory.CreateNormal(parser, index, offset, scale); + return GltfTextureImporter.CreateNormal(parser, index, offset, scale); default: - return TextureFactory.CreateSRGB(parser, index, offset, scale); + return GltfTextureImporter.CreateSRGB(parser, index, offset, scale); case TextureImportParam.OCCLUSION_PROP: case TextureImportParam.METALLIC_GLOSS_PROP: diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/TakeOwnershipFunc.cs.meta b/Assets/VRM/Runtime/IO/MToonTextureParam.cs.meta similarity index 83% rename from Assets/UniGLTF/Runtime/UniGLTF/IO/TakeOwnershipFunc.cs.meta rename to Assets/VRM/Runtime/IO/MToonTextureParam.cs.meta index 0b34ae7e0..382c9f4ec 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/TakeOwnershipFunc.cs.meta +++ b/Assets/VRM/Runtime/IO/MToonTextureParam.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ab2b998b9235dc94a90ccaf2e40a50a6 +guid: a7af3a3bafe41a64db660a18b1c25097 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM/Runtime/IO/VRMImporterContext.cs b/Assets/VRM/Runtime/IO/VRMImporterContext.cs index e85f9f42b..9fe29dbad 100644 --- a/Assets/VRM/Runtime/IO/VRMImporterContext.cs +++ b/Assets/VRM/Runtime/IO/VRMImporterContext.cs @@ -297,7 +297,7 @@ namespace VRM meta.Title = gltfMeta.title; if (gltfMeta.texture >= 0) { - meta.Thumbnail = await TextureFactory.GetTextureAsync(awaitCaller, GLTF, TextureFactory.CreateSRGB(Parser, gltfMeta.texture, Vector2.zero, Vector2.one)); + meta.Thumbnail = await TextureFactory.GetTextureAsync(GltfTextureImporter.CreateSRGB(Parser, gltfMeta.texture, Vector2.zero, Vector2.one)); } meta.AllowedUser = gltfMeta.allowedUser; meta.ViolentUssage = gltfMeta.violentUssage; @@ -311,7 +311,7 @@ namespace VRM return meta; } - public override void TransferOwnership(TakeOwnershipFunc take) + public override void TransferOwnership(Func take) { // VRM 固有のリソース(ScriptableObject) if (take(HumanoidAvatar)) diff --git a/Assets/VRM/Runtime/IO/VRMMaterialImporter.cs b/Assets/VRM/Runtime/IO/VRMMaterialImporter.cs index 29496a5a5..38e45ee1c 100644 --- a/Assets/VRM/Runtime/IO/VRMMaterialImporter.cs +++ b/Assets/VRM/Runtime/IO/VRMMaterialImporter.cs @@ -82,8 +82,8 @@ namespace VRM scale = new Vector2(value[2], value[3]); } - var param = VRMTextureParam.Create(parser, kv.Value, offset, scale, kv.Key, 1, 1); - var texture = await getTexture(awaitCaller, parser.GLTF, param); + var param = MToonTextureParam.Create(parser, kv.Value, offset, scale, kv.Key, 1, 1); + var texture = await getTexture(param); if (texture != null) { material.SetTexture(kv.Key, texture); diff --git a/Assets/VRM/Runtime/IO/VRMTextureEnumerator.cs b/Assets/VRM/Runtime/IO/VRMTextureEnumerator.cs index 31d0da25d..20ed7f39d 100644 --- a/Assets/VRM/Runtime/IO/VRMTextureEnumerator.cs +++ b/Assets/VRM/Runtime/IO/VRMTextureEnumerator.cs @@ -35,7 +35,7 @@ namespace VRM } // SRGB color or normalmap - yield return VRMTextureParam.Create(parser, kv.Value, offset, scale, kv.Key, default, default); + yield return MToonTextureParam.Create(parser, kv.Value, offset, scale, kv.Key, default, default); } } @@ -72,7 +72,7 @@ namespace VRM // thumbnail if (m_vrm.meta != null && m_vrm.meta.texture != -1) { - var textureInfo = TextureFactory.CreateSRGB(parser, m_vrm.meta.texture, Vector2.zero, Vector2.one); + var textureInfo = GltfTextureImporter.CreateSRGB(parser, m_vrm.meta.texture, Vector2.zero, Vector2.one); if (used.Add(textureInfo.ExtractKey)) { yield return textureInfo; diff --git a/Assets/VRMShaders/Runtime/TextureFactory.cs b/Assets/VRMShaders/Runtime/TextureFactory.cs new file mode 100644 index 000000000..06e2ac35c --- /dev/null +++ b/Assets/VRMShaders/Runtime/TextureFactory.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using System.Linq; +using System.Threading.Tasks; + + +namespace VRMShaders +{ + [Flags] + public enum TextureLoadFlags + { + None = 0, + Used = 1, + External = 1 << 1, + } + + public struct TextureLoadInfo + { + public readonly Texture2D Texture; + public readonly TextureLoadFlags Flags; + public bool IsUsed => Flags.HasFlag(TextureLoadFlags.Used); + public bool IsExternal => Flags.HasFlag(TextureLoadFlags.External); + + public bool IsSubAsset => IsUsed && !IsExternal; + + public TextureLoadInfo(Texture2D texture, bool used, bool isExternal) + { + Texture = texture; + var flags = TextureLoadFlags.None; + if (used) + { + flags |= TextureLoadFlags.Used; + } + if (isExternal) + { + flags |= TextureLoadFlags.External; + } + Flags = flags; + } + } + + public class TextureFactory : IDisposable + { + public readonly Dictionary ExternalMap; + + public TextureFactory(IEnumerable<(string, UnityEngine.Object)> externalMap) + { + if (externalMap != null) + { + ExternalMap = externalMap + .Select(kv => (kv.Item1, kv.Item2 as Texture2D)) + .Where(kv => kv.Item2 != null) + .ToDictionary(kv => kv.Item1, kv => kv.Item2); + } + } + + public static Action DestroyResource() + { + Action des = (UnityEngine.Object o) => UnityEngine.Object.Destroy(o); + Action desi = (UnityEngine.Object o) => UnityEngine.Object.DestroyImmediate(o); + Action func = Application.isPlaying + ? des + : desi + ; + return func; + } + + public void Dispose() + { + Action destroy = DestroyResource(); + foreach (var kv in m_textureCache) + { + if (!kv.Value.IsExternal) + { +#if VRM_DEVELOP + // Debug.Log($"Destroy {kv.Value.Texture}"); +#endif + destroy(kv.Value.Texture); + } + } + m_textureCache.Clear(); + } + + /// + /// 所有権(Dispose権)を移譲する + /// + /// + public void TransferOwnership(Func take) + { + var keys = new List(); + foreach (var x in m_textureCache) + { + if (x.Value.IsUsed && !x.Value.IsExternal) + { + // マテリアルから参照されていて + // 外部のAssetからロードしていない。 + if (take(x.Value.Texture)) + { + keys.Add(x.Key); + } + } + } + foreach (var x in keys) + { + m_textureCache.Remove(x); + } + } + + /// + /// + /// + /// + /// + /// + Dictionary m_textureCache = new Dictionary(); + + public IEnumerable Textures => m_textureCache.Values; + + + async Task GetOrCreateBaseTexture(TextureImportParam param, GetTextureBytesAsync getTextureBytesAsync, RenderTextureReadWrite colorSpace, bool used) + { + var name = param.GltfName; + if (m_textureCache.TryGetValue(name, out TextureLoadInfo cacheInfo)) + { + return cacheInfo; + } + + // not found. load new + var imageBytes = await getTextureBytesAsync(); + + // + // texture from image(png etc) bytes + // + var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false, colorSpace == RenderTextureReadWrite.Linear); + texture.name = name; + if (imageBytes != null) + { + texture.LoadImage(imageBytes); + } + + SetSampler(texture, param); + + cacheInfo = new TextureLoadInfo(texture, used, false); + m_textureCache.Add(name, cacheInfo); + return cacheInfo; + } + + public static void SetSampler(Texture2D texture, TextureImportParam param) + { + if (texture == null) + { + return; + } + + foreach (var (key, value) in param.Sampler.WrapModes) + { + switch (key) + { + case SamplerWrapType.All: + texture.wrapMode = value; + break; + + case SamplerWrapType.U: + texture.wrapModeU = value; + break; + + case SamplerWrapType.V: + texture.wrapModeV = value; + break; + + case SamplerWrapType.W: + texture.wrapModeW = value; + break; + + default: + throw new NotImplementedException(); + } + } + + texture.filterMode = param.Sampler.FilterMode; + } + + /// + /// テクスチャーをロード、必要であれば変換して返す。 + /// 同じものはキャッシュを返す + /// + /// 変換の有無を判断する: METALLIC_GLOSS_PROP + /// METALLIC_GLOSS_PROPの追加パラメーター + /// gltf の texture index + /// + public async Task GetTextureAsync(TextureImportParam param) + { + // + // ExtractKey で External とのマッチングを試みる + // + // Normal => GltfName + // Standard => ConvertedName + // sRGB => GltfName + // Linear => GltfName + // + if (param.Index0 != null && ExternalMap != null) + { + if (ExternalMap.TryGetValue(param.ExtractKey, out Texture2D external)) + { + return external; + } + } + + switch (param.TextureType) + { + case TextureImportTypes.NormalMap: + // Runtime/SubAsset 用に変換する + { + if (!m_textureCache.TryGetValue(param.ConvertedName, out TextureLoadInfo info)) + { + var baseTexture = await GetOrCreateBaseTexture(param, param.Index0, RenderTextureReadWrite.Linear, false); + var converted = NormalConverter.Import(baseTexture.Texture); + converted.name = param.ConvertedName; + info = new TextureLoadInfo(converted, true, false); + m_textureCache.Add(converted.name, info); + } + return info.Texture; + } + + case TextureImportTypes.StandardMap: + // 変換する + { + if (!m_textureCache.TryGetValue(param.ConvertedName, out TextureLoadInfo info)) + { + TextureLoadInfo baseTexture = default; + if (param.Index0!=null) + { + baseTexture = await GetOrCreateBaseTexture(param, param.Index0, RenderTextureReadWrite.Linear, false); + } + TextureLoadInfo occlusionBaseTexture = default; + if (param.Index1!=null) + { + occlusionBaseTexture = await GetOrCreateBaseTexture(param, param.Index1, RenderTextureReadWrite.Linear, false); + } + var converted = OcclusionMetallicRoughnessConverter.Import(baseTexture.Texture, param.MetallicFactor, param.RoughnessFactor, occlusionBaseTexture.Texture); + converted.name = param.ConvertedName; + info = new TextureLoadInfo(converted, true, false); + m_textureCache.Add(converted.name, info); + } + return info.Texture; + } + + default: + { + var baseTexture = await GetOrCreateBaseTexture(param, param.Index0, RenderTextureReadWrite.sRGB, true); + return baseTexture.Texture; + } + } + + throw new NotImplementedException(); + } + + + } +} diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/TextureFactory.cs.meta b/Assets/VRMShaders/Runtime/TextureFactory.cs.meta similarity index 100% rename from Assets/UniGLTF/Runtime/UniGLTF/IO/TextureIO/TextureFactory.cs.meta rename to Assets/VRMShaders/Runtime/TextureFactory.cs.meta