VRMShaders.TextureFactory

This commit is contained in:
ousttrue
2021-03-25 20:23:58 +09:00
parent 53ac235201
commit b421ad7369
16 changed files with 506 additions and 487 deletions

View File

@@ -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 ヒエラルキーで使っているリソース
/// </summary>
/// <returns></returns>
public virtual void TransferOwnership(TakeOwnershipFunc take)
public virtual void TransferOwnership(Func<UnityEngine.Object, bool> take)
{
var list = new List<UnityEngine.Object>();
foreach (var mesh in Meshes)

View File

@@ -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

View File

@@ -99,9 +99,15 @@ namespace UniGLTF
/// <summary>
/// 所有権(Dispose権)を移譲する
///
/// 所有権を移動する関数。
///
/// * 所有権が移動する。return true => ImporterContext.Dispose の対象から外れる
/// * 所有権が移動しない。return false => Importer.Context.Dispose でDestroyされる
///
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(TakeOwnershipFunc take)
public void TransferOwnership(Func<UnityEngine.Object, bool> take)
{
var list = new List<Material>();
foreach (var x in m_materials)

View File

@@ -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<Material> CreateAsync(IAwaitCaller awaitCaller, GltfParser parser, int i, GetTextureAsyncFunc getTexture)
{
if (getTexture == null)
{
getTexture = (IAwaitCaller _awaitCaller, glTF _gltf, TextureImportParam _param) => Task.FromResult<Texture2D>(null);
getTexture = (TextureImportParam _param) => Task.FromResult<Texture2D>(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);

View File

@@ -12,7 +12,7 @@ namespace UniGLTF
{
if (getTexture == null)
{
getTexture = (_x, _y, _z) => Task.FromResult<Texture2D>(default);
getTexture = (_) => Task.FromResult<Texture2D>(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);

View File

@@ -1,13 +0,0 @@
namespace UniGLTF
{
/// <summary>
/// 所有権を移動する関数。
///
/// * 所有権が移動する。return true => ImporterContext.Dispose の対象から外れる
/// * 所有権が移動しない。return false => Importer.Context.Dispose でDestroyされる
///
/// </summary>
/// <param name="o">対象のオブジェクト</param>
/// <returns>所有権が移動したらtrue</returns>
public delegate bool TakeOwnershipFunc(UnityEngine.Object o);
}

View File

@@ -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<Texture2D> GetTextureAsyncFunc(TextureImportParam param);
/// <summary>
/// glTFTexture を TextureImportParam に変換する
/// </summary>
public static class GltfTextureImporter
{
static Byte[] ToArray(ArraySegment<byte> 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();
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 58277bcba49b13142acfa67d40041970
guid: c9128dde9a44e1f43a808330ebfba145
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -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<Texture2D> GetTextureAsyncFunc(IAwaitCaller awaitCaller, glTF gltf, TextureImportParam param);
public class TextureFactory : IDisposable
{
glTF m_gltf;
IStorage m_storage;
public readonly Dictionary<string, Texture2D> 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<UnityEngine.Object> 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();
}
/// <summary>
/// 所有権(Dispose権)を移譲する
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(TakeOwnershipFunc take)
{
var keys = new List<string>();
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);
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="string"></typeparam>
/// <typeparam name="TextureLoadInfo"></typeparam>
/// <returns></returns>
Dictionary<string, TextureLoadInfo> m_textureCache = new Dictionary<string, TextureLoadInfo>();
public IEnumerable<TextureLoadInfo> Textures => m_textureCache.Values;
static Byte[] ToArray(ArraySegment<byte> 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<TextureLoadInfo> 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;
}
/// <summary>
/// テクスチャーをロード、必要であれば変換して返す。
/// 同じものはキャッシュを返す
/// </summary>
/// <param name="texture_type">変換の有無を判断する: METALLIC_GLOSS_PROP</param>
/// <param name="roughnessFactor">METALLIC_GLOSS_PROPの追加パラメーター</param>
/// <param name="indices">gltf の texture index</param>
/// <returns></returns>
public async Task<Texture2D> 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();
}
}
}
}

View File

@@ -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:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ab2b998b9235dc94a90ccaf2e40a50a6
guid: a7af3a3bafe41a64db660a18b1c25097
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -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<UnityEngine.Object, bool> take)
{
// VRM 固有のリソース(ScriptableObject)
if (take(HumanoidAvatar))

View File

@@ -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);

View File

@@ -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;

View File

@@ -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<string, Texture2D> 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<UnityEngine.Object> DestroyResource()
{
Action<UnityEngine.Object> des = (UnityEngine.Object o) => UnityEngine.Object.Destroy(o);
Action<UnityEngine.Object> desi = (UnityEngine.Object o) => UnityEngine.Object.DestroyImmediate(o);
Action<UnityEngine.Object> func = Application.isPlaying
? des
: desi
;
return func;
}
public void Dispose()
{
Action<UnityEngine.Object> 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();
}
/// <summary>
/// 所有権(Dispose権)を移譲する
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(Func<UnityEngine.Object, bool> take)
{
var keys = new List<string>();
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);
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="string"></typeparam>
/// <typeparam name="TextureLoadInfo"></typeparam>
/// <returns></returns>
Dictionary<string, TextureLoadInfo> m_textureCache = new Dictionary<string, TextureLoadInfo>();
public IEnumerable<TextureLoadInfo> Textures => m_textureCache.Values;
async Task<TextureLoadInfo> 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;
}
/// <summary>
/// テクスチャーをロード、必要であれば変換して返す。
/// 同じものはキャッシュを返す
/// </summary>
/// <param name="texture_type">変換の有無を判断する: METALLIC_GLOSS_PROP</param>
/// <param name="roughnessFactor">METALLIC_GLOSS_PROPの追加パラメーター</param>
/// <param name="indices">gltf の texture index</param>
/// <returns></returns>
public async Task<Texture2D> 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();
}
}
}