Merge pull request #830 from ousttrue/feature/vrmshaders_materialfactory

マテリアル生成を VRMShaders に移動
This commit is contained in:
ousttrue 2021-03-29 16:50:16 +09:00 committed by GitHub
commit 270a2d3eed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1268 additions and 1226 deletions

View File

@ -57,17 +57,27 @@ namespace UniGLTF
s_foldTextures = EditorGUILayout.Foldout(s_foldTextures, "Remapped Textures");
if (s_foldTextures)
{
DrawRemapGUI<UnityEngine.Texture2D>(importer, GltfTextureEnumerator.Enumerate(parser).Select(x =>
{
switch (x.TextureType)
var names = GltfTextureEnumerator.Enumerate(parser)
.Select(x =>
{
case TextureImportTypes.NormalMap:
return x.GltfName;
if (x.TextureType != TextureImportTypes.StandardMap && !string.IsNullOrEmpty(x.Uri))
{
// GLTF の 無変換テクスチャーをスキップする
return null;
}
default:
return x.ConvertedName;
}
}));
switch (x.TextureType)
{
case TextureImportTypes.NormalMap:
return x.GltfName;
default:
return x.ConvertedName;
}
})
.Where(x => !string.IsNullOrEmpty(x))
;
DrawRemapGUI<UnityEngine.Texture2D>(importer, names);
}
if (GUILayout.Button("Clear"))

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using UnityEngine;
using System.Threading.Tasks;
using System.Text;
using VRMShaders;
namespace UniGLTF
{
@ -35,6 +36,8 @@ namespace UniGLTF
MaterialFactory m_materialFactory;
public MaterialFactory MaterialFactory => m_materialFactory;
public readonly GltfMaterialImporter GltfMaterialImporter = new GltfMaterialImporter();
TextureFactory m_textureFactory;
public TextureFactory TextureFactory => m_textureFactory;
@ -44,8 +47,8 @@ namespace UniGLTF
IEnumerable<(string, UnityEngine.Object)> externalObjectMap = null)
{
m_parser = parser;
m_textureFactory = new TextureFactory(GLTF, Storage, externalObjectMap);
m_materialFactory = new MaterialFactory(m_parser, externalObjectMap);
m_textureFactory = new TextureFactory(externalObjectMap);
m_materialFactory = new MaterialFactory(externalObjectMap);
}
#region Source
@ -110,7 +113,7 @@ namespace UniGLTF
using (MeasureTime("LoadMaterials"))
{
await m_materialFactory.LoadMaterialsAsync(m_awaitCaller, m_textureFactory.GetTextureAsync);
await LoadMaterialsAsync();
}
var meshImporter = new MeshImporter();
@ -170,6 +173,24 @@ namespace UniGLTF
await OnLoadModel(m_awaitCaller, MeasureTime);
}
public async Task LoadMaterialsAsync()
{
if (m_parser.GLTF.materials == null || m_parser.GLTF.materials.Count == 0)
{
// no material. work around.
var param = GltfMaterialImporter.CreateParam(m_parser, 0);
var material = await MaterialFactory.LoadAsync(param, TextureFactory.GetTextureAsync);
}
else
{
for (int i = 0; i < m_parser.GLTF.materials.Count; ++i)
{
var param = GltfMaterialImporter.CreateParam(m_parser, i);
var material = await MaterialFactory.LoadAsync(param, TextureFactory.GetTextureAsync);
}
}
}
protected virtual Task OnLoadModel(IAwaitCaller awaitCaller, Func<string, IDisposable> MeasureTime)
{
// do nothing
@ -283,7 +304,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

@ -0,0 +1,99 @@
using System.Collections.Generic;
using UnityEngine;
using VRMShaders;
namespace UniGLTF
{
public delegate bool TryCreateMaterialParamFromGltf(GltfParser parser, int i, out MaterialImportParam param);
public class GltfMaterialImporter
{
/// <summary>
/// gltfMaterialを解釈する関数。
/// 拡張するには、先頭に挿入するべし。
/// </summary>
/// <typeparam name="TryCreateMaterialParamFromGltf"></typeparam>
/// <returns></returns>
public readonly List<TryCreateMaterialParamFromGltf> GltfMaterialParamProcessors = new List<TryCreateMaterialParamFromGltf>();
public GltfMaterialImporter()
{
// unlit を試し
GltfMaterialParamProcessors.Add(GltfUnlitMaterial.TryCreateParam);
// PBR を作成する(失敗しない)
GltfMaterialParamProcessors.Add(GltfPBRMaterial.TryCreateParam);
}
public static string MaterialName(int index, glTFMaterial src)
{
if (src != null && !string.IsNullOrEmpty(src.name))
{
return src.name;
}
return $"material_{index:00}";
}
public MaterialImportParam CreateParam(GltfParser parser, int i)
{
foreach (var tryCreate in GltfMaterialParamProcessors)
{
if (tryCreate(parser, i, out MaterialImportParam param))
{
return param;
}
}
// fallback
#if VRM_DEVELOP
Debug.LogWarning($"material: {i} out of range. fallback");
#endif
return new MaterialImportParam(MaterialName(i, null), GltfPBRMaterial.ShaderName);
}
public static (Vector2, Vector2) GetTextureOffsetAndScale(glTFTextureInfo textureInfo)
{
Vector2 offset = new Vector2(0, 0);
Vector2 scale = new Vector2(1, 1);
if (glTF_KHR_texture_transform.TryGet(textureInfo, out glTF_KHR_texture_transform textureTransform))
{
if (textureTransform.offset != null && textureTransform.offset.Length == 2)
{
offset = new Vector2(textureTransform.offset[0], textureTransform.offset[1]);
}
if (textureTransform.scale != null && textureTransform.scale.Length == 2)
{
scale = new Vector2(textureTransform.scale[0], textureTransform.scale[1]);
}
offset.y = (offset.y + scale.y - 1.0f) * -1.0f;
}
return (offset, scale);
}
/// <summary>
/// for unittest
/// </summary>
public static glTF CreateMaterialForTest(glTFMaterial material)
{
return new glTF
{
materials = new System.Collections.Generic.List<glTFMaterial> {
material
},
textures = new List<glTFTexture>{
new glTFTexture{
name = "texture_0"
}
},
images = new List<glTFImage>{
new glTFImage{
name = "image_0",
mimeType = "image/png",
}
},
};
}
}
}

View File

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

View File

@ -0,0 +1,213 @@
using System.Threading.Tasks;
using UnityEngine;
using VRMShaders;
namespace UniGLTF
{
/// <summary>
/// Gltf から MaterialImportParam に変換する
///
/// StandardShader variables
///
/// _Color
/// _MainTex
/// _Cutoff
/// _Glossiness
/// _Metallic
/// _MetallicGlossMap
/// _BumpScale
/// _BumpMap
/// _Parallax
/// _ParallaxMap
/// _OcclusionStrength
/// _OcclusionMap
/// _EmissionColor
/// _EmissionMap
/// _DetailMask
/// _DetailAlbedoMap
/// _DetailNormalMapScale
/// _DetailNormalMap
/// _UVSec
/// _EmissionScaleUI
/// _EmissionColorUI
/// _Mode
/// _SrcBlend
/// _DstBlend
/// _ZWrite
///
/// </summary>
public static class GltfPBRMaterial
{
public const string ShaderName = "Standard";
private enum BlendMode
{
Opaque,
Cutout,
Fade,
Transparent
}
public static TextureImportParam BaseColorTexture(GltfParser parser, glTFMaterial src)
{
var (offset, scale) = GltfMaterialImporter.GetTextureOffsetAndScale(src.pbrMetallicRoughness.baseColorTexture);
return GltfTextureImporter.CreateSRGB(parser, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale);
}
public static TextureImportParam StandardTexture(GltfParser parser, glTFMaterial src)
{
var metallicFactor = 1.0f;
var roughnessFactor = 1.0f;
if (src.pbrMetallicRoughness != null)
{
metallicFactor = src.pbrMetallicRoughness.metallicFactor;
roughnessFactor = src.pbrMetallicRoughness.roughnessFactor;
}
var (offset, scale) = GltfMaterialImporter.GetTextureOffsetAndScale(src.pbrMetallicRoughness.metallicRoughnessTexture);
return GltfTextureImporter.CreateStandard(parser,
src.pbrMetallicRoughness?.metallicRoughnessTexture?.index,
src.occlusionTexture?.index,
offset, scale,
metallicFactor,
roughnessFactor);
}
public static TextureImportParam NormalTexture(GltfParser parser, glTFMaterial src)
{
var (offset, scale) = GltfMaterialImporter.GetTextureOffsetAndScale(src.normalTexture);
return GltfTextureImporter.CreateNormal(parser, src.normalTexture.index, offset, scale);
}
public static bool TryCreateParam(GltfParser parser, int i, out MaterialImportParam param)
{
if (i < 0 || i >= parser.GLTF.materials.Count)
{
param = default;
return false;
}
var src = parser.GLTF.materials[i];
param = new MaterialImportParam(GltfMaterialImporter.MaterialName(i, src), ShaderName);
var standardParam = default(TextureImportParam);
if (src.pbrMetallicRoughness != null || src.occlusionTexture != null)
{
if (src.pbrMetallicRoughness.metallicRoughnessTexture != null || src.occlusionTexture != null)
{
standardParam = StandardTexture(parser, src);
}
if (src.pbrMetallicRoughness.baseColorFactor != null && src.pbrMetallicRoughness.baseColorFactor.Length == 4)
{
var color = src.pbrMetallicRoughness.baseColorFactor;
param.Colors.Add("_Color", (new Color(color[0], color[1], color[2], color[3])).gamma);
}
if (src.pbrMetallicRoughness.baseColorTexture != null && src.pbrMetallicRoughness.baseColorTexture.index != -1)
{
var textureParam = BaseColorTexture(parser, src);
param.TextureSlots.Add("_MainTex", textureParam);
}
if (src.pbrMetallicRoughness.metallicRoughnessTexture != null && src.pbrMetallicRoughness.metallicRoughnessTexture.index != -1)
{
param.Actions.Add(material => material.EnableKeyword("_METALLICGLOSSMAP"));
param.TextureSlots.Add("_MetallicGlossMap", standardParam);
// Set 1.0f as hard-coded. See: https://github.com/dwango/UniVRM/issues/212.
param.FloatValues.Add("_Metallic", 1.0f);
param.FloatValues.Add("_GlossMapScale", 1.0f);
}
else
{
param.FloatValues.Add("_Metallic", src.pbrMetallicRoughness.metallicFactor);
param.FloatValues.Add("_Glossiness", 1.0f - src.pbrMetallicRoughness.roughnessFactor);
}
}
if (src.normalTexture != null && src.normalTexture.index != -1)
{
param.Actions.Add(material => material.EnableKeyword("_NORMALMAP"));
var textureParam = NormalTexture(parser, src);
param.TextureSlots.Add("_BumpMap", textureParam);
param.FloatValues.Add("_BumpScale", src.normalTexture.scale);
}
if (src.occlusionTexture != null && src.occlusionTexture.index != -1)
{
param.TextureSlots.Add("_OcclusionMap", standardParam);
param.FloatValues.Add("_OcclusionStrength", src.occlusionTexture.strength);
}
if (src.emissiveFactor != null
|| (src.emissiveTexture != null && src.emissiveTexture.index != -1))
{
param.Actions.Add(material => {
material.EnableKeyword("_EMISSION");
material.globalIlluminationFlags &= ~MaterialGlobalIlluminationFlags.EmissiveIsBlack;
});
if (src.emissiveFactor != null && src.emissiveFactor.Length == 3)
{
param.Colors.Add("_EmissionColor", new Color(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]));
}
if (src.emissiveTexture != null && src.emissiveTexture.index != -1)
{
var (offset, scale) = GltfMaterialImporter.GetTextureOffsetAndScale(src.emissiveTexture);
var textureParam = GltfTextureImporter.CreateSRGB(parser, src.emissiveTexture.index, offset, scale);
param.TextureSlots.Add("_EmissionMap", textureParam);
}
}
param.Actions.Add(material => {
BlendMode blendMode = BlendMode.Opaque;
// https://forum.unity.com/threads/standard-material-shader-ignoring-setfloat-property-_mode.344557/#post-2229980
switch (src.alphaMode)
{
case "BLEND":
blendMode = BlendMode.Fade;
material.SetOverrideTag("RenderType", "Transparent");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
break;
case "MASK":
blendMode = BlendMode.Cutout;
material.SetOverrideTag("RenderType", "TransparentCutout");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.SetFloat("_Cutoff", src.alphaCutoff);
material.EnableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 2450;
break;
default: // OPAQUE
blendMode = BlendMode.Opaque;
material.SetOverrideTag("RenderType", "");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = -1;
break;
}
material.SetFloat("_Mode", (float)blendMode);
});
return true;
}
}
}

View File

@ -42,7 +42,7 @@ namespace UniGLTF
// base color
if (m.pbrMetallicRoughness?.baseColorTexture != null)
{
yield return PBRMaterialItem.BaseColorTexture(parser, m);
yield return GltfPBRMaterial.BaseColorTexture(parser, m);
}
// metallic roughness
@ -55,14 +55,14 @@ namespace UniGLTF
// emission
if (m.emissiveTexture != null)
{
var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(m.emissiveTexture);
yield return TextureFactory.CreateSRGB(parser, m.emissiveTexture.index, offset, scale);
var (offset, scale) = GltfMaterialImporter.GetTextureOffsetAndScale(m.emissiveTexture);
yield return GltfTextureImporter.CreateSRGB(parser, m.emissiveTexture.index, offset, scale);
}
// normal
if (m.normalTexture != null)
{
yield return PBRMaterialItem.NormalTexture(parser, m);
yield return GltfPBRMaterial.NormalTexture(parser, m);
}
// occlusion
@ -75,7 +75,7 @@ namespace UniGLTF
// metallicSmooth and occlusion
if (metallicRoughnessTexture.HasValue || occlusionTexture.HasValue)
{
yield return PBRMaterialItem.StandardTexture(parser, m);
yield return GltfPBRMaterial.StandardTexture(parser, m);
}
}

View File

@ -0,0 +1,88 @@
using UnityEngine;
using VRMShaders;
namespace UniGLTF
{
public static class GltfUnlitMaterial
{
public const string ShaderName = "UniGLTF/UniUnlit";
public static bool TryCreateParam(GltfParser parser, int i, out MaterialImportParam param)
{
if (i < 0 || i >= parser.GLTF.materials.Count)
{
param = default;
return false;
}
var src = parser.GLTF.materials[i];
if (!glTF_KHR_materials_unlit.IsEnable(src))
{
param = default;
return false;
}
param = new MaterialImportParam(GltfMaterialImporter.MaterialName(i, src), ShaderName);
// texture
if (src.pbrMetallicRoughness.baseColorTexture != null)
{
var (offset, scale) = GltfMaterialImporter.GetTextureOffsetAndScale(src.pbrMetallicRoughness.baseColorTexture);
var textureParam = GltfTextureImporter.CreateSRGB(parser, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale);
param.TextureSlots.Add("_MainTex", textureParam);
}
// color
if (src.pbrMetallicRoughness.baseColorFactor != null && src.pbrMetallicRoughness.baseColorFactor.Length == 4)
{
var color = src.pbrMetallicRoughness.baseColorFactor;
param.Colors.Add("_Color", (new Color(color[0], color[1], color[2], color[3])).gamma);
}
//renderMode
param.Actions.Add(material =>
{
if (src.alphaMode == "OPAQUE")
{
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Opaque);
}
else if (src.alphaMode == "BLEND")
{
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Transparent);
}
else if (src.alphaMode == "MASK")
{
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Cutout);
material.SetFloat("_Cutoff", src.alphaCutoff);
}
else
{
// default OPAQUE
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Opaque);
}
// culling
if (src.doubleSided)
{
UniUnlit.Utils.SetCullMode(material, UniUnlit.UniUnlitCullMode.Off);
}
else
{
UniUnlit.Utils.SetCullMode(material, UniUnlit.UniUnlitCullMode.Back);
}
// VColor
var hasVertexColor = parser.GLTF.MaterialHasVertexColor(i);
if (hasVertexColor)
{
UniUnlit.Utils.SetVColBlendMode(material, UniUnlit.UniUnlitVertexColorBlendOp.Multiply);
}
UniUnlit.Utils.ValidateProperties(material, true);
});
return true;
}
}
}

View File

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

View File

@ -1,240 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
namespace UniGLTF
{
public class MaterialFactory : IDisposable
{
GltfParser m_parser;
Dictionary<string, Material> m_externalMap;
public bool TryGetExternal(int index, out Material external)
{
if (m_externalMap != null)
{
var gltfMaterial = m_parser.GLTF.materials[index];
if (m_externalMap.TryGetValue(gltfMaterial.name, out external))
{
return true;
}
}
external = default;
return false;
}
public MaterialFactory(GltfParser parser, IEnumerable<(string, UnityEngine.Object)> externalMap)
{
m_parser = parser;
if (externalMap != null)
{
m_externalMap = externalMap
.Select(kv => (kv.Item1, kv.Item2 as Material))
.Where(kv => kv.Item2 != null)
.ToDictionary(kv => kv.Item1, kv => kv.Item2)
;
}
}
public delegate Task<Material> CreateMaterialAsyncFunc(IAwaitCaller awaitCaller, GltfParser parser, int i, GetTextureAsyncFunc getTexture);
CreateMaterialAsyncFunc m_createMaterialAsync;
public CreateMaterialAsyncFunc CreateMaterialAsync
{
set
{
m_createMaterialAsync = value;
}
get
{
if (m_createMaterialAsync == null)
{
m_createMaterialAsync = MaterialFactory.DefaultCreateMaterialAsync;
}
return m_createMaterialAsync;
}
}
public struct MaterialLoadInfo
{
public readonly Material Asset;
public readonly bool UseExternal;
public bool IsSubAsset => !UseExternal;
public MaterialLoadInfo(Material asset, bool useExternal)
{
Asset = asset;
UseExternal = useExternal;
}
}
List<MaterialLoadInfo> m_materials = new List<MaterialLoadInfo>();
public IReadOnlyList<MaterialLoadInfo> Materials => m_materials;
void Remove(Material material)
{
var index = m_materials.FindIndex(x => x.Asset == material);
if (index >= 0)
{
m_materials.RemoveAt(index);
}
}
public void Dispose()
{
foreach (var x in m_materials)
{
if (!x.UseExternal)
{
// 外部の '.asset' からロードしていない
#if VRM_DEVELOP
// Debug.Log($"Destroy {x.Asset}");
#endif
UnityEngine.Object.DestroyImmediate(x.Asset, false);
}
}
}
/// <summary>
/// 所有権(Dispose権)を移譲する
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(TakeOwnershipFunc take)
{
var list = new List<Material>();
foreach (var x in m_materials)
{
if (!x.UseExternal)
{
// 外部の '.asset' からロードしていない
if (take(x.Asset))
{
list.Add(x.Asset);
}
}
}
foreach (var x in list)
{
Remove(x);
}
}
public Material GetMaterial(int index)
{
if (index < 0) return null;
if (index >= m_materials.Count) return null;
return m_materials[index].Asset;
}
/// <summary>
/// テクスチャ生成
/// </summary>
/// <param name="getTexture"></param>
/// <returns></returns>
public async Task LoadMaterialsAsync(IAwaitCaller awaitCaller, GetTextureAsyncFunc getTexture)
{
if (m_parser.GLTF.materials == null || m_parser.GLTF.materials.Count == 0)
{
// no material. work around.
var material = await CreateMaterialAsync(awaitCaller, m_parser, 0, getTexture);
m_materials.Add(new MaterialLoadInfo(material, false));
return;
}
for (int i = 0; i < m_parser.GLTF.materials.Count; ++i)
{
if (TryGetExternal(i, out Material material))
{
m_materials.Add(new MaterialLoadInfo(material, true));
continue;
}
material = await CreateMaterialAsync(awaitCaller, m_parser, i, getTexture);
m_materials.Add(new MaterialLoadInfo(material, false));
}
}
public static string MaterialName(int index, glTFMaterial src)
{
if(src!=null && !string.IsNullOrEmpty(src.name))
{
return src.name;
}
return $"material_{index:00}";
}
public static (Vector2, Vector2) GetTextureOffsetAndScale(glTFTextureInfo textureInfo)
{
Vector2 offset = new Vector2(0, 0);
Vector2 scale = new Vector2(1, 1);
if (glTF_KHR_texture_transform.TryGet(textureInfo, out glTF_KHR_texture_transform textureTransform))
{
if (textureTransform.offset != null && textureTransform.offset.Length == 2)
{
offset = new Vector2(textureTransform.offset[0], textureTransform.offset[1]);
}
if (textureTransform.scale != null && textureTransform.scale.Length == 2)
{
scale = new Vector2(textureTransform.scale[0], textureTransform.scale[1]);
}
offset.y = (offset.y + scale.y - 1.0f) * -1.0f;
}
return (offset, scale);
}
public static void SetTextureOffsetAndScale(Material material, string propertyName, Vector2 offset, Vector2 scale)
{
material.SetTextureOffset(propertyName, offset);
material.SetTextureScale(propertyName, scale);
}
public static Task<Material> DefaultCreateMaterialAsync(IAwaitCaller awaitCaller, GltfParser parser, int i, GetTextureAsyncFunc getTexture)
{
if (i < 0 || i >= parser.GLTF.materials.Count)
{
UnityEngine.Debug.LogWarning("glTFMaterial is empty");
return PBRMaterialItem.CreateAsync(awaitCaller, parser, i, getTexture);
}
var x = parser.GLTF.materials[i];
if (glTF_KHR_materials_unlit.IsEnable(x))
{
var hasVertexColor = parser.GLTF.MaterialHasVertexColor(i);
return UnlitMaterialItem.CreateAsync(awaitCaller, parser, i, getTexture, hasVertexColor);
}
return PBRMaterialItem.CreateAsync(awaitCaller, parser, i, getTexture);
}
/// <summary>
/// for unittest
/// </summary>
/// <param name="i"></param>
/// <param name="material"></param>
/// <param name="getTexture"></param>
/// <returns></returns>
public static Material CreateMaterialForTest(int i, glTFMaterial material)
{
var gltf = new glTF
{
materials = new System.Collections.Generic.List<glTFMaterial> { material },
textures = new List<glTFTexture>{
new glTFTexture{
name = "texture_0"
}
},
images = new List<glTFImage>{
new glTFImage{
name = "image_0",
mimeType = "image/png",
}
},
};
var task = DefaultCreateMaterialAsync(default(ImmediateCaller), new GltfParser{GLTF = gltf}, i, null);
return task.Result;
}
}
}

View File

@ -1,244 +0,0 @@

using System.Threading.Tasks;
using UnityEngine;
using VRMShaders;
namespace UniGLTF
{
/// StandardShader variables
///
/// _Color
/// _MainTex
/// _Cutoff
/// _Glossiness
/// _Metallic
/// _MetallicGlossMap
/// _BumpScale
/// _BumpMap
/// _Parallax
/// _ParallaxMap
/// _OcclusionStrength
/// _OcclusionMap
/// _EmissionColor
/// _EmissionMap
/// _DetailMask
/// _DetailAlbedoMap
/// _DetailNormalMapScale
/// _DetailNormalMap
/// _UVSec
/// _EmissionScaleUI
/// _EmissionColorUI
/// _Mode
/// _SrcBlend
/// _DstBlend
/// _ZWrite
public static class PBRMaterialItem
{
public const string ShaderName = "Standard";
private enum BlendMode
{
Opaque,
Cutout,
Fade,
Transparent
}
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);
}
public static TextureImportParam StandardTexture(GltfParser parser, glTFMaterial src)
{
var metallicFactor = 1.0f;
var roughnessFactor = 1.0f;
if (src.pbrMetallicRoughness != null)
{
metallicFactor = src.pbrMetallicRoughness.metallicFactor;
roughnessFactor = src.pbrMetallicRoughness.roughnessFactor;
}
var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.pbrMetallicRoughness.metallicRoughnessTexture);
return TextureFactory.CreateStandard(parser,
src.pbrMetallicRoughness?.metallicRoughnessTexture?.index,
src.occlusionTexture?.index,
offset, scale,
metallicFactor,
roughnessFactor);
}
public static TextureImportParam NormalTexture(GltfParser parser, glTFMaterial src)
{
var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.normalTexture);
return TextureFactory.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);
}
var material = new Material(Shader.Find(ShaderName));
if (i < 0 || i >= parser.GLTF.materials.Count)
{
material.name = MaterialFactory.MaterialName(i, null);
return material;
}
var src = parser.GLTF.materials[i];
material.name = MaterialFactory.MaterialName(i, src);
var standardParam = default(TextureImportParam);
if (src.pbrMetallicRoughness != null || src.occlusionTexture != null)
{
if (src.pbrMetallicRoughness.metallicRoughnessTexture != null || src.occlusionTexture != null)
{
standardParam = StandardTexture(parser, src);
}
if (src.pbrMetallicRoughness.baseColorFactor != null && src.pbrMetallicRoughness.baseColorFactor.Length == 4)
{
var color = src.pbrMetallicRoughness.baseColorFactor;
material.color = (new Color(color[0], color[1], color[2], color[3])).gamma;
}
if (src.pbrMetallicRoughness.baseColorTexture != null && src.pbrMetallicRoughness.baseColorTexture.index != -1)
{
var param = BaseColorTexture(parser, src);
material.mainTexture = await getTexture(awaitCaller, parser.GLTF, param);
// Texture Offset and Scale
MaterialFactory.SetTextureOffsetAndScale(material, "_MainTex", param.Offset, param.Scale);
}
if (src.pbrMetallicRoughness.metallicRoughnessTexture != null && src.pbrMetallicRoughness.metallicRoughnessTexture.index != -1)
{
material.EnableKeyword("_METALLICGLOSSMAP");
var texture = await getTexture(awaitCaller, parser.GLTF, standardParam);
if (texture != null)
{
material.SetTexture(TextureImportParam.METALLIC_GLOSS_PROP, texture);
}
material.SetFloat("_Metallic", 1.0f);
// Set 1.0f as hard-coded. See: https://github.com/dwango/UniVRM/issues/212.
material.SetFloat("_GlossMapScale", 1.0f);
// Texture Offset and Scale
var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.pbrMetallicRoughness.metallicRoughnessTexture);
MaterialFactory.SetTextureOffsetAndScale(material, "_MetallicGlossMap", offset, scale);
}
else
{
material.SetFloat("_Metallic", src.pbrMetallicRoughness.metallicFactor);
material.SetFloat("_Glossiness", 1.0f - src.pbrMetallicRoughness.roughnessFactor);
}
}
if (src.normalTexture != null && src.normalTexture.index != -1)
{
material.EnableKeyword("_NORMALMAP");
var param = NormalTexture(parser, src);
var texture = await getTexture(awaitCaller, parser.GLTF, param);
if (texture != null)
{
material.SetTexture(TextureImportParam.NORMAL_PROP, texture);
material.SetFloat("_BumpScale", src.normalTexture.scale);
}
// Texture Offset and Scale
MaterialFactory.SetTextureOffsetAndScale(material, "_BumpMap", param.Offset, param.Scale);
}
if (src.occlusionTexture != null && src.occlusionTexture.index != -1)
{
var texture = await getTexture(awaitCaller, parser.GLTF, standardParam);
if (texture != null)
{
material.SetTexture(TextureImportParam.OCCLUSION_PROP, texture);
material.SetFloat("_OcclusionStrength", src.occlusionTexture.strength);
}
// Texture Offset and Scale
var (offset, scale) = MaterialFactory.GetTextureOffsetAndScale(src.occlusionTexture);
MaterialFactory.SetTextureOffsetAndScale(material, "_OcclusionMap", offset, scale);
}
if (src.emissiveFactor != null
|| (src.emissiveTexture != null && src.emissiveTexture.index != -1))
{
material.EnableKeyword("_EMISSION");
material.globalIlluminationFlags &= ~MaterialGlobalIlluminationFlags.EmissiveIsBlack;
if (src.emissiveFactor != null && src.emissiveFactor.Length == 3)
{
material.SetColor("_EmissionColor", new Color(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]));
}
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);
if (texture != null)
{
material.SetTexture("_EmissionMap", texture);
}
// Texture Offset and Scale
MaterialFactory.SetTextureOffsetAndScale(material, "_EmissionMap", param.Offset, param.Scale);
}
}
BlendMode blendMode = BlendMode.Opaque;
// https://forum.unity.com/threads/standard-material-shader-ignoring-setfloat-property-_mode.344557/#post-2229980
switch (src.alphaMode)
{
case "BLEND":
blendMode = BlendMode.Fade;
material.SetOverrideTag("RenderType", "Transparent");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
break;
case "MASK":
blendMode = BlendMode.Cutout;
material.SetOverrideTag("RenderType", "TransparentCutout");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.SetFloat("_Cutoff", src.alphaCutoff);
material.EnableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 2450;
break;
default: // OPAQUE
blendMode = BlendMode.Opaque;
material.SetOverrideTag("RenderType", "");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = -1;
break;
}
material.SetFloat("_Mode", (float)blendMode);
return material;
}
}
}

View File

@ -1,80 +0,0 @@

using System.Threading.Tasks;
using UnityEngine;
namespace UniGLTF
{
public static class UnlitMaterialItem
{
public const string ShaderName = "UniGLTF/UniUnlit";
public static async Task<Material> CreateAsync(IAwaitCaller awaitCaller, GltfParser parser, int i, GetTextureAsyncFunc getTexture, bool hasVertexColor)
{
if (getTexture == null)
{
getTexture = (_x, _y, _z) => Task.FromResult<Texture2D>(default);
}
var src = parser.GLTF.materials[i];
var material = new Material(Shader.Find(ShaderName));
material.name = MaterialFactory.MaterialName(i, src);
// texture
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));
// Texture Offset and Scale
MaterialFactory.SetTextureOffsetAndScale(material, "_MainTex", offset, scale);
}
// color
if (src.pbrMetallicRoughness.baseColorFactor != null && src.pbrMetallicRoughness.baseColorFactor.Length == 4)
{
var color = src.pbrMetallicRoughness.baseColorFactor;
material.color = (new Color(color[0], color[1], color[2], color[3])).gamma;
}
//renderMode
if (src.alphaMode == "OPAQUE")
{
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Opaque);
}
else if (src.alphaMode == "BLEND")
{
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Transparent);
}
else if (src.alphaMode == "MASK")
{
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Cutout);
material.SetFloat("_Cutoff", src.alphaCutoff);
}
else
{
// default OPAQUE
UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Opaque);
}
// culling
if (src.doubleSided)
{
UniUnlit.Utils.SetCullMode(material, UniUnlit.UniUnlitCullMode.Off);
}
else
{
UniUnlit.Utils.SetCullMode(material, UniUnlit.UniUnlitCullMode.Back);
}
// VColor
if (hasVertexColor)
{
UniUnlit.Utils.SetVColBlendMode(material, UniUnlit.UniUnlitVertexColorBlendOp.Multiply);
}
UniUnlit.Utils.ValidateProperties(material, true);
return material;
}
}
}

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using UnityEngine;
using VRMShaders;
namespace UniGLTF
{

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,208 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using VRMShaders;
namespace UniGLTF
{
/// <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: 14bc3a4ce3e24924a9ae8bb1654a1b7b
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

@ -2,6 +2,7 @@
using UnityEngine;
using UniJSON;
using System.Linq;
using VRMShaders;
namespace UniGLTF
{
@ -31,12 +32,11 @@ namespace UniGLTF
var gltfMaterial = materialExporter.ExportMaterial(srcMaterial, textureManager);
gltfMaterial.pbrMetallicRoughness.baseColorTexture.extensions = gltfMaterial.pbrMetallicRoughness.baseColorTexture.extensions.Deserialize();
var dstMaterial = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual(dstMaterial.mainTextureOffset.x, offset.x, 0.3f);
Assert.AreEqual(dstMaterial.mainTextureOffset.y, offset.y, 0.2f);
Assert.AreEqual(dstMaterial.mainTextureScale.x, scale.x, 0.5f);
Assert.AreEqual(dstMaterial.mainTextureScale.y, scale.y, 0.6f);
Assert.IsTrue(glTF_KHR_texture_transform.TryGet(gltfMaterial.pbrMetallicRoughness.baseColorTexture, out glTF_KHR_texture_transform t));
Assert.AreEqual(t.offset[0], offset.x, 0.3f);
Assert.AreEqual(t.offset[1], offset.y, 0.2f);
Assert.AreEqual(t.scale[0], scale.x, 0.5f);
Assert.AreEqual(t.scale[1], scale.y, 0.6f);
}
[Test]
@ -79,8 +79,7 @@ namespace UniGLTF
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
}
@ -98,8 +97,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -116,8 +114,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -135,8 +132,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -150,8 +146,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -168,8 +163,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -187,8 +181,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -205,8 +198,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -224,8 +216,7 @@ namespace UniGLTF
},
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
{
@ -234,16 +225,14 @@ namespace UniGLTF
{
extensions = glTF_KHR_materials_unlit.Serialize().Deserialize(),
};
var material = MaterialFactory.CreateMaterialForTest(0, gltfMaterial);
Assert.AreEqual("UniGLTF/UniUnlit", material.shader.name);
Assert.IsTrue(glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
}
}
[Test]
public void MaterialImportTest()
{
var material = MaterialFactory.CreateMaterialForTest(0, new glTFMaterial { });
Assert.AreEqual("Standard", material.shader.name);
Assert.IsFalse(glTF_KHR_materials_unlit.IsEnable(new glTFMaterial { }));
}
[Test]

View File

@ -125,7 +125,7 @@ namespace VRM
//
// convert images(metallic roughness, occlusion map)
//
var task = m_context.MaterialFactory.LoadMaterialsAsync(default(ImmediateCaller), m_context.TextureFactory.GetTextureAsync);
var task = m_context.LoadMaterialsAsync();
if (!task.IsCompleted)
{
throw new Exception();

View File

@ -0,0 +1,107 @@
using System.Collections.Generic;
using UniGLTF;
using UnityEngine;
using VRMShaders;
namespace VRM
{
public class MToonMaterialImporter
{
public static bool TryCreateParam(GltfParser parser, int i, glTF_VRM_Material vrmMaterial, out MaterialImportParam param)
{
if (vrmMaterial.shader == VRM.glTF_VRM_Material.VRM_USE_GLTFSHADER)
{
// fallback to gltf
param = default;
return false;
}
//
// restore VRM material
//
// use material.name, because material name may renamed in GltfParser.
var name = parser.GLTF.materials[i].name;
param = new MaterialImportParam(name, vrmMaterial.shader);
param.Actions.Add(material => material.renderQueue = vrmMaterial.renderQueue);
foreach (var kv in vrmMaterial.floatProperties)
{
param.FloatValues.Add(kv.Key, kv.Value);
}
var offsetScaleMap = new Dictionary<string, float[]>();
foreach (var kv in vrmMaterial.vectorProperties)
{
if (vrmMaterial.textureProperties.ContainsKey(kv.Key))
{
// texture offset & scale
offsetScaleMap.Add(kv.Key, kv.Value);
}
else
{
// vector4
var v = new Vector4(kv.Value[0], kv.Value[1], kv.Value[2], kv.Value[3]);
param.Vectors.Add(kv.Key, v);
}
}
foreach (var kv in vrmMaterial.textureProperties)
{
var (offset, scale) = (Vector2.zero, Vector2.one);
if (offsetScaleMap.TryGetValue(kv.Key, out float[] value))
{
offset = new Vector2(value[0], value[1]);
scale = new Vector2(value[2], value[3]);
}
var textureParam = MToonTextureParam.Create(parser, kv.Value, offset, scale, kv.Key, 1, 1);
param.TextureSlots.Add(kv.Key, textureParam);
}
foreach (var kv in vrmMaterial.keywordMap)
{
if (kv.Value)
{
param.Actions.Add(material => material.EnableKeyword(kv.Key));
}
else
{
param.Actions.Add(material => material.DisableKeyword(kv.Key));
}
}
foreach (var kv in vrmMaterial.tagMap)
{
param.Actions.Add(material => material.SetOverrideTag(kv.Key, kv.Value));
}
if (vrmMaterial.shader == MToon.Utils.ShaderName)
{
// TODO: Material拡張にMToonの項目が追加されたら旧バージョンのshaderPropから変換をかける
// インポート時にUniVRMに含まれるMToonのバージョンに上書きする
param.FloatValues[MToon.Utils.PropVersion] = MToon.Utils.VersionNumber;
}
return true;
}
List<glTF_VRM_Material> m_materials;
public MToonMaterialImporter(List<glTF_VRM_Material> materials)
{
m_materials = materials;
}
public bool TryCreateParam(GltfParser parser, int i, out MaterialImportParam param)
{
if (TryCreateParam(parser, i, m_materials[i], out param))
{
return true;
}
param = default;
return false;
}
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 77db57076c0650c469c3ff0d4853ce21
timeCreated: 1533624711
licenseType: Free
guid: 35c90d5d3fa706b4f87a92ce4dc59008
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

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

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7af3a3bafe41a64db660a18b1c25097
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -26,7 +26,7 @@ namespace VRM
{
VRM = vrm;
// override material importer
MaterialFactory.CreateMaterialAsync = new VRMMaterialImporter(VRM.materialProperties).CreateMaterialAsync;
GltfMaterialImporter.GltfMaterialParamProcessors.Insert(0, new MToonMaterialImporter(VRM.materialProperties).TryCreateParam);
}
else
{
@ -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

@ -1,139 +0,0 @@
using System.Collections.Generic;
using UniGLTF;
using UnityEngine;
using System.Linq;
using System;
using System.Threading.Tasks;
namespace VRM
{
public static class MToonMaterialItem
{
static string[] VRM_SHADER_NAMES =
{
"Standard",
"VRM/MToon",
"UniGLTF/UniUnlit",
"VRM/UnlitTexture",
"VRM/UnlitCutout",
"VRM/UnlitTransparent",
"VRM/UnlitTransparentZWrite",
};
public static async Task<Material> CreateAsync(IAwaitCaller awaitCaller, GltfParser parser, int m_index, glTF_VRM_Material vrmMaterial, GetTextureAsyncFunc getTexture)
{
var item = vrmMaterial;
var shaderName = item.shader;
var shader = Shader.Find(shaderName);
if (shader == null)
{
//
// no shader
//
if (VRM_SHADER_NAMES.Contains(shaderName))
{
Debug.LogErrorFormat("shader {0} not found. set Assets/VRM/Shaders/VRMShaders to Edit - project setting - Graphics - preloaded shaders", shaderName);
}
else
{
// #if VRM_DEVELOP
// Debug.LogWarningFormat("unknown shader {0}.", shaderName);
// #endif
}
return await MaterialFactory.DefaultCreateMaterialAsync(awaitCaller, parser, m_index, getTexture);
}
//
// restore VRM material
//
var material = new Material(shader);
// use material.name, because material name may renamed in GltfParser.
material.name = parser.GLTF.materials[m_index].name;
material.renderQueue = item.renderQueue;
foreach (var kv in item.floatProperties)
{
material.SetFloat(kv.Key, kv.Value);
}
var offsetScaleMap = new Dictionary<string, float[]>();
foreach (var kv in item.vectorProperties)
{
if (item.textureProperties.ContainsKey(kv.Key))
{
// texture offset & scale
offsetScaleMap.Add(kv.Key, kv.Value);
}
else
{
// vector4
var v = new Vector4(kv.Value[0], kv.Value[1], kv.Value[2], kv.Value[3]);
material.SetVector(kv.Key, v);
}
}
foreach (var kv in item.textureProperties)
{
var (offset, scale) = (Vector2.zero, Vector2.one);
if (offsetScaleMap.TryGetValue(kv.Key, out float[] value))
{
offset = new Vector2(value[0], value[1]);
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);
if (texture != null)
{
material.SetTexture(kv.Key, texture);
MaterialFactory.SetTextureOffsetAndScale(material, kv.Key, offset, scale);
}
}
foreach (var kv in item.keywordMap)
{
if (kv.Value)
{
material.EnableKeyword(kv.Key);
}
else
{
material.DisableKeyword(kv.Key);
}
}
foreach (var kv in item.tagMap)
{
material.SetOverrideTag(kv.Key, kv.Value);
}
if (shaderName == MToon.Utils.ShaderName)
{
// TODO: Material拡張にMToonの項目が追加されたら旧バージョンのshaderPropから変換をかける
// インポート時にUniVRMに含まれるMToonのバージョンに上書きする
material.SetFloat(MToon.Utils.PropVersion, MToon.Utils.VersionNumber);
}
return material;
}
}
public class VRMMaterialImporter
{
List<glTF_VRM_Material> m_materials;
public VRMMaterialImporter(List<glTF_VRM_Material> materials)
{
m_materials = materials;
}
public Task<Material> CreateMaterialAsync(IAwaitCaller awaitCaller, GltfParser parser, int i, GetTextureAsyncFunc getTexture)
{
if (i == 0 && m_materials.Count == 0)
{
// dummy
return MaterialFactory.DefaultCreateMaterialAsync(awaitCaller, parser, i, getTexture);
}
return MToonMaterialItem.CreateAsync(awaitCaller, parser, i, m_materials[i], getTexture);
}
}
}

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

@ -29,7 +29,7 @@ namespace VRM
var vrmMaterial = VRMMaterialExporter.CreateFromMaterial(srcMaterial, textureManager);
Assert.AreEqual(vrmMaterial.vectorProperties["_MainTex"], new float[]{0.3f, 0.2f, 0.5f, 0.6f});
var materialImporter = new VRMMaterialImporter(new System.Collections.Generic.List<glTF_VRM_Material>{ vrmMaterial });
var materialImporter = new MToonMaterialImporter(new System.Collections.Generic.List<glTF_VRM_Material>{ vrmMaterial });
}
}
}

View File

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
namespace VRMShaders
{
public delegate Task<Texture2D> GetTextureAsyncFunc(TextureImportParam param);
public class MaterialFactory : IDisposable
{
Dictionary<string, Material> m_externalMap;
public MaterialFactory(IEnumerable<(string, UnityEngine.Object)> externalMap)
{
if (externalMap == null)
{
externalMap = Enumerable.Empty<(string, UnityEngine.Object)>();
}
m_externalMap = externalMap
.Select(kv => (kv.Item1, kv.Item2 as Material))
.Where(kv => kv.Item2 != null)
.ToDictionary(kv => kv.Item1, kv => kv.Item2)
;
}
public struct MaterialLoadInfo
{
public readonly Material Asset;
public readonly bool UseExternal;
public bool IsSubAsset => !UseExternal;
public MaterialLoadInfo(Material asset, bool useExternal)
{
Asset = asset;
UseExternal = useExternal;
}
}
List<MaterialLoadInfo> m_materials = new List<MaterialLoadInfo>();
public IReadOnlyList<MaterialLoadInfo> Materials => m_materials;
void Remove(Material material)
{
var index = m_materials.FindIndex(x => x.Asset == material);
if (index >= 0)
{
m_materials.RemoveAt(index);
}
}
public void Dispose()
{
foreach (var x in m_materials)
{
if (!x.UseExternal)
{
// 外部の '.asset' からロードしていない
#if VRM_DEVELOP
// Debug.Log($"Destroy {x.Asset}");
#endif
UnityEngine.Object.DestroyImmediate(x.Asset, false);
}
}
}
/// <summary>
/// 所有権(Dispose権)を移譲する
///
/// 所有権を移動する関数。
///
/// * 所有権が移動する。return true => ImporterContext.Dispose の対象から外れる
/// * 所有権が移動しない。return false => Importer.Context.Dispose でDestroyされる
///
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(Func<UnityEngine.Object, bool> take)
{
var list = new List<Material>();
foreach (var x in m_materials)
{
if (!x.UseExternal)
{
// 外部の '.asset' からロードしていない
if (take(x.Asset))
{
list.Add(x.Asset);
}
}
}
foreach (var x in list)
{
Remove(x);
}
}
public Material GetMaterial(int index)
{
if (index < 0) return null;
if (index >= m_materials.Count) return null;
return m_materials[index].Asset;
}
public async Task<Material> LoadAsync(MaterialImportParam param, GetTextureAsyncFunc getTexture)
{
if (m_externalMap.TryGetValue(param.Name, out Material material))
{
m_materials.Add(new MaterialLoadInfo(material, true));
return material;
}
if (getTexture == null)
{
getTexture = (_) => Task.FromResult<Texture2D>(null);
}
material = new Material(Shader.Find(param.ShaderName));
material.name = param.Name;
foreach(var kv in param.TextureSlots)
{
var texture = await getTexture(kv.Value);
if(texture!=null){
material.SetTexture(kv.Key, texture);
SetTextureOffsetAndScale(material, kv.Key, kv.Value.Offset, kv.Value.Scale);
}
}
foreach(var kv in param.Colors)
{
material.SetColor(kv.Key, kv.Value);
}
foreach(var kv in param.Vectors)
{
material.SetVector(kv.Key, kv.Value);
}
foreach(var kv in param.FloatValues)
{
material.SetFloat(kv.Key, kv.Value);
}
foreach(var action in param.Actions)
{
action(material);
}
m_materials.Add(new MaterialLoadInfo(material, false));
return material;
}
public static void SetTextureOffsetAndScale(Material material, string propertyName, Vector2 offset, Vector2 scale)
{
material.SetTextureOffset(propertyName, offset);
material.SetTextureScale(propertyName, scale);
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace VRMShaders
{
public class MaterialImportParam
{
public readonly string Name;
public readonly string ShaderName;
public readonly Dictionary<string, TextureImportParam> TextureSlots = new Dictionary<string, TextureImportParam>();
public readonly Dictionary<string, float> FloatValues = new Dictionary<string, float>();
public readonly Dictionary<string, Color> Colors = new Dictionary<string, Color>();
public readonly Dictionary<string, Vector4> Vectors = new Dictionary<string, Vector4>();
public readonly List<Action<Material>> Actions = new List<Action<Material>>();
public MaterialImportParam(string name, string shaderName)
{
Name = name;
ShaderName = shaderName;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0844821341efaf644b91bff72f813a2c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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();
}
}
}