initial game submission

This commit is contained in:
Caleb Sandford deQuincey
2025-06-11 13:04:35 +01:00
parent 47944971ca
commit e9c9bc4409
3041 changed files with 328378 additions and 71117 deletions

View File

@@ -0,0 +1,109 @@
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityGLTF
{
[Serializable]
public struct BlendShapeFrameWeightSetting
{
public enum MultiplierOption
{
Multiplier1 = 0,
Multiplier100 = 1,
Custom = 2
}
[SerializeField]
internal MultiplierOption _option;
[SerializeField]
internal float _multiplier;
public BlendShapeFrameWeightSetting(MultiplierOption option)
{
_option = option;
_multiplier = 1;
}
public BlendShapeFrameWeightSetting(float multiplier)
{
_option = MultiplierOption.Custom;
_multiplier = multiplier;
}
public float Multiplier
{
get
{
switch (_option)
{
case MultiplierOption.Multiplier1:
return 1f;
case MultiplierOption.Multiplier100:
return 100f;
case MultiplierOption.Custom:
return _multiplier;
default:
throw new NotImplementedException();
}
}
set
{
_option = MultiplierOption.Custom;
_multiplier = value;
}
}
public static implicit operator float(BlendShapeFrameWeightSetting weightSetting)
{
return weightSetting.Multiplier;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(BlendShapeFrameWeightSetting))]
public class BlendShapeFrameWeightMultiplierSettingDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var option = property.FindPropertyRelative(nameof(BlendShapeFrameWeightSetting._option));
var customValue = property.FindPropertyRelative(nameof(BlendShapeFrameWeightSetting._multiplier));
EditorGUI.BeginProperty(position, label, property);
{
position.height = EditorGUIUtility.singleLineHeight;
var newBlendShapeFrameWeightSettingIndex = EditorGUI.Popup(position, label.text, option.enumValueIndex, new[] {"1", "100", "Custom"});
if (newBlendShapeFrameWeightSettingIndex != option.enumValueIndex)
{
option.enumValueIndex = newBlendShapeFrameWeightSettingIndex;
}
if (newBlendShapeFrameWeightSettingIndex == 2)
{
position.y += EditorGUIUtility.singleLineHeight;
EditorGUI.indentLevel++;
customValue.floatValue = EditorGUI.FloatField(position, new GUIContent("Custom", "Custom value used as multiplier for Blend Shape frame weights on import."), customValue.floatValue);
EditorGUI.indentLevel--;
}
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var option = property.FindPropertyRelative(nameof(BlendShapeFrameWeightSetting._option));
if (option.enumValueIndex == 2)
{
return EditorGUIUtility.singleLineHeight * 2f;
}
return EditorGUIUtility.singleLineHeight;
}
}
#endif
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9df89f3f8d3645f3b4150e95b864974d
timeCreated: 1706174663

View File

@@ -0,0 +1,11 @@
#if UNITY_2017_1_OR_NEWER
namespace UnityGLTF
{
public enum GLTFImporterNormals
{
Import,
Calculate,
None
}
}
#endif

View File

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

View File

@@ -0,0 +1,615 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GLTF;
using GLTF.Schema;
using GLTF.Schema.KHR_lights_punctual;
using GLTF.Utilities;
using UnityEngine;
using UnityGLTF.Cache;
using UnityGLTF.Extensions;
using UnityGLTF.Plugins;
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
#if UNITY_ANIMATION || !UNITY_2019_1_OR_NEWER
private static string RelativePathFrom(Transform self, Transform root)
{
var path = new List<String>();
for (var current = self; current != null; current = current.parent)
{
if (current == root)
{
return String.Join("/", path.ToArray());
}
path.Insert(0, current.name);
}
throw new Exception("no RelativePath");
}
protected virtual async Task BuildAnimationSamplers(GLTFAnimation animation, int animationId)
{
// look up expected data types
var typeMap = new Dictionary<int, string>();
foreach (var channel in animation.Channels)
{
typeMap[channel.Sampler.Id] = channel.Target.Path.ToString();
}
var samplers = _assetCache.AnimationCache[animationId].Samplers;
var samplersByType = new Dictionary<string, List<AttributeAccessor>>
{
{"time", new List<AttributeAccessor>(animation.Samplers.Count)}
};
for (var i = 0; i < animation.Samplers.Count; i++)
{
// no sense generating unused samplers
if (!typeMap.ContainsKey(i))
{
continue;
}
var samplerDef = animation.Samplers[i];
samplers[i].Interpolation = samplerDef.Interpolation;
// set up input accessors
BufferCacheData inputBufferCacheData = await GetBufferData(samplerDef.Input.Value.BufferView.Value.Buffer);
AttributeAccessor attributeAccessor = new AttributeAccessor
{
AccessorId = samplerDef.Input,
bufferData = inputBufferCacheData.bufferData,
Offset = inputBufferCacheData.ChunkOffset
};
samplers[i].Input = attributeAccessor;
samplersByType["time"].Add(attributeAccessor);
// set up output accessors
BufferCacheData outputBufferCacheData = await GetBufferData(samplerDef.Output.Value.BufferView.Value.Buffer);
attributeAccessor = new AttributeAccessor
{
AccessorId = samplerDef.Output,
bufferData = outputBufferCacheData.bufferData,
Offset = outputBufferCacheData.ChunkOffset
};
samplers[i].Output = attributeAccessor;
if (!samplersByType.ContainsKey(typeMap[i]))
{
samplersByType[typeMap[i]] = new List<AttributeAccessor>();
}
samplersByType[typeMap[i]].Add(attributeAccessor);
}
// populate attributeAccessors with buffer data
GLTFHelpers.BuildAnimationSamplers(ref samplersByType);
}
protected void SetAnimationCurve(
AnimationClip clip,
string relativePath,
string[] propertyNames,
NumericArray input,
NumericArray output,
InterpolationType mode,
Type curveType,
ValuesConvertion getConvertedValues)
{
var channelCount = propertyNames.Length;
var frameCount = input.AsFloats.Length;
// copy all the key frame data to cache
Keyframe[][] keyframes = new Keyframe[channelCount][];
for (var ci = 0; ci < channelCount; ++ci)
{
keyframes[ci] = new Keyframe[frameCount];
}
for (var i = 0; i < frameCount; ++i)
{
var time = input.AsFloats[i];
float[] values = null;
float[] inTangents = null;
float[] outTangents = null;
if (mode == InterpolationType.CUBICSPLINE)
{
// For cubic spline, the output will contain 3 values per keyframe; inTangent, dataPoint, and outTangent.
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
var cubicIndex = i * 3;
inTangents = getConvertedValues(output, cubicIndex);
values = getConvertedValues(output, cubicIndex + 1);
outTangents = getConvertedValues(output, cubicIndex + 2);
}
else
{
// For other interpolation types, the output will only contain one value per keyframe
values = getConvertedValues(output, i);
}
for (var ci = 0; ci < channelCount; ++ci)
{
if (mode == InterpolationType.CUBICSPLINE)
{
keyframes[ci][i] = new Keyframe(time, values[ci], inTangents[ci], outTangents[ci]);
}
else
{
keyframes[ci][i] = new Keyframe(time, values[ci]);
}
}
}
if (mode == InterpolationType.LINEAR && channelCount == 4 && propertyNames.All(p => p == "localRotation.x" || p == "localRotation.y" || p == "localRotation.z" || p == "localRotation.w"))
{
Quaternion prev = Quaternion.identity;
for (int i = 0; i < keyframes[0].Length; i++)
{
Quaternion q = new Quaternion(keyframes[0][i].value, keyframes[1][i].value, keyframes[2][i].value, keyframes[3][i].value);
if (i > 0)
{
if (Quaternion.Dot(prev, q) < 0)
q = new Quaternion(-q.x, -q.y, -q.z, -q.w);
keyframes[0][i].value = q.x;
keyframes[1][i].value = q.y;
keyframes[2][i].value = q.z;
keyframes[3][i].value = q.w;
}
prev = q;
}
}
for (var ci = 0; ci < channelCount; ci++)
{
if (mode != InterpolationType.CUBICSPLINE)
{
// For cubic spline interpolation, the inTangents and outTangents are already explicitly defined.
// For the rest, set them appropriately.
for (var i = 0; i < keyframes[ci].Length; i++)
{
SetTangentMode(keyframes[ci], i, mode);
}
}
// copy all key frames data to animation curve and add it to the clip
AnimationCurve curve = new AnimationCurve(keyframes[ci]);
#if !UNITY_EDITOR
// Only in editor SetCurve works with non-legacy clips
if (clip.legacy)
#endif
clip.SetCurve(relativePath, curveType, propertyNames[ci], curve);
}
}
private void SetTangentMode(Keyframe[] keyframes, int keyframeIndex, InterpolationType interpolation)
{
var key = keyframes[keyframeIndex];
switch (interpolation)
{
case InterpolationType.CATMULLROMSPLINE:
key.inTangent = 0;
key.outTangent = 0;
break;
case InterpolationType.LINEAR:
key.inTangent = GetCurveKeyframeLeftLinearSlope(keyframes, keyframeIndex, ref AnyAnimationTimeNotIncreasing);
key.outTangent = GetCurveKeyframeLeftLinearSlope(keyframes, keyframeIndex + 1, ref AnyAnimationTimeNotIncreasing);
break;
case InterpolationType.STEP:
key.inTangent = float.PositiveInfinity;
key.outTangent = 0;
break;
default:
throw new NotImplementedException($"Unknown interpolation type for animation (File: {_gltfFileName})");
}
keyframes[keyframeIndex] = key;
}
private static float GetCurveKeyframeLeftLinearSlope(Keyframe[] keyframes, int keyframeIndex, ref bool anyNonCreasing)
{
if (keyframeIndex <= 0 || keyframeIndex >= keyframes.Length)
{
return 0;
}
var valueDelta = keyframes[keyframeIndex].value - keyframes[keyframeIndex - 1].value;
var timeDelta = keyframes[keyframeIndex].time - keyframes[keyframeIndex - 1].time;
if (timeDelta <= 0)
{
var k = keyframes[keyframeIndex];
k.time = keyframes[keyframeIndex - 1].time + Mathf.Epsilon + 1 / 100f;
keyframes[keyframeIndex] = k;
anyNonCreasing = true;
return float.PositiveInfinity;
}
return valueDelta / timeDelta;
}
protected async Task<AnimationClip> ConstructClip(Transform root, int animationId, CancellationToken cancellationToken)
{
GLTFAnimation animation = _gltfRoot.Animations[animationId];
AnimationCacheData animationCache = _assetCache.AnimationCache[animationId];
if (animationCache == null)
{
animationCache = new AnimationCacheData(animation.Samplers.Count);
_assetCache.AnimationCache[animationId] = animationCache;
}
else if (animationCache.LoadedAnimationClip != null)
{
return animationCache.LoadedAnimationClip;
}
// unpack accessors
await BuildAnimationSamplers(animation, animationId);
// init clip
AnimationClip clip = new AnimationClip
{
name = animation.Name ?? $"animation:{animationId}",
};
_assetCache.AnimationCache[animationId].LoadedAnimationClip = clip;
if (_options.AnimationMethod == AnimationMethod.Legacy)
clip.legacy = true;
int[] nodeIds = new int[0];
AnimationPointerImportContext pointerImportContext = null;
AttributeAccessor FindSecondaryChannel(string animationPointerPath)
{
foreach (AnimationChannel secondAnimationChannel in animation.Channels)
{
if (secondAnimationChannel.Target.Extensions == null ||
!secondAnimationChannel.Target.Extensions.TryGetValue(KHR_animation_pointer.EXTENSION_NAME,
out IExtension secondaryExt))
continue;
if (secondaryExt is KHR_animation_pointer secondaryPointer)
{
AnimationSamplerCacheData secondarySamplerCache =
animationCache.Samplers[secondAnimationChannel.Sampler.Id];
if (secondaryPointer.path == animationPointerPath)
{
return secondarySamplerCache.Output;
}
}
}
return null;
}
foreach (AnimationChannel channel in animation.Channels)
{
bool usesPointer = false;
IExtension pointerExtension = null;
AnimationSamplerCacheData samplerCache = animationCache.Samplers[channel.Sampler.Id];
if (channel.Target.Extensions != null && channel.Target.Extensions.TryGetValue(
KHR_animation_pointer.EXTENSION_NAME,
out pointerExtension))
{
if (Context.TryGetPlugin(out pointerImportContext))
usesPointer = true;
}
if (!usesPointer && channel.Target.Node == null)
{
// If a channel doesn't have a target node, then just skip it.
// This is legal and is present in one of the asset generator models, but means that animation doesn't actually do anything.
// https://github.com/KhronosGroup/glTF-Asset-Generator/tree/master/Output/Positive/Animation_NodeMisc
// Model 08
continue;
}
string relativePath = null;
AnimationPointerData pointerData = null;
string[] propertyNames;
GLTFAnimationChannelPath path = GLTFAnimationChannelPath.translation;
if (usesPointer && pointerExtension != null)
{
KHR_animation_pointer pointer = pointerExtension as KHR_animation_pointer;
if (pointer == null || pointer.path == null)
continue;
path = GLTFAnimationChannelPath.pointer;
relativePath = pointer.path;
var pointerHierarchy = new PointerPath(relativePath);
if (!pointerHierarchy.isValid)
continue;
switch (pointerHierarchy.PathElementType)
{
case PointerPath.PathElement.RootExtension:
if (!_gltfRoot.Extensions.TryGetValue(pointerHierarchy.elementName, out IExtension hierarchyExtension))
continue;
// Check if the extension support animation pointers
if (hierarchyExtension is IImportAnimationPointerRootExtension rootExtension)
{
// Let the extension handle the pointer data and create the nodeIds and unity properties
if (rootExtension.TryGetImportAnimationPointerData(_gltfRoot, pointerHierarchy, out pointerData))
nodeIds = pointerData.targetNodeIds;
else
continue;
}
pointerData.primaryData = samplerCache.Output;
break;
case PointerPath.PathElement.Root:
var rootType = pointerHierarchy.elementName;
var rootIndex = pointerHierarchy.FindNext(PointerPath.PathElement.Index);
if (rootIndex == null)
continue;
switch (rootType)
{
case "nodes":
var pointerPropertyElement = pointerHierarchy.FindNext(PointerPath.PathElement.Property);
if (pointerPropertyElement == null)
continue;
pointerData = new AnimationPointerData();
pointerData.targetNodeIds = new int[] {rootIndex.index};
nodeIds = pointerData.targetNodeIds;
// Convert translate, scale, rotation from pointer path to to GLTFAnimationChannelPath, so we can use the same code path as the non-animation-pointer channels
if (!GLTFAnimationChannelPath.TryParse(pointerPropertyElement.elementName, out path))
continue;
break;
case "materials":
nodeIds = _gltfRoot.GetAllNodeIdsWithMaterialId(rootIndex.index);
if (nodeIds.Length == 0)
continue;
var materialPath = pointerHierarchy.FindNext(PointerPath.PathElement.Index).next;
if (materialPath == null)
continue;
var gltfPropertyPath = materialPath.ExtractPath();
var mat = _assetCache.MaterialCache[rootIndex.index];
if (!mat.UnityMaterial)
continue;
if (!AnimationPointerHelpers.BuildImportMaterialAnimationPointerData(pointer.path, pointerImportContext.materialPropertiesRemapper, mat.UnityMaterial, gltfPropertyPath, samplerCache.Output, out pointerData))
continue;
pointerData.targetNodeIds = nodeIds;
if (string.IsNullOrEmpty(pointerData.primaryPath))
{
pointerData.primaryPath = "/" + pointerHierarchy.ExtractPath().Replace(rootIndex.next.ExtractPath(), pointerData.primaryProperty);
pointerData.primaryData = FindSecondaryChannel(pointerData.primaryPath);
if (pointerData.primaryData != null)
{
//cancel here and process this combined property later when we found first the Primary Property
continue;
}
}
else
if (!string.IsNullOrEmpty(pointerData.secondaryProperty))
{
// When an property has potentially a second Sampler, we need to find it. e.g. like EmissionFactor and EmissionStrength
pointerData.secondaryPath = "/" + pointerHierarchy.ExtractPath().Replace(rootIndex.next.ExtractPath(), pointerData.secondaryProperty);
pointerData.secondaryData = FindSecondaryChannel(pointerData.secondaryPath);
}
break;
case "cameras":
int cameraId = rootIndex.index;
pointerData = new AnimationPointerData();
pointerData.targetType = typeof(Camera);
pointerData.primaryData = samplerCache.Output;
string gltfCameraPropertyPath = rootIndex.next.ExtractPath();
switch (gltfCameraPropertyPath)
{
case "orthographic/ymag":
pointerData.secondaryPath = $"/{pointerHierarchy.elementName}/{rootIndex.index.ToString()}/orthographic/xmag";
pointerData.unityPropertyNames = new string[] { "orthographic size" };
pointerData.secondaryData = FindSecondaryChannel(pointerData.secondaryPath);
pointerData.importAccessorContentConversion = (data, frame) =>
{
var xmag = data.secondaryData.AccessorContent.AsFloats[frame];
var ymag = data.primaryData.AccessorContent.AsFloats[frame];
return new float[] {Mathf.Max(xmag, ymag)};
};
break;
case "orthographic/xmag":
continue;
case "perspective/znear":
case "orthographic/znear":
pointerData.unityPropertyNames = new string[] { "near clip plane" };
pointerData.importAccessorContentConversion = (data, frame) =>
new float[] {data.primaryData.AccessorContent.AsFloats[frame]};
break;
case "perspective/zfar":
case "orthographic/zfar":
pointerData.unityPropertyNames = new string[] { "far clip plane" };
pointerData.importAccessorContentConversion = (data, frame) =>
new float[] {data.primaryData.AccessorContent.AsFloats[frame]};
break;
case "perspective/yfov":
pointerData.unityPropertyNames = new string[] { "field of view" };
pointerData.importAccessorContentConversion = (data, frame) =>
{
var fov = data.primaryData.AccessorContent.AsFloats[frame] * Mathf.Rad2Deg;
return new float[] {fov};
};
break;
case "backgroundColor":
pointerData.unityPropertyNames = new string[] { "background color.r", "background color.g", "background color.b", "background color.a" };
pointerData.importAccessorContentConversion = (data, frame) =>
{
var color = data.primaryData.AccessorContent.AsFloat4s[frame].ToUnityColorRaw();
return new float[] {color.r, color.g, color.b, color.a};
};
break;
default:
Debug.Log(LogType.Warning, "Unknown property name on Camera " + cameraId.ToString() + ": " + gltfCameraPropertyPath);
break;
}
nodeIds = _gltfRoot.GetAllNodeIdsWithCameraId(cameraId);
break;
default:
continue;
//throw new NotImplementedException();
}
break;
default:
continue;
}
if (pointerData == null)
continue;
}
else
{
if (channel.Target == null || channel.Target.Node == null)
continue;
nodeIds = new int[] {channel.Target.Node.Id};
}
// In case an animated material are referenced from many nodes, whe need to create a curve for each node. (e.g. Materials)
foreach (var nodeId in nodeIds)
{
var node = await GetNode(nodeId, cancellationToken);
var targetNode = _gltfRoot.Nodes[nodeId];
relativePath = RelativePathFrom(node.transform, root);
NumericArray input = samplerCache.Input.AccessorContent;
NumericArray output = samplerCache.Output.AccessorContent;
if (!usesPointer)
{
var known = Enum.TryParse(channel.Target.Path, out path);
if (!known) continue;
}
else
{
if (pointerData.targetType == null)
pointerData.targetType = targetNode.Skin != null
? typeof(SkinnedMeshRenderer)
: typeof(MeshRenderer);
}
switch (path)
{
case GLTFAnimationChannelPath.pointer:
if (pointerData.importAccessorContentConversion == null)
continue;
SetAnimationCurve(clip, relativePath, pointerData.unityPropertyNames, input, output,
samplerCache.Interpolation, pointerData.targetType,
(data, frame) => pointerData.importAccessorContentConversion(pointerData, frame));
break;
case GLTFAnimationChannelPath.translation:
propertyNames = new string[] { "localPosition.x", "localPosition.y", "localPosition.z" };
#if UNITY_EDITOR
// TODO technically this should be postprocessing in the ScriptedImporter instead,
// but performance is much better if we do it when constructing the clips
var factor = Context?.ImportScaleFactor ?? 1f;
#endif
SetAnimationCurve(clip, relativePath, propertyNames, input, output,
samplerCache.Interpolation, typeof(Transform),
(data, frame) =>
{
var position = data.AsFloat3s[frame].ToUnityVector3Convert();
#if UNITY_EDITOR
return new float[]
{ position.x * factor, position.y * factor, position.z * factor };
#else
return new float[] { position.x, position.y, position.z };
#endif
});
break;
case GLTFAnimationChannelPath.rotation:
propertyNames = new string[]
{ "localRotation.x", "localRotation.y", "localRotation.z", "localRotation.w" };
bool flipRotation = (targetNode.Extensions != null
&& targetNode.Extensions.ContainsKey(KHR_lights_punctualExtensionFactory.EXTENSION_NAME)
&& Context.TryGetPlugin<LightsPunctualImportContext>(out _));
SetAnimationCurve(clip, relativePath, propertyNames, input, output,
samplerCache.Interpolation, typeof(Transform),
(data, frame) =>
{
var rotation = data.AsFloat4s[frame];
var quaternion = rotation.ToUnityQuaternionConvert();
if (flipRotation)
quaternion *= Quaternion.Euler(0, 180, 0);
return new float[] { quaternion.x, quaternion.y, quaternion.z, quaternion.w };
});
break;
case GLTFAnimationChannelPath.scale:
propertyNames = new string[] { "localScale.x", "localScale.y", "localScale.z" };
SetAnimationCurve(clip, relativePath, propertyNames, input, output,
samplerCache.Interpolation, typeof(Transform),
(data, frame) =>
{
var scale = data.AsFloat3s[frame].ToUnityVector3Raw();
return new float[] { scale.x, scale.y, scale.z };
});
break;
case GLTFAnimationChannelPath.weights:
var mesh = targetNode.Mesh.Value;
var primitives = mesh.Primitives;
if (primitives[0].Targets == null) break;
var targetCount = primitives[0].Targets.Count;
for (int primitiveIndex = 0; primitiveIndex < primitives.Count; primitiveIndex++)
{
// see SceneImporter:156
// blend shapes are always called "Morphtarget"
var targetNames = mesh.TargetNames;
propertyNames = new string[targetCount];
for (var i = 0; i < targetCount; i++)
propertyNames[i] = _options.ImportBlendShapeNames
? ("blendShape." + ((targetNames != null && targetNames.Count > i)
? targetNames[i]
: ("Morphtarget" + i)))
: "blendShape." + i.ToString();
var frameFloats = new float[targetCount];
var blendShapeFrameWeight = _options.BlendShapeFrameWeight;
SetAnimationCurve(clip, relativePath, propertyNames, input, output,
samplerCache.Interpolation, typeof(SkinnedMeshRenderer),
(data, frame) =>
{
var allValues = data.AsFloats;
for (var k = 0; k < targetCount; k++)
frameFloats[k] = allValues[frame * targetCount + k] * blendShapeFrameWeight;
return frameFloats;
});
}
break;
default:
Debug.Log(LogType.Warning, $"Cannot read GLTF animation path (File: {_gltfFileName})");
break;
} // switch target type
} // foreach nodeIds
await YieldOnTimeoutAndThrowOnLowMemory();
} // foreach channel
// EnsureQuaternionContinuity results in unwanted tangents on the first and last keyframes > custom Solution in SetAnimationCurve
//clip.EnsureQuaternionContinuity();
return clip;
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 20eade7af2db4b06a0d23b23c3a83c47
timeCreated: 1657012127

View File

@@ -0,0 +1,46 @@
using GLTF.Schema;
using UnityEngine;
using UnityGLTF.Extensions;
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
private bool ConstructCamera(GameObject nodeObj, Node node)
{
if (node.Camera == null)
return false;
if (_options.CameraImport == CameraImportOption.None)
return false;
var camera = node.Camera.Value;
Camera unityCamera = null;
if (camera.Orthographic != null)
{
unityCamera = nodeObj.AddComponent<Camera>();
unityCamera.orthographic = true;
unityCamera.orthographicSize = Mathf.Max((float)camera.Orthographic.XMag, (float)camera.Orthographic.YMag);
unityCamera.farClipPlane = (float)camera.Orthographic.ZFar;
unityCamera.nearClipPlane = (float)camera.Orthographic.ZNear;
}
else if (camera.Perspective != null)
{
unityCamera = nodeObj.AddComponent<Camera>();
unityCamera.orthographic = false;
unityCamera.fieldOfView = (float)camera.Perspective.YFov * Mathf.Rad2Deg;
unityCamera.farClipPlane = (float)camera.Perspective.ZFar;
unityCamera.nearClipPlane = (float)camera.Perspective.ZNear;
}
if (!unityCamera)
return false;
if (_options.CameraImport == CameraImportOption.ImportAndCameraDisabled)
unityCamera.enabled = false;
nodeObj.transform.localRotation *= SchemaExtensions.InvertDirection;
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4bce81583cdc4b5b831072f00b368b09
timeCreated: 1713049096

View File

@@ -0,0 +1,18 @@
using UnityEngine;
using UnityGLTF.Loader;
namespace UnityGLTF
{
public abstract class ImporterFactory : ScriptableObject
{
public abstract GLTFSceneImporter CreateSceneImporter(string gltfFileName, ImportOptions options);
}
public class DefaultImporterFactory : ImporterFactory
{
public override GLTFSceneImporter CreateSceneImporter(string gltfFileName, ImportOptions options)
{
return new GLTFSceneImporter(gltfFileName, options);
}
}
}

View File

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

View File

@@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GLTF.Schema;
using UnityEngine;
using UnityGLTF.Plugins;
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
private async Task ConstructLods(GLTFRoot gltfRoot, GameObject nodeObj, Node node, int nodeIndex, CancellationToken cancellationToken)
{
if (!Context.TryGetPlugin<LodsImportContext>(out _)) return;
const string msft_LODExtName = MSFT_LODExtensionFactory.EXTENSION_NAME;
MSFT_LODExtension lodsExtension = null;
if (_gltfRoot.ExtensionsUsed != null
&& _gltfRoot.ExtensionsUsed.Contains(msft_LODExtName)
&& node.Extensions != null
&& node.Extensions.ContainsKey(msft_LODExtName))
{
lodsExtension = node.Extensions[msft_LODExtName] as MSFT_LODExtension;
if (lodsExtension != null && lodsExtension.NodeIds.Count > 0)
{
int lodCount = lodsExtension.NodeIds.Count + 1;
if (CullFarLOD)
{
lodCount += 1;
}
LOD[] lods = new LOD[lodCount];
List<double> lodCoverage = lodsExtension.GetLODCoverage(node);
if (lodCoverage == null)
lodCoverage = new List<double>();
var hasExplicitLodCoverage = lodCoverage.Count > 0;
if (lodCoverage.Count != lods.Length)
{
if (!hasExplicitLodCoverage)
lodCoverage.Add(0.5f);
var count = lodCoverage.Count;
var length = lods.Length;
for (var i = count; i < length; i++)
{
lodCoverage.Add(lodCoverage[i - 1] / 2);
}
if (!CullFarLOD && !hasExplicitLodCoverage)
lodCoverage[lodCoverage.Count - 1] = 0;
}
// sanitize values so that they are strictly decreasing
for (int i = lodCoverage.Count - 2; i >= 0; i--)
{
if (lodCoverage[i] <= lodCoverage[i + 1])
lodCoverage[i] = lodCoverage[i + 1] + 0.01f;
}
var lodGroupNodeObj = nodeObj;
lodGroupNodeObj.SetActive(false);
var firstLodChildRenderers = nodeObj.GetComponentsInChildren<Renderer>().ToList();
LODGroup lodGroup = lodGroupNodeObj.AddComponent<LODGroup>();
for (int i = 0; i < lodsExtension.NodeIds.Count; i++)
{
int lodNodeId = lodsExtension.NodeIds[i];
var lodNodeObj = await GetNode(lodNodeId, cancellationToken);
// progressStatus.NodeTotal++;
lodNodeObj.transform.SetParent(lodGroupNodeObj.transform, false);
var childRenderers = lodNodeObj.GetComponentsInChildren<Renderer>();
int lodIndex = i + 1;
// make sure to kick out the renderers that are used in lower LOD levels from the root LOD level
foreach (var child in childRenderers)
firstLodChildRenderers.Remove(child);
lods[lodIndex] = new LOD(GetLodCoverage(lodCoverage, lodIndex), childRenderers);
}
lods[0] = new LOD(GetLodCoverage(lodCoverage, 0), firstLodChildRenderers.ToArray());
if (CullFarLOD)
{
//use the last mesh as the LOD
lods[lodsExtension.NodeIds.Count + 1] = new LOD(0, null);
}
lodGroup.SetLODs(lods);
lodGroup.RecalculateBounds();
lodGroupNodeObj.SetActive(true);
_assetCache.NodeCache[nodeIndex] = lodGroupNodeObj;
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fe34f290cdf64038ad7d60c706251109
timeCreated: 1675772109

View File

@@ -0,0 +1,61 @@
using GLTF.Schema;
using GLTF.Schema.KHR_lights_punctual;
using UnityEngine;
using UnityGLTF.Extensions;
using UnityGLTF.Plugins;
using LightType = UnityEngine.LightType;
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
private bool ConstructLights(GameObject nodeObj, Node node)
{
var useLightsExtension = Context.TryGetPlugin<LightsPunctualImportContext>(out _);
if (!useLightsExtension) return false;
// TODO this should be handled by the lights extension directly, not here
const string lightExt = KHR_lights_punctualExtensionFactory.EXTENSION_NAME;
KHR_LightsPunctualNodeExtension lightsExtension = null;
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(lightExt) && node.Extensions != null && node.Extensions.ContainsKey(lightExt))
{
lightsExtension = node.Extensions[lightExt] as KHR_LightsPunctualNodeExtension;
var l = lightsExtension.LightId;
var light = l.Value;
var newLight = nodeObj.AddComponent<Light>();
switch (light.Type)
{
case GLTF.Schema.KHR_lights_punctual.LightType.spot:
newLight.intensity = (float) light.Intensity / Mathf.PI;
newLight.type = LightType.Spot;
break;
case GLTF.Schema.KHR_lights_punctual.LightType.directional:
newLight.type = LightType.Directional;
break;
case GLTF.Schema.KHR_lights_punctual.LightType.point:
newLight.type = LightType.Point;
newLight.intensity = (float) light.Intensity * Mathf.PI;
break;
}
newLight.name = light.Name;
newLight.color = light.Color.ToUnityColorRaw();
newLight.range = (float) light.Range;
if (light.Spot != null)
{
#if UNITY_2019_1_OR_NEWER
newLight.innerSpotAngle = (float) light.Spot.InnerConeAngle * 2 / (Mathf.Deg2Rad * 0.8f);
#endif
newLight.spotAngle = (float) light.Spot.OuterConeAngle * 2 / Mathf.Deg2Rad;
}
nodeObj.transform.localRotation *= SchemaExtensions.InvertDirection;
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7b547db626e3430c87fe1e060a131222
timeCreated: 1657014645

View File

@@ -0,0 +1,1061 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GLTF.Schema;
using UnityEngine;
using UnityGLTF.Cache;
using UnityGLTF.Extensions;
using UnityGLTF.Plugins;
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
internal static List<Texture> _runtimeNormalTextures = new List<Texture>();
protected virtual async Task ConstructMaterial(GLTFMaterial def, int materialIndex)
{
IUniformMap mapper;
const string specGlossExtName = KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME;
const string unlitExtName = KHR_MaterialsUnlitExtensionFactory.EXTENSION_NAME;
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(specGlossExtName) && def.Extensions != null && def.Extensions.ContainsKey(specGlossExtName))
{
Debug.Log(LogType.Warning, $"KHR_materials_pbrSpecularGlossiness has been deprecated, material {def.Name} may not look correct. Use `gltf-transform metalrough` or other tools to convert to PBR. (File: {_gltfFileName})");
if (!string.IsNullOrEmpty(CustomShaderName))
{
mapper = new SpecGlossMap(CustomShaderName, MaximumLod);
}
else
{
#if UNITY_2021_3_OR_NEWER
// This isn't fully supported. KHR_materials_pbrSpecularGlossiness is deprecated though, so we're warning here.
mapper = new PBRGraphMap();
#else
mapper = new SpecGlossMap(MaximumLod);
#endif
}
}
else if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(unlitExtName) && def.Extensions != null && def.Extensions.ContainsKey(unlitExtName))
{
if (!string.IsNullOrEmpty(CustomShaderName))
{
mapper = new UnlitMap(CustomShaderName, null, MaximumLod);
}
else
{
#if UNITY_2021_3_OR_NEWER
mapper = new UnlitGraphMap();
#elif UNITY_2019_1_OR_NEWER
if (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline)
mapper = new UnlitGraphMap(def.AlphaMode == AlphaMode.BLEND, def.DoubleSided);
else
mapper = new UnlitMap(MaximumLod);
#else
mapper = new UnlitMap(MaximumLod);
#endif
}
}
else
{
if (!string.IsNullOrEmpty(CustomShaderName))
{
mapper = new MetalRoughMap(CustomShaderName, MaximumLod);
}
else
{
// do we have URP or Unity 2021.2+? Use the PBR Graph Material!
#if UNITY_2021_3_OR_NEWER
mapper = new PBRGraphMap();
#elif UNITY_2019_1_OR_NEWER
if (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline)
mapper = new PBRGraphMap(def.AlphaMode == AlphaMode.BLEND, def.DoubleSided);
else
mapper = new MetalRoughMap(MaximumLod);
#else
mapper = new MetalRoughMap(MaximumLod);
#endif
}
}
void CalculateYOffsetAndScale(TextureId textureId, ExtTextureTransformExtension ext, out Vector2 scale, out Vector2 offset)
{
offset = ext.Offset.ToUnityVector2Raw();
scale = ext.Scale.ToUnityVector2Raw();
if (IsTextureFlipped(textureId.Value))
{
offset.y = scale.y + offset.y;
scale.y *= -1f;
}
else
{
offset.y = 1 - scale.y - offset.y;
}
}
mapper.Material.name = def.Name;
mapper.AlphaMode = def.AlphaMode;
mapper.AlphaCutoff = def.AlphaCutoff;
mapper.DoubleSided = def.DoubleSided;
mapper.Material.SetFloat("_BUILTIN_QueueControl", -1);
mapper.Material.SetFloat("_QueueControl", -1);
#if UNITY_EDITOR
if (Context.SourceImporter == null)
#endif
mapper.Material.SetFloat("_NormalMapFormatXYZ", 1);
void SetTransformKeyword()
{
MatHelper.SetKeyword(mapper.Material, "_TEXTURE_TRANSFORM", true);
}
var mrMapper = mapper as IMetalRoughUniformMap;
if (def.PbrMetallicRoughness != null && mrMapper != null)
{
var pbr = def.PbrMetallicRoughness;
mrMapper.BaseColorFactor = pbr.BaseColorFactor.ToUnityColorRaw();
if (pbr.BaseColorTexture != null)
{
TextureId textureId = pbr.BaseColorTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, false, false);
mrMapper.BaseColorTexture = _assetCache.TextureCache[textureId.Id].Texture;
mrMapper.BaseColorTexCoord = pbr.BaseColorTexture.TexCoord;
var ext = GetTextureTransform(pbr.BaseColorTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
mrMapper.BaseColorXOffset = offset;
mrMapper.BaseColorXRotation = ext.Rotation;
mrMapper.BaseColorXScale = scale;
if (ext.TexCoord != null) mrMapper.BaseColorXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
mrMapper.BaseColorXScale = new Vector2(1f,-1f);
mrMapper.BaseColorXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
mrMapper.MetallicFactor = pbr.MetallicFactor;
mrMapper.RoughnessFactor = pbr.RoughnessFactor;
if (pbr.MetallicRoughnessTexture != null)
{
TextureId textureId = pbr.MetallicRoughnessTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, true, false);
mrMapper.MetallicRoughnessTexture = _assetCache.TextureCache[textureId.Id].Texture;
mrMapper.MetallicRoughnessTexCoord = pbr.MetallicRoughnessTexture.TexCoord;
var ext = GetTextureTransform(pbr.MetallicRoughnessTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
mrMapper.MetallicRoughnessXOffset = offset;
mrMapper.MetallicRoughnessXRotation = ext.Rotation;
mrMapper.MetallicRoughnessXScale = scale;
if (ext.TexCoord != null) mrMapper.MetallicRoughnessXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
mrMapper.MetallicRoughnessXScale = new Vector2(1f,-1f);
mrMapper.MetallicRoughnessXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
}
// when PbrMetallicRoughness is not defined, default values MUST apply
else if (def.PbrMetallicRoughness == null && mrMapper != null)
{
mrMapper.MetallicFactor = 1;
mrMapper.RoughnessFactor = 1;
}
// get MaterialImportPluginContext and check which options are enabled
// ReSharper disable InconsistentNaming IdentifierTypo
var settings = (Context.Plugins.FirstOrDefault(x => x is MaterialExtensionsImportContext) as MaterialExtensionsImportContext)?.settings;
var KHR_materials_ior = settings && settings.KHR_materials_ior;
var KHR_materials_transmission = settings && settings.KHR_materials_transmission;
var KHR_materials_volume = settings && settings.KHR_materials_volume;
var KHR_materials_iridescence = settings && settings.KHR_materials_iridescence;
var KHR_materials_specular = settings && settings.KHR_materials_specular;
var KHR_materials_clearcoat = settings && settings.KHR_materials_clearcoat;
var KHR_materials_pbrSpecularGlossiness = settings && settings.KHR_materials_pbrSpecularGlossiness;
var KHR_materials_emissive_strength = settings && settings.KHR_materials_emissive_strength;
var KHR_materials_sheen = settings && settings.KHR_materials_sheen;
// ReSharper restore InconsistentNaming
var sgMapper = mapper as ISpecGlossUniformMap;
if (sgMapper != null && KHR_materials_pbrSpecularGlossiness)
{
var specGloss = def.Extensions[specGlossExtName] as KHR_materials_pbrSpecularGlossinessExtension;
sgMapper.DiffuseFactor = specGloss.DiffuseFactor.ToUnityColorRaw();
if (specGloss.DiffuseTexture != null)
{
TextureId textureId = specGloss.DiffuseTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, false, false);
sgMapper.DiffuseTexture = _assetCache.TextureCache[textureId.Id].Texture;
sgMapper.DiffuseTexCoord = specGloss.DiffuseTexture.TexCoord;
var ext = GetTextureTransform(specGloss.DiffuseTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
sgMapper.DiffuseXOffset = offset;
sgMapper.DiffuseXRotation = ext.Rotation;
sgMapper.DiffuseXScale = scale;
if (ext.TexCoord != null) sgMapper.DiffuseXTexCoord = ext.TexCoord.Value;
MatHelper.SetKeyword(mapper.Material, "_TEXTURE_TRANSFORM", true);
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
sgMapper.DiffuseXScale = new Vector2(1f,-1f);
sgMapper.DiffuseXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
sgMapper.SpecularFactor = specGloss.SpecularFactor.ToUnityVector3Raw();
sgMapper.GlossinessFactor = specGloss.GlossinessFactor;
if (specGloss.SpecularGlossinessTexture != null)
{
TextureId textureId = specGloss.SpecularGlossinessTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, false, false);
sgMapper.SpecularGlossinessTexture = _assetCache.TextureCache[textureId.Id].Texture;
sgMapper.SpecularGlossinessTexCoord = specGloss.SpecularGlossinessTexture.TexCoord;
var ext = GetTextureTransform(specGloss.SpecularGlossinessTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
sgMapper.SpecularGlossinessXOffset = offset;
sgMapper.SpecularGlossinessXRotation = ext.Rotation;
sgMapper.SpecularGlossinessXScale = scale;
if (ext.TexCoord != null) sgMapper.SpecularGlossinessXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
sgMapper.SpecularGlossinessXScale = new Vector2(1f,-1f);
sgMapper.SpecularGlossinessXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
}
var unlitMapper = mapper as IUnlitUniformMap;
if (unlitMapper != null)
{
var pbr = def.PbrMetallicRoughness;
unlitMapper.BaseColorFactor = pbr.BaseColorFactor.ToUnityColorRaw();
if (pbr.BaseColorTexture != null)
{
TextureId textureId = pbr.BaseColorTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, false, false);
unlitMapper.BaseColorTexture = _assetCache.TextureCache[textureId.Id].Texture;
unlitMapper.BaseColorTexCoord = pbr.BaseColorTexture.TexCoord;
var ext = GetTextureTransform(pbr.BaseColorTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
unlitMapper.BaseColorXOffset = offset;
unlitMapper.BaseColorXRotation = ext.Rotation;
unlitMapper.BaseColorXScale = scale;
if (ext.TexCoord != null) unlitMapper.BaseColorXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
unlitMapper.BaseColorXScale = new Vector2(1f,-1f);
unlitMapper.BaseColorXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
}
var iorMapper = mapper as IIORMap;
if (iorMapper != null && KHR_materials_ior)
{
var ior = GetIOR(def);
if (ior != null)
{
iorMapper.IOR = ior.ior;
}
}
var sheenMapper = mapper as ISheenMap;
if (sheenMapper != null && KHR_materials_sheen)
{
var sheen = GetSheen(def);
if (sheen != null)
{
sheenMapper.SheenColorFactor = sheen.sheenColorFactor.ToUnityColorRaw();
sheenMapper.SheenRoughnessFactor = sheen.sheenRoughnessFactor;
MatHelper.SetKeyword(mapper.Material, "_SHEEN", true);
if (sheen.sheenColorTexture != null)
{
var td = await FromTextureInfo(sheen.sheenColorTexture, false);
sheenMapper.SheenColorTexture = td.Texture;
sheenMapper.SheenColorTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(sheen.sheenColorTexture);
if (ext != null)
{
CalculateYOffsetAndScale(sheen.sheenColorTexture.Index, ext, out var scale, out var offset);
sheenMapper.SheenColorTextureOffset = offset;
sheenMapper.SheenColorTextureScale = scale;
sheenMapper.SheenColorTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) sheenMapper.SheenColorTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(sheen.sheenColorTexture.Index.Value))
{
sheenMapper.SheenColorTextureScale = new Vector2(1f,-1f);
sheenMapper.SheenColorTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
if (sheen.sheenRoughnessTexture != null)
{
var td = await FromTextureInfo(sheen.sheenRoughnessTexture, false);
sheenMapper.SheenRoughnessTexture = td.Texture;
sheenMapper.SheenColorTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(sheen.sheenRoughnessTexture);
if (ext != null)
{
CalculateYOffsetAndScale(sheen.sheenRoughnessTexture.Index, ext, out var scale, out var offset);
sheenMapper.SheenRoughnessTextureOffset = offset;
sheenMapper.SheenRoughnessTextureScale = scale;
sheenMapper.SheenRoughnessTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) sheenMapper.SheenRoughnessTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(sheen.sheenRoughnessTexture.Index.Value))
{
sheenMapper.SheenRoughnessTextureScale = new Vector2(1f,-1f);
sheenMapper.SheenRoughnessTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
}
}
var transmissionMapper = mapper as ITransmissionMap;
if (transmissionMapper != null && KHR_materials_transmission)
{
var transmission = GetTransmission(def);
if (transmission != null)
{
transmissionMapper.TransmissionFactor = transmission.transmissionFactor;
if (transmission.transmissionTexture != null)
{
var td = await FromTextureInfo(transmission.transmissionTexture, false);
transmissionMapper.TransmissionTexture = td.Texture;
transmissionMapper.TransmissionTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(transmission.transmissionTexture);
if (ext != null)
{
CalculateYOffsetAndScale(transmission.transmissionTexture.Index, ext, out var scale, out var offset);
transmissionMapper.TransmissionTextureOffset = offset;
transmissionMapper.TransmissionTextureScale = scale;
transmissionMapper.TransmissionTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) transmissionMapper.TransmissionTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(transmission.transmissionTexture.Index.Value))
{
transmissionMapper.TransmissionTextureScale = new Vector2(1f,-1f);
transmissionMapper.TransmissionTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
mapper.Material.renderQueue = 3000;
#if UNITY_VISIONOS
mapper.AlphaMode = AlphaMode.BLEND;
#endif
bool hasDispersion = false;
if (transmissionMapper is IDispersionMap dispersionMapper)
{
var dispersion = GetDispersion(def);
if (dispersion != null)
{
hasDispersion = true;
dispersionMapper.Dispersion = dispersion.dispersion;
}
}
if (hasDispersion)
{
mapper.Material.EnableKeyword("_VOLUME_TRANSMISSION_ANDDISPERSION");
mapper.Material.SetFloat("_VOLUME_TRANSMISSION", 2f);
}
else
{
mapper.Material.EnableKeyword("_VOLUME_TRANSMISSION_ON");
mapper.Material.SetFloat("_VOLUME_TRANSMISSION", 1f);
}
}
}
var volumeMapper = mapper as IVolumeMap;
if (volumeMapper != null && KHR_materials_volume)
{
var volume = GetVolume(def);
if (volume != null)
{
#if UNITY_VISIONOS
mapper.AlphaMode = AlphaMode.BLEND;
#endif
volumeMapper.AttenuationColor = QualitySettings.activeColorSpace == ColorSpace.Linear ? volume.attenuationColor.ToUnityColorLinear() : volume.attenuationColor.ToUnityColorRaw();
volumeMapper.AttenuationDistance = volume.attenuationDistance;
volumeMapper.ThicknessFactor = volume.thicknessFactor;
if (volume.thicknessTexture != null)
{
var td = await FromTextureInfo(volume.thicknessTexture, false);
volumeMapper.ThicknessTexture = td.Texture;
volumeMapper.ThicknessTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(volume.thicknessTexture);
if (ext != null)
{
CalculateYOffsetAndScale(volume.thicknessTexture.Index, ext, out var scale, out var offset);
volumeMapper.ThicknessTextureOffset = offset;
volumeMapper.ThicknessTextureScale = scale;
volumeMapper.ThicknessTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) volumeMapper.ThicknessTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(volume.thicknessTexture.Index.Value))
{
volumeMapper.ThicknessTextureScale = new Vector2(1f,-1f);
volumeMapper.ThicknessTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
mapper.Material.renderQueue = 3000;
mapper.Material.SetFloat("_VOLUME_ON", 1f);
}
}
var iridescenceMapper = mapper as IIridescenceMap;
if (iridescenceMapper != null && KHR_materials_iridescence)
{
var iridescence = GetIridescence(def);
if (iridescence != null)
{
iridescenceMapper.IridescenceFactor = iridescence.iridescenceFactor;
iridescenceMapper.IridescenceIor = iridescence.iridescenceIor;
iridescenceMapper.IridescenceThicknessMinimum = iridescence.iridescenceThicknessMinimum;
iridescenceMapper.IridescenceThicknessMaximum = iridescence.iridescenceThicknessMaximum;
if (iridescence.iridescenceTexture != null)
{
var td = await FromTextureInfo(iridescence.iridescenceTexture, false);
iridescenceMapper.IridescenceTexture = td.Texture;
iridescenceMapper.IridescenceTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(iridescence.iridescenceTexture);
if (ext != null)
{
CalculateYOffsetAndScale(iridescence.iridescenceTexture.Index, ext, out var scale, out var offset);
iridescenceMapper.IridescenceTextureOffset = offset;
iridescenceMapper.IridescenceTextureScale = scale;
iridescenceMapper.IridescenceTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) iridescenceMapper.IridescenceTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(iridescence.iridescenceTexture.Index.Value))
{
iridescenceMapper.IridescenceTextureScale = new Vector2(1f,-1f);
iridescenceMapper.IridescenceTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
if (iridescence.iridescenceThicknessTexture != null)
{
var td2 = await FromTextureInfo(iridescence.iridescenceThicknessTexture, false);
iridescenceMapper.IridescenceThicknessTexture = td2.Texture;
iridescenceMapper.IridescenceThicknessTextureTexCoord = td2.TexCoord;
var ext2 = GetTextureTransform(iridescence.iridescenceThicknessTexture);
if (ext2 != null)
{
CalculateYOffsetAndScale(iridescence.iridescenceThicknessTexture.Index, ext2, out var scale,
out var offset);
iridescenceMapper.IridescenceThicknessTextureOffset = offset;
iridescenceMapper.IridescenceThicknessTextureScale = scale;
iridescenceMapper.IridescenceThicknessTextureRotation = td2.Rotation;
if (td2.TexCoordExtra != null) iridescenceMapper.IridescenceThicknessTextureTexCoord = td2.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(iridescence.iridescenceThicknessTexture.Index.Value))
{
iridescenceMapper.IridescenceThicknessTextureScale = new Vector2(1f, -1f);
iridescenceMapper.IridescenceThicknessTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
mapper.Material.SetKeyword("_IRIDESCENCE", true);
}
}
var specularMapper = mapper as ISpecularMap;
if (specularMapper != null && KHR_materials_specular)
{
var specular = GetSpecular(def);
if (specular != null)
{
specularMapper.SpecularFactor = specular.specularFactor;
specularMapper.SpecularColorFactor = specular.specularColorFactor.ToUnityColorLinear();
if (specular.specularTexture != null)
{
var td = await FromTextureInfo(specular.specularTexture, false);
specularMapper.SpecularTexture = td.Texture;
specularMapper.SpecularTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(specular.specularTexture);
if (ext != null)
{
CalculateYOffsetAndScale(specular.specularTexture.Index, ext, out var scale,
out var offset);
specularMapper.SpecularTextureOffset = offset;
specularMapper.SpecularTextureScale = scale;
specularMapper.SpecularTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) specularMapper.SpecularTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(specular.specularTexture.Index.Value))
{
specularMapper.SpecularTextureScale = new Vector2(1f, -1f);
specularMapper.SpecularTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
if (specular.specularColorTexture != null)
{
var td2 = await FromTextureInfo(specular.specularColorTexture, false);
specularMapper.SpecularColorTexture = td2.Texture;
specularMapper.SpecularColorTextureTexCoord = td2.TexCoord;
var ext2 = GetTextureTransform(specular.specularColorTexture);
if (ext2 != null)
{
CalculateYOffsetAndScale(specular.specularColorTexture.Index, ext2, out var scale,
out var offset);
specularMapper.SpecularColorTextureOffset = offset;
specularMapper.SpecularColorTextureScale = scale;
specularMapper.SpecularColorTextureRotation = td2.Rotation;
if (td2.TexCoordExtra != null) specularMapper.SpecularColorTextureTexCoord = td2.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(specular.specularColorTexture.Index.Value))
{
specularMapper.SpecularColorTextureScale = new Vector2(1f, -1f);
specularMapper.SpecularColorTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
mapper.Material.SetKeyword("_SPECULAR", true);
}
}
var clearcoatMapper = mapper as IClearcoatMap;
if (clearcoatMapper != null && KHR_materials_clearcoat)
{
var clearcoat = GetClearcoat(def);
if (clearcoat != null)
{
clearcoatMapper.ClearcoatFactor = clearcoat.clearcoatFactor;
clearcoatMapper.ClearcoatRoughnessFactor = clearcoat.clearcoatRoughnessFactor;
if (clearcoat.clearcoatTexture != null)
{
var td = await FromTextureInfo(clearcoat.clearcoatTexture, false);
clearcoatMapper.ClearcoatTexture = td.Texture;
clearcoatMapper.ClearcoatTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(clearcoat.clearcoatTexture);
if (ext != null)
{
CalculateYOffsetAndScale(clearcoat.clearcoatTexture.Index, ext, out var scale,
out var offset);
clearcoatMapper.ClearcoatTextureOffset = offset;
clearcoatMapper.ClearcoatTextureScale = scale;
clearcoatMapper.ClearcoatTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) clearcoatMapper.ClearcoatTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(clearcoat.clearcoatTexture.Index.Value))
{
clearcoatMapper.ClearcoatTextureScale = new Vector2(1f, -1f);
clearcoatMapper.ClearcoatTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
if (clearcoat.clearcoatRoughnessTexture != null)
{
var td = await FromTextureInfo(clearcoat.clearcoatRoughnessTexture, false);
clearcoatMapper.ClearcoatRoughnessTexture = td.Texture;
clearcoatMapper.ClearcoatRoughnessTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(clearcoat.clearcoatRoughnessTexture);
if (ext != null)
{
CalculateYOffsetAndScale(clearcoat.clearcoatRoughnessTexture.Index, ext, out var scale,
out var offset);
clearcoatMapper.ClearcoatRoughnessTextureOffset = offset;
clearcoatMapper.ClearcoatRoughnessTextureScale = scale;
clearcoatMapper.ClearcoatRoughnessTextureRotation = td.Rotation;
if (td.TexCoordExtra != null) clearcoatMapper.ClearcoatRoughnessTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(clearcoat.clearcoatRoughnessTexture.Index.Value))
{
clearcoatMapper.ClearcoatRoughnessTextureScale = new Vector2(1f, -1f);
clearcoatMapper.ClearcoatRoughnessTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
var clearcoatNormalMapper = mapper as IClearcoatNormalMap;
if (clearcoatNormalMapper != null)
{
if (clearcoat.clearcoatNormalTexture != null)
{
var td = await FromTextureInfo(clearcoat.clearcoatNormalTexture, false);
_runtimeNormalTextures.Add(td.Texture);
clearcoatNormalMapper.ClearcoatNormalTexture = td.Texture;
clearcoatNormalMapper.ClearcoatNormalTextureTexCoord = td.TexCoord;
var ext = GetTextureTransform(clearcoat.clearcoatNormalTexture);
if (ext != null)
{
CalculateYOffsetAndScale(clearcoat.clearcoatNormalTexture.Index, ext, out var scale,
out var offset);
clearcoatNormalMapper.ClearcoatNormalTextureOffset = offset;
clearcoatNormalMapper.ClearcoatNormalTextureScale = scale;
clearcoatNormalMapper.ClearcoatNormalTextureRotation = td.Rotation;
if (td.TexCoordExtra != null)
clearcoatNormalMapper.ClearcoatNormalTextureTexCoord = td.TexCoordExtra.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(clearcoat.clearcoatNormalTexture.Index.Value))
{
clearcoatNormalMapper.ClearcoatNormalTextureScale = new Vector2(1f, -1f);
clearcoatNormalMapper.ClearcoatNormalTextureOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
}
mapper.Material.SetKeyword("_CLEARCOAT", true);
}
}
var uniformMapper = mapper as ILitMap;
if (uniformMapper != null)
{
if (def.NormalTexture != null)
{
TextureId textureId = def.NormalTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, true, true);
uniformMapper.NormalTexture = _assetCache.TextureCache[textureId.Id].Texture;
uniformMapper.NormalTexCoord = def.NormalTexture.TexCoord;
uniformMapper.NormalTexScale = def.NormalTexture.Scale;
_runtimeNormalTextures.Add(uniformMapper.NormalTexture);
var ext = GetTextureTransform(def.NormalTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
uniformMapper.NormalXOffset = offset;
uniformMapper.NormalXRotation = ext.Rotation;
uniformMapper.NormalXScale = scale;
if (ext.TexCoord != null) uniformMapper.NormalXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
uniformMapper.NormalXScale = new Vector2(1f,-1f);
uniformMapper.NormalXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
if (def.EmissiveTexture != null)
{
TextureId textureId = def.EmissiveTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, false, false);
uniformMapper.EmissiveTexture = _assetCache.TextureCache[textureId.Id].Texture;
uniformMapper.EmissiveTexCoord = def.EmissiveTexture.TexCoord;
var ext = GetTextureTransform(def.EmissiveTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
uniformMapper.EmissiveXOffset = offset;
uniformMapper.EmissiveXRotation = ext.Rotation;
uniformMapper.EmissiveXScale = scale;
if (ext.TexCoord != null) uniformMapper.EmissiveXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
uniformMapper.EmissiveXScale = new Vector2(1f,-1f);
uniformMapper.EmissiveXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
if (def.OcclusionTexture != null)
{
uniformMapper.OcclusionTexStrength = def.OcclusionTexture.Strength;
TextureId textureId = def.OcclusionTexture.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, true, false);
uniformMapper.OcclusionTexture = _assetCache.TextureCache[textureId.Id].Texture;
uniformMapper.OcclusionTexCoord = def.OcclusionTexture.TexCoord;
var ext = GetTextureTransform(def.OcclusionTexture);
if (ext != null)
{
CalculateYOffsetAndScale(textureId, ext, out var scale, out var offset);
uniformMapper.OcclusionXOffset = offset;
uniformMapper.OcclusionXRotation = ext.Rotation;
uniformMapper.OcclusionXScale = scale;
if (ext.TexCoord != null) uniformMapper.OcclusionXTexCoord = ext.TexCoord.Value;
SetTransformKeyword();
}
else if (IsTextureFlipped(textureId.Value))
{
uniformMapper.OcclusionXScale = new Vector2(1f,-1f);
uniformMapper.OcclusionXOffset = new Vector2(0f, 1f);
SetTransformKeyword();
}
}
// ??
uniformMapper.EmissiveFactor = QualitySettings.activeColorSpace == ColorSpace.Linear ? def.EmissiveFactor.ToUnityColorLinear() : def.EmissiveFactor.ToUnityColorLinear();
var emissiveExt = GetEmissiveStrength(def);
if (emissiveExt != null && KHR_materials_emissive_strength)
{
uniformMapper.EmissiveFactor = uniformMapper.EmissiveFactor * emissiveExt.emissiveStrength;
}
}
var vertColorMapper = mapper.Clone();
vertColorMapper.VertexColorsEnabled = true;
// if (mapper is PBRGraphMap pbrGraphMap)
// {
// MaterialExtensions.ValidateMaterialKeywords(pbrGraphMap.Material);
// }
MaterialCacheData materialWrapper = new MaterialCacheData
{
UnityMaterial = mapper.Material,
UnityMaterialWithVertexColor = vertColorMapper.Material,
GLTFMaterial = def
};
if (materialIndex >= 0)
{
_assetCache.MaterialCache[materialIndex] = materialWrapper;
}
else
{
_defaultLoadedMaterial = materialWrapper;
}
foreach (var plugin in Context.Plugins)
{
plugin.OnAfterImportMaterial(def, materialIndex, mapper.Material);
}
}
protected virtual Task ConstructMaterialImageBuffers(GLTFMaterial def)
{
var tasks = new List<Task>(8);
if (def.PbrMetallicRoughness != null)
{
var pbr = def.PbrMetallicRoughness;
if (pbr.BaseColorTexture != null)
{
var textureId = pbr.BaseColorTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (pbr.MetallicRoughnessTexture != null)
{
var textureId = pbr.MetallicRoughnessTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.CommonConstant != null)
{
if (def.CommonConstant.LightmapTexture != null)
{
var textureId = def.CommonConstant.LightmapTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.NormalTexture != null)
{
var textureId = def.NormalTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (def.OcclusionTexture != null)
{
var textureId = def.OcclusionTexture.Index;
if (!(def.PbrMetallicRoughness != null
&& def.PbrMetallicRoughness.MetallicRoughnessTexture != null
&& def.PbrMetallicRoughness.MetallicRoughnessTexture.Index.Id == textureId.Id))
{
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.EmissiveTexture != null)
{
var textureId = def.EmissiveTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
// pbr_spec_gloss extension
const string specGlossExtName = KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME;
if (def.Extensions != null && def.Extensions.ContainsKey(specGlossExtName))
{
var specGlossDef = (KHR_materials_pbrSpecularGlossinessExtension)def.Extensions[specGlossExtName];
if (specGlossDef.DiffuseTexture != null)
{
var textureId = specGlossDef.DiffuseTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (specGlossDef.SpecularGlossinessTexture != null)
{
var textureId = specGlossDef.SpecularGlossinessTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_transmission_Factory.EXTENSION_NAME))
{
var transmissionDef = (KHR_materials_transmission)def.Extensions[KHR_materials_transmission_Factory.EXTENSION_NAME];
if (transmissionDef.transmissionTexture != null)
{
var textureId = transmissionDef.transmissionTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_volume_Factory.EXTENSION_NAME))
{
var transmissionDef = (KHR_materials_volume)def.Extensions[KHR_materials_volume_Factory.EXTENSION_NAME];
if (transmissionDef.thicknessTexture != null)
{
var textureId = transmissionDef.thicknessTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_iridescence_Factory.EXTENSION_NAME))
{
var iridescenceDef = (KHR_materials_iridescence) def.Extensions[KHR_materials_iridescence_Factory.EXTENSION_NAME];
if (iridescenceDef.iridescenceTexture != null)
{
var textureId = iridescenceDef.iridescenceTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (iridescenceDef.iridescenceThicknessTexture != null)
{
var textureId = iridescenceDef.iridescenceThicknessTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_specular_Factory.EXTENSION_NAME))
{
var specularDef = (KHR_materials_specular) def.Extensions[KHR_materials_specular_Factory.EXTENSION_NAME];
if (specularDef.specularTexture != null)
{
var textureId = specularDef.specularTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (specularDef.specularColorTexture != null)
{
var textureId = specularDef.specularColorTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_sheen_Factory.EXTENSION_NAME))
{
var sheenDef = (KHR_materials_sheen)def.Extensions[KHR_materials_sheen_Factory.EXTENSION_NAME];
if (sheenDef.sheenColorTexture != null)
{
var textureId = sheenDef.sheenColorTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (sheenDef.sheenRoughnessTexture != null)
{
var textureId = sheenDef.sheenRoughnessTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_clearcoat_Factory.EXTENSION_NAME))
{
var clearCoatDef = (KHR_materials_clearcoat) def.Extensions[KHR_materials_clearcoat_Factory.EXTENSION_NAME];
if (clearCoatDef.clearcoatTexture != null)
{
var textureId = clearCoatDef.clearcoatTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (clearCoatDef.clearcoatNormalTexture != null)
{
var textureId = clearCoatDef.clearcoatNormalTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
if (clearCoatDef.clearcoatRoughnessTexture != null)
{
var textureId = clearCoatDef.clearcoatRoughnessTexture.Index;
tasks.Add(ConstructImageBuffer(textureId.Value, textureId.Id));
}
}
return Task.WhenAll(tasks);
}
protected virtual ExtTextureTransformExtension GetTextureTransform(TextureInfo def)
{
IExtension extension;
if (_gltfRoot.ExtensionsUsed != null &&
_gltfRoot.ExtensionsUsed.Contains(ExtTextureTransformExtensionFactory.EXTENSION_NAME) &&
def.Extensions != null &&
def.Extensions.TryGetValue(ExtTextureTransformExtensionFactory.EXTENSION_NAME, out extension))
{
return (ExtTextureTransformExtension)extension;
}
return null;
}
protected virtual KHR_materials_emissive_strength GetEmissiveStrength(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_emissive_strength_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_emissive_strength_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_emissive_strength) extension;
}
return null;
}
protected virtual KHR_materials_transmission GetTransmission(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_transmission_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_transmission_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_transmission) extension;
}
return null;
}
protected virtual KHR_materials_sheen GetSheen(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_sheen_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_sheen_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_sheen) extension;
}
return null;
}
protected virtual KHR_materials_dispersion GetDispersion(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_dispersion_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_dispersion_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_dispersion) extension;
}
return null;
}
protected virtual KHR_materials_volume GetVolume(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_volume_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_volume_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_volume) extension;
}
return null;
}
protected virtual KHR_materials_ior GetIOR(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_ior_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_ior_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_ior) extension;
}
return null;
}
protected virtual KHR_materials_iridescence GetIridescence(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_iridescence_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_iridescence_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_iridescence) extension;
}
return null;
}
protected virtual KHR_materials_specular GetSpecular(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_specular_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_specular_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_specular) extension;
}
return null;
}
protected virtual KHR_materials_clearcoat GetClearcoat(GLTFMaterial def)
{
if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(KHR_materials_clearcoat_Factory.EXTENSION_NAME) &&
def.Extensions != null && def.Extensions.TryGetValue(KHR_materials_clearcoat_Factory.EXTENSION_NAME, out var extension))
{
return (KHR_materials_clearcoat) extension;
}
return null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25e2614154434641a15540a7203e47f6
timeCreated: 1657012132

View File

@@ -0,0 +1,1430 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GLTF;
using GLTF.Schema;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityGLTF.Cache;
using UnityGLTF.Extensions;
#if HAVE_DRACO
using Draco;
using UnityGLTF.Plugins;
#if !HAVE_DRACO_VERSION_5
using DecodeResult = Draco.DracoMeshLoader.DecodeResult;
#endif
#endif
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
#if HAVE_DRACO
protected class DracoDecodeResult
{
public int meshIndex;
public Task<DecodeResult>[] decodeResults = new Task<DecodeResult>[0];
}
#endif
private async Task CreateMaterials(MeshPrimitive primitive)
{
bool shouldUseDefaultMaterial = primitive.Material == null;
GLTFMaterial materialToLoad = shouldUseDefaultMaterial ? DefaultMaterial : primitive.Material.Value;
if ((shouldUseDefaultMaterial && _defaultLoadedMaterial == null) ||
(!shouldUseDefaultMaterial && _assetCache.MaterialCache[primitive.Material.Id] == null))
{
await ConstructMaterial(materialToLoad, shouldUseDefaultMaterial ? -1 : primitive.Material.Id);
}
}
/// <summary>
/// Triggers loading, converting, and constructing of a UnityEngine.Mesh, and stores it in the asset cache
/// </summary>
/// <param name="mesh">The definition of the mesh to generate</param>
/// <param name="meshIndex">The index of the mesh to generate</param>
/// <param name="cancellationToken"></param>
/// <returns>A task that completes when the mesh is attached to the given GameObject</returns>
protected virtual async Task ConstructMesh(GLTFMesh mesh, int meshIndex, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_assetCache.MeshCache[meshIndex] == null)
{
throw new Exception($"Cannot generate mesh before ConstructMeshAttributes is called! (File: {_gltfFileName})");
}
else if (_assetCache.MeshCache[meshIndex].LoadedMesh)
{
return;
}
var anyHadDraco = mesh.Primitives.Any(p => p.Extensions != null && p.Extensions.ContainsKey(KHR_draco_mesh_compression_Factory.EXTENSION_NAME));
#if HAVE_DRACO
if (anyHadDraco)
{
if (Context.TryGetPlugin<DracoImportContext>(out _))
{
await PrepareDracoMesh(mesh, meshIndex);
var dracoTask = ConstructDracoMesh(mesh, meshIndex, cancellationToken);
await Task.WhenAll( dracoTask.decodeResults);
for (int i = 0; i < dracoTask.decodeResults.Length; i++)
{
_assetCache.MeshCache[meshIndex].DracoMeshDecodeResult[i] = dracoTask.decodeResults[i].Result;
}
await BuildUnityDracoMesh(mesh, meshIndex);
return;
}
else
{
throw new NotSupportedException($"Can't import model because it uses the KHR_draco_mesh_compression extension. Add the package \"com.unity.cloud.draco\" to your project to import this file. (File: {_gltfFileName})");
}
}
#else
if (anyHadDraco)
{
throw new NotSupportedException($"Can't import model because it uses the KHR_draco_mesh_compression extension. Add the package \"com.unity.cloud.draco\" to your project to import this file. (File: {_gltfFileName})");
}
#endif
cancellationToken.ThrowIfCancellationRequested();
var meshCache = _assetCache.MeshCache[meshIndex];
var unityData = CreateUnityMeshData(mesh, meshIndex);
for (int i = 0; i < mesh.Primitives.Count; ++i)
{
var primitive = mesh.Primitives[i];
var primCache = meshCache.Primitives[i];
unityData.Topology[i] = GetTopology(primitive.Mode);
unityData.DrawModes[i] = primitive.Mode;
ConvertAttributeAccessorsToUnityTypes(primCache, unityData, unityData.subMeshVertexOffset[i], i);
await CreateMaterials(primitive);
cancellationToken.ThrowIfCancellationRequested();
if (unityData.Topology[i] == MeshTopology.Triangles && primitive.Indices != null && primitive.Indices.Value != null)
{
Statistics.TriangleCount += primitive.Indices.Value.Count / 3;
}
}
Statistics.VertexCount += unityData.Vertices.Length;
await ConstructUnityMesh(unityData, meshIndex, mesh.Name);
}
private static uint[] CalculateSubMeshVertexOffset(GLTFMesh mesh, out uint totalVertCount)
{
Dictionary<int, AccessorId> accessorIds = new Dictionary<int, AccessorId>();
uint vOffset = 0;
int primIndex = 0;
uint[] vertOffsetBySubMesh = new uint[mesh.Primitives.Count];
totalVertCount = 0;
uint lastVertOffset = 0;
foreach (var p in mesh.Primitives)
{
var acc = p.Attributes[SemanticProperties.POSITION];
if (!accessorIds.ContainsKey(acc.Id))
{
accessorIds.Add(acc.Id, acc);
totalVertCount += acc.Value.Count;
vOffset = lastVertOffset;
lastVertOffset += acc.Value.Count;
}
vertOffsetBySubMesh[primIndex] = vOffset;
primIndex++;
}
return vertOffsetBySubMesh;
}
protected void PrepareUnityMeshData()
{
if (_gltfRoot.Meshes == null)
return;
for (int i = 0; i < _gltfRoot.Meshes.Count(); i++)
{
int meshIndex = i;
var mesh = _gltfRoot.Meshes[meshIndex];
var meshCache = _assetCache.MeshCache[meshIndex];
var unityData = CreateUnityMeshData(mesh, meshIndex);
for (int primIndex = 0; primIndex < mesh.Primitives.Count; ++primIndex)
{
var primitive = mesh.Primitives[primIndex];
var primCache = meshCache.Primitives[primIndex];
unityData.Topology[primIndex] = GetTopology(primitive.Mode);
unityData.DrawModes[primIndex] = primitive.Mode;
ConvertAttributeAccessorsToUnityTypes(primCache, unityData,
unityData.subMeshVertexOffset[primIndex], primIndex);
}
}
}
#if HAVE_DRACO
protected virtual async Task PrepareDracoMesh(GLTFMesh mesh, int meshIndex)
{
if (_assetCache.MeshCache[meshIndex].DracoMeshDataPrepared)
return;
Mesh.MeshDataArray meshDataArray = Mesh.AllocateWritableMeshData(mesh.Primitives.Count);
_assetCache.MeshCache[meshIndex].DracoMeshData = meshDataArray;
_assetCache.MeshCache[meshIndex].DracoMeshDecodeResult = new DecodeResult[mesh.Primitives.Count];
_assetCache.MeshCache[meshIndex].DracoMeshDataPrepared = true;
for (int i = 0; i < mesh.Primitives.Count; i++)
{
var primitive = mesh.Primitives[i];
if (primitive.Extensions == null || !primitive.Extensions.ContainsKey("KHR_draco_mesh_compression"))
continue;
if (primitive.Extensions.TryGetValue("KHR_draco_mesh_compression", out var extension))
{
var dracoExtension = (KHR_draco_mesh_compression)extension;
if (_assetCache.BufferCache[dracoExtension.bufferView.Value.Buffer.Id] == null)
await ConstructBuffer(dracoExtension.bufferView.Value.Buffer.Value,
dracoExtension.bufferView.Value.Buffer.Id);
}
}
}
protected virtual DracoDecodeResult ConstructDracoMesh(GLTFMesh mesh, int meshIndex, CancellationToken cancellationToken)
{
var firstPrim = mesh.Primitives.Count > 0 ? mesh.Primitives[0] : null;
DracoDecodeResult decodeResult = new DracoDecodeResult
{
meshIndex = meshIndex,
decodeResults = new Task<DecodeResult>[_assetCache.MeshCache[meshIndex].DracoMeshDecodeResult.Length]
};
if (!_assetCache.MeshCache[meshIndex].DracoMeshDataPrepared)
{
Debug.Log(LogType.Error, $"Draco Mesh Data is not prepared! Call PrepareDracoMesh first (File: {_gltfFileName})");
return new DracoDecodeResult();
}
if (_assetCache.MeshCache[meshIndex].HasDracoMeshData)
{
return new DracoDecodeResult();
}
_assetCache.MeshCache[meshIndex].HasDracoMeshData = true;
for (int i = 0; i < mesh.Primitives.Count; i++)
{
var primitive = mesh.Primitives[i];
if (primitive.Extensions == null || !primitive.Extensions.ContainsKey("KHR_draco_mesh_compression"))
continue;
if (primitive.Extensions.TryGetValue("KHR_draco_mesh_compression", out var extension))
{
var dracoExtension = (KHR_draco_mesh_compression)extension;
BufferCacheData bufferContents =
_assetCache.BufferCache[dracoExtension.bufferView.Value.Buffer.Id];
GLTFHelpers.LoadBufferView(dracoExtension.bufferView.Value, bufferContents.ChunkOffset,
bufferContents.bufferData, out NativeArray<byte> bufferViewData);
int weightsAttributeId = -1;
if (dracoExtension.attributes.TryGetValue("WEIGHTS_0", out var attribute))
weightsAttributeId = (int)attribute;
int jointsAttributeId = -1;
if (dracoExtension.attributes.TryGetValue("JOINTS_0", out var extensionAttribute))
jointsAttributeId = (int)extensionAttribute;
// TODO: check if normals and tangents are needed
#pragma warning disable 0219
bool hasTangents = dracoExtension.attributes.ContainsKey(SemanticProperties.TANGENT);
bool needsTangents = _options.ImportTangents != GLTFImporterNormals.None && hasTangents;
bool needsNormals = _options.ImportNormals != GLTFImporterNormals.None || needsTangents;
#pragma warning restore 0219
#if HAVE_DRACO_VERSION_5
DecodeSettings decodeSettings = DecodeSettings.ConvertSpace;
if (needsNormals)
decodeSettings |= DecodeSettings.RequireNormals;
if (needsTangents)
decodeSettings |= DecodeSettings.RequireTangents;
if (firstPrim != null && firstPrim.Targets != null)
decodeSettings |= DecodeSettings.ForceUnityVertexLayout;
var attrMap = DracoDecoder.CreateAttributeIdMap(weightsAttributeId, jointsAttributeId);
if (attrMap == null)
attrMap = new Dictionary<VertexAttribute, int>();
if (hasTangents)
attrMap.Add( VertexAttribute.Tangent, dracoExtension.attributes[SemanticProperties.TANGENT]);
if (dracoExtension.attributes.TryGetValue(SemanticProperties.COLOR_0, out var colorAttr))
attrMap.Add( VertexAttribute.Color, colorAttr);
if (dracoExtension.attributes.TryGetValue(SemanticProperties.TEXCOORD_0, out var uvAttr))
attrMap.Add( VertexAttribute.TexCoord0, uvAttr);
if (dracoExtension.attributes.TryGetValue(SemanticProperties.TEXCOORD_1, out var uv2Attr))
attrMap.Add( VertexAttribute.TexCoord1, uv2Attr);
if (dracoExtension.attributes.TryGetValue(SemanticProperties.NORMAL, out var normalAttr))
attrMap.Add( VertexAttribute.Normal, normalAttr);
if (dracoExtension.attributes.TryGetValue(SemanticProperties.POSITION, out var positionAttr))
attrMap.Add( VertexAttribute.Position, positionAttr);
decodeResult.decodeResults[i] = DracoDecoder.DecodeMesh( _assetCache.MeshCache[meshIndex].DracoMeshData[i], bufferViewData, decodeSettings, attrMap);
#else
var draco = new DracoMeshLoader();
decodeResult.decodeResults[i] = draco.ConvertDracoMeshToUnity(_assetCache.MeshCache[meshIndex].DracoMeshData[i], bufferViewData,
needsNormals, needsTangents,
weightsAttributeId, jointsAttributeId, firstPrim.Targets != null);
#endif
}
}
return decodeResult;
}
private async Task BuildUnityDracoMesh(GLTFMesh mesh, int meshIndex)
{
if (!_assetCache.MeshCache[meshIndex].HasDracoMeshData)
{
Debug.Log(LogType.Error, $"Draco Mesh Data is not decoded! Call ConstructDracoMesh first (File: {_gltfFileName})");
return;
}
foreach (var primitive in mesh.Primitives)
await CreateMaterials(primitive);
await ConstructUnityMesh(mesh, _assetCache.MeshCache[meshIndex].DracoMeshDecodeResult, _assetCache.MeshCache[meshIndex].DracoMeshData, meshIndex, mesh.Name);
}
#endif
#if HAVE_MESHOPT_DECOMPRESS
private async Task MeshOptDecodeBuffer(GLTFRoot root)
{
if (root.BufferViews == null)
return;
int bufferViewIndex = 0;
var jobHandlesList = new List<JobHandle>(root.BufferViews.Count);
var meshOptBufferViews = new Dictionary<int, NativeArray<byte>>();
var meshOptReturnValues = new NativeArray<int>( root.BufferViews.Count, Allocator.TempJob);
foreach (var bView in root.BufferViews)
{
if (bView.Extensions != null && bView.Extensions.ContainsKey(EXT_meshopt_compression_Factory.EXTENSION_NAME))
{
var meshOpt = bView.Extensions[EXT_meshopt_compression_Factory.EXTENSION_NAME] as EXT_meshopt_compression;
var arr = new NativeArray<byte>((int)(meshOpt.count * meshOpt.bufferView.ByteStride), Allocator.TempJob);
if (_assetCache.BufferCache[meshOpt.bufferView.Buffer.Id] == null)
await ConstructBuffer(meshOpt.bufferView.Buffer.Value, meshOpt.bufferView.Buffer.Id);
BufferCacheData bufferContents = _assetCache.BufferCache[meshOpt.bufferView.Buffer.Id];
GLTFHelpers.LoadBufferView(meshOpt.bufferView, bufferContents.ChunkOffset, bufferContents.bufferData, out NativeArray<byte> bufferViewData);
var jobHandle = Meshoptimizer.Decode.DecodeGltfBuffer(
new NativeSlice<int>(meshOptReturnValues,bufferViewIndex,1),
arr,
meshOpt.count,
(int)meshOpt.bufferView.ByteStride,
bufferViewData,
meshOpt.mode,
meshOpt.filter
);
jobHandlesList.Add(jobHandle);
meshOptBufferViews[bufferViewIndex] = arr;
}
bufferViewIndex++;
}
if (jobHandlesList.Count > 0)
{
JobHandle meshoptJobHandle;
using (var jobHandles = new NativeArray<JobHandle>(jobHandlesList.ToArray(), Allocator.TempJob))
{
meshoptJobHandle = JobHandle.CombineDependencies(jobHandles);
}
while (!meshoptJobHandle.IsCompleted)
{
await Task.Yield();
}
meshoptJobHandle.Complete();
}
foreach (var m in meshOptBufferViews)
{
var bufferView = root.BufferViews[m.Key];
var bufferData = await GetBufferData(bufferView.Buffer);
NativeArray<byte>.Copy(m.Value, 0, bufferData.bufferData, (int)bufferView.ByteOffset, m.Value.Length);
m.Value.Dispose();
}
meshOptReturnValues.Dispose();
}
#endif
protected void ApplyImportOptionsOnMesh(Mesh mesh)
{
bool isTriangleTopology = mesh.GetTopology(0) == MeshTopology.Triangles;
if (_options.ImportNormals == GLTFImporterNormals.None)
mesh.normals = Array.Empty<Vector3>();
else
if (isTriangleTopology)
{
if (_options.ImportNormals == GLTFImporterNormals.Calculate)
mesh.RecalculateNormals();
else if (_options.ImportNormals == GLTFImporterNormals.Import && mesh.normals.Length == 0)
mesh.RecalculateNormals();
else if (_options.ImportTangents != GLTFImporterNormals.None && mesh.normals.Length == 0)
mesh.RecalculateNormals();
}
if (_options.ImportTangents == GLTFImporterNormals.None)
mesh.tangents = Array.Empty<Vector4>();
else
if (isTriangleTopology)
{
if (_options.ImportTangents == GLTFImporterNormals.Calculate)
mesh.RecalculateTangents();
else if (_options.ImportTangents == GLTFImporterNormals.Import && mesh.tangents.Length == 0)
mesh.RecalculateTangents();
}
if (_options.SwapUVs)
{
var uv = mesh.uv;
var uv2 = mesh.uv2;
mesh.uv = uv2;
if(uv.Length > 0)
mesh.uv2 = uv;
}
}
#if HAVE_DRACO
/// <summary>
/// Populate a UnityEngine.Mesh from Draco generated SubMeshes
/// </summary>
/// <returns></returns>
protected async Task ConstructUnityMesh(GLTFMesh gltfMesh, DecodeResult[] decodeResults, Mesh.MeshDataArray meshes, int meshIndex, string meshName)
{
uint verticesLength = 0;
for (int i = 0; i < meshes.Length; i++)
verticesLength+= (uint)meshes[i].vertexCount;
Mesh mesh = new Mesh
{
name = meshName,
#if UNITY_2017_3_OR_NEWER
indexFormat = verticesLength > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16,
#endif
};
Mesh[] subMeshes = new Mesh[meshes.Length];
for (int i = 0; i < subMeshes.Length; i++)
subMeshes[i] = new Mesh();
Mesh.ApplyAndDisposeWritableMeshData(meshes, subMeshes);
CombineInstance[] combineInstances = new CombineInstance[subMeshes.Length];
for (int i = 0; i < combineInstances.Length; i++)
{
combineInstances[i] = new CombineInstance();
combineInstances[i].mesh = subMeshes[i];
}
int boneWeightCount = 0;
int bonesPerVertexCount = 0;
for (int i = 0; i < subMeshes.Length; i++)
{
if (decodeResults[i].boneWeightData == null) continue;
decodeResults[i].boneWeightData.ApplyOnMesh(subMeshes[i]);
decodeResults[i].boneWeightData.Dispose();
boneWeightCount += subMeshes[i].GetAllBoneWeights().Length;
bonesPerVertexCount += subMeshes[i].GetBonesPerVertex().Length;
}
// Custom combine all boneweights and bonePerVertex of sub meshes and apply to final combined mesh
// >> Bug(?) in CombineMeshes that does not proper copy bone weights and bones per vertex
NativeArray<BoneWeight1> allBoneWeights = new NativeArray<BoneWeight1>(boneWeightCount, Allocator.TempJob);
NativeArray<byte> allBonesPerVertex = new NativeArray<byte>(bonesPerVertexCount, Allocator.TempJob);
int currentArrayPositionBoneWeights = 0;
int currentArrayPositionBpV = 0;
for (int i = 0; i < subMeshes.Length; i++)
{
var subMeshBoneWeights = subMeshes[i].GetAllBoneWeights();
var subMeshBonesPerVertex = subMeshes[i].GetBonesPerVertex();
NativeArray<BoneWeight1>.Copy(subMeshBoneWeights, 0, allBoneWeights, currentArrayPositionBoneWeights, subMeshBoneWeights.Length);
NativeArray<byte>.Copy(subMeshBonesPerVertex, 0, allBonesPerVertex, currentArrayPositionBpV, subMeshBonesPerVertex.Length);
currentArrayPositionBoneWeights += subMeshBoneWeights.Length;
currentArrayPositionBpV += subMeshBonesPerVertex.Length;
}
mesh.CombineMeshes(combineInstances, false, false);
mesh.SetBoneWeights(allBonesPerVertex, allBoneWeights);
allBoneWeights.Dispose();
allBonesPerVertex.Dispose();
foreach (var m in subMeshes)
{
#if UNITY_EDITOR
UnityEngine.Object.DestroyImmediate(m);
#else
UnityEngine.Object.Destroy(m);
#endif
}
await YieldOnTimeoutAndThrowOnLowMemory();
var unityMeshData = CreateUnityMeshData(gltfMesh, meshIndex, true);
uint vertOffset = 0;
var meshCache = _assetCache.MeshCache[meshIndex];
unityMeshData.BoneWeights = mesh.boneWeights;
for (int i = 0; i < gltfMesh.Primitives.Count; i++)
{
var primCache = meshCache.Primitives[i];
if (IsMultithreaded)
{
await Task.Run(() => ConvertAttributeAccessorsToUnityTypes(primCache, unityMeshData, vertOffset, i));
}
else
{
ConvertAttributeAccessorsToUnityTypes(primCache, unityMeshData, vertOffset, i);
}
vertOffset += (uint)mesh.GetSubMesh(i).vertexCount;
}
mesh.RecalculateBounds();
await YieldOnTimeoutAndThrowOnLowMemory();
AddBlendShapesToMesh(unityMeshData, meshIndex, mesh);
await YieldOnTimeoutAndThrowOnLowMemory();
ApplyImportOptionsOnMesh(mesh);
if (!KeepCPUCopyOfMesh)
{
mesh.UploadMeshData(true);
}
_assetCache.MeshCache[meshIndex].LoadedMesh = mesh;
}
#endif
private UnityMeshData CreateUnityMeshData(GLTFMesh gltfMesh, int meshIndex, bool onlyMorphTargets = false)
{
if (_assetCache.UnityMeshDataCache[meshIndex] != null)
{
return _assetCache.UnityMeshDataCache[meshIndex];
}
var vertOffsetBySubMesh = CalculateSubMeshVertexOffset(gltfMesh, out var verticesLength);
UnityMeshData unityMeshData = new UnityMeshData()
{
Topology = new MeshTopology[gltfMesh.Primitives.Count],
DrawModes = new DrawMode[gltfMesh.Primitives.Count],
Indices = new int[gltfMesh.Primitives.Count][],
subMeshDataCreated = new bool[gltfMesh.Primitives.Count],
subMeshVertexOffset = vertOffsetBySubMesh
};
for (int i = 0; i < unityMeshData.subMeshDataCreated.Length; i++)
unityMeshData.subMeshDataCreated[i] = false;
var attributes = new HashSet<string>();
bool hasTargets = false;
int targetCount = 0;
foreach (var prim in gltfMesh.Primitives)
{
if (prim.Targets != null)
{
hasTargets = true;
targetCount = prim.Targets.Count;
}
if (prim.Attributes == null)
continue;
foreach (var attribute in prim.Attributes)
attributes.Add(attribute.Key);
}
if (hasTargets)
{
unityMeshData.MorphTargetVertices = new Vector3[targetCount][];
unityMeshData.MorphTargetNormals = new Vector3[targetCount][];
unityMeshData.MorphTargetTangents = new Vector3[targetCount][];
foreach (var prim in gltfMesh.Primitives)
{
for (int i = 0; i < prim.Targets.Count; i++)
{
if (unityMeshData.MorphTargetVertices[i] == null && prim.Targets[i].ContainsKey(SemanticProperties.POSITION))
{
unityMeshData.MorphTargetVertices[i] = new Vector3[verticesLength];
for (int j = 0; j < verticesLength; j++)
unityMeshData.MorphTargetVertices[i][j] = Vector3.zero;
}
if (unityMeshData.MorphTargetNormals[i] == null && prim.Targets[i].ContainsKey(SemanticProperties.NORMAL))
{
unityMeshData.MorphTargetNormals[i] = new Vector3[verticesLength];
for (int j = 0; j < verticesLength; j++)
unityMeshData.MorphTargetNormals[i][j] = Vector3.zero;
}
if (unityMeshData.MorphTargetTangents[i] == null && prim.Targets[i].ContainsKey(SemanticProperties.TANGENT))
{
unityMeshData.MorphTargetTangents[i] = new Vector3[verticesLength];
for (int j = 0; j < verticesLength; j++)
unityMeshData.MorphTargetTangents[i][j] = Vector3.zero;
}
}
}
}
_assetCache.UnityMeshDataCache[meshIndex] = unityMeshData;
if (!onlyMorphTargets)
{
unityMeshData.Vertices = new Vector3[verticesLength];
unityMeshData.Normals = attributes.Contains(SemanticProperties.NORMAL)
? new Vector3[verticesLength]
: null;
unityMeshData.Tangents = attributes.Contains(SemanticProperties.TANGENT)
? new Vector4[verticesLength]
: null;
unityMeshData.Uv1 = attributes.Contains(SemanticProperties.TEXCOORD_0)
? new Vector2[verticesLength]
: null;
unityMeshData.Uv2 = attributes.Contains(SemanticProperties.TEXCOORD_1)
? new Vector2[verticesLength]
: null;
unityMeshData.Uv3 = attributes.Contains(SemanticProperties.TEXCOORD_2)
? new Vector2[verticesLength]
: null;
unityMeshData.Uv4 = attributes.Contains(SemanticProperties.TEXCOORD_3)
? new Vector2[verticesLength]
: null;
unityMeshData.Colors = attributes.Contains(SemanticProperties.COLOR_0)
? new Color[verticesLength]
: null;
unityMeshData.BoneWeights = attributes.Contains(SemanticProperties.WEIGHTS_0)
? new BoneWeight[verticesLength]
: null;
}
return unityMeshData;
}
/// <summary>
/// Populate a UnityEngine.Mesh from preloaded and preprocessed buffer data
/// </summary>
/// <param name="unityMeshData"></param>
/// <param name="meshIndex"></param>
/// <param name="meshName"></param>
/// <returns></returns>
protected async Task ConstructUnityMesh(UnityMeshData unityMeshData, int meshIndex, string meshName)
{
await YieldOnTimeoutAndThrowOnLowMemory();
Mesh mesh = new Mesh
{
name = meshName,
#if UNITY_2017_3_OR_NEWER
indexFormat = unityMeshData.Vertices.Length > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16,
#endif
};
mesh.vertices = unityMeshData.Vertices;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.normals = unityMeshData.Normals;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.tangents = unityMeshData.Tangents;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.uv = unityMeshData.Uv1;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.uv2 = unityMeshData.Uv2;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.uv3 = unityMeshData.Uv3;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.uv4 = unityMeshData.Uv4;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.colors = unityMeshData.Colors;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.boneWeights = unityMeshData.BoneWeights;
await YieldOnTimeoutAndThrowOnLowMemory();
mesh.subMeshCount = unityMeshData.Indices.Length;
for (int i = 0; i < unityMeshData.Indices.Length; i++)
{
mesh.SetIndices(unityMeshData.Indices[i], unityMeshData.Topology[i], i, false,
(int)unityMeshData.subMeshVertexOffset[i]);
}
mesh.RecalculateBounds();
await YieldOnTimeoutAndThrowOnLowMemory();
AddBlendShapesToMesh(unityMeshData, meshIndex, mesh);
await YieldOnTimeoutAndThrowOnLowMemory();
ApplyImportOptionsOnMesh(mesh);
if (!KeepCPUCopyOfMesh)
{
mesh.UploadMeshData(true);
}
_assetCache.MeshCache[meshIndex].LoadedMesh = mesh;
// Free up some memory
unityMeshData.Clear();
}
private void AddBlendShapesToMesh(UnityMeshData unityMeshData, int meshIndex, Mesh mesh)
{
if (unityMeshData.MorphTargetVertices != null && _gltfRoot.Meshes != null)
{
var gltfMesh = _gltfRoot.Meshes[meshIndex];
var firstPrim = gltfMesh.Primitives[0];
// TODO theoretically there could be multiple prims and only one of them has morph targets
for (int i = 0; i < firstPrim.Targets.Count; i++)
{
var targetName = _options.ImportBlendShapeNames ? ((gltfMesh.TargetNames != null && gltfMesh.TargetNames.Count > i) ? gltfMesh.TargetNames[i] : $"Morphtarget{i}") : i.ToString();
mesh.AddBlendShapeFrame(targetName, 1f * _options.BlendShapeFrameWeight,
unityMeshData.MorphTargetVertices[i],
unityMeshData.MorphTargetNormals != null && unityMeshData.MorphTargetNormals[i] != null ? unityMeshData.MorphTargetNormals[i] : null,
unityMeshData.MorphTargetTangents != null && unityMeshData.MorphTargetTangents[i] != null ? unityMeshData.MorphTargetTangents[i] : null
);
}
}
}
protected virtual async Task ConstructMeshTargetsPrepareBuffers(MeshPrimitive primitive, int meshIndex, int primitiveIndex)
{
var newTargets = new List<Dictionary<string, AttributeAccessor>>(primitive.Targets.Count);
_assetCache.MeshCache[meshIndex].Primitives[primitiveIndex].Targets = newTargets;
// Prepare Buffer Data
for (int i = 0; i < primitive.Targets.Count; i++)
{
var target = primitive.Targets[i];
newTargets.Add(new Dictionary<string, AttributeAccessor>());
foreach (var targetAttribute in target)
{
BufferId bufferIdPair = null;
if (targetAttribute.Value.Value.Sparse == null)
{
bufferIdPair = targetAttribute.Value.Value.BufferView.Value.Buffer;
}
else
{
// When using Draco, it's possible the BufferView is null
if (primitive.Attributes[targetAttribute.Key].Value.BufferView == null)
{
continue;
}
bufferIdPair = primitive.Attributes[targetAttribute.Key].Value.BufferView.Value.Buffer;
targetAttribute.Value.Value.BufferView =
primitive.Attributes[targetAttribute.Key].Value.BufferView;
}
GLTFBuffer buffer = bufferIdPair.Value;
int bufferID = bufferIdPair.Id;
if (_assetCache.BufferCache[bufferID] == null)
{
await ConstructBuffer(buffer, bufferID);
}
newTargets[i][targetAttribute.Key] = new AttributeAccessor
{
AccessorId = targetAttribute.Value,
bufferData = _assetCache.BufferCache[bufferID].bufferData,
Offset = (uint)_assetCache.BufferCache[bufferID].ChunkOffset
};
// if this buffer isn't sparse, we're done here
if (targetAttribute.Value.Value.Sparse == null) continue;
var bufferId = targetAttribute.Value.Value.Sparse.Values.BufferView.Value.Buffer;
await GetBufferData(bufferId);
bufferId = targetAttribute.Value.Value.Sparse.Indices.BufferView.Value.Buffer;
await GetBufferData(bufferId);
}
}
}
protected virtual void ConstructMeshTargets(MeshPrimitive primitive, int meshIndex, int primitiveIndex)
{
float scaleFactor = 0f;
bool hasScale = false;
#if UNITY_EDITOR
hasScale = Context != null && !Mathf.Approximately(Context.ImportScaleFactor, 1f);
scaleFactor = hasScale ? Context.ImportScaleFactor : 1f;
#endif
var newTargets = _assetCache.MeshCache[meshIndex].Primitives[primitiveIndex].Targets;
for (int i = 0; i < primitive.Targets.Count; i++)
{
var target = primitive.Targets[i];
var att = newTargets[i];
NumericArray[] sparseNormals = null;
NumericArray[] sparsePositions = null;
NumericArray[] sparseTangents = null;
const string NormalKey = "NORMAL";
const string PositionKey = "POSITION";
const string TangentKey = "TANGENT";
// normals, positions, tangents
foreach (var targetAttribute in target)
{
if (targetAttribute.Value.Value.Sparse != null)
{
// When using Draco, it's possible the BufferView is null
if (primitive.Attributes[targetAttribute.Key].Value.BufferView == null)
{
continue;
}
}
// if this buffer isn't sparse, we're done here
if (targetAttribute.Value.Value.Sparse == null) continue;
// Values
var bufferId = targetAttribute.Value.Value.Sparse.Values.BufferView.Value.Buffer;
var bufferData = _assetCache.BufferCache[bufferId.Id];
AttributeAccessor sparseValues = new AttributeAccessor
{
AccessorId = targetAttribute.Value,
bufferData = bufferData.bufferData,
Offset = (uint)bufferData.ChunkOffset
};
GLTFHelpers.LoadBufferView(sparseValues.AccessorId.Value.Sparse.Values.BufferView.Value,
sparseValues.Offset, sparseValues.bufferData, out NativeArray<byte> bufferViewCache1);
// Indices
bufferId = targetAttribute.Value.Value.Sparse.Indices.BufferView.Value.Buffer;
bufferData = _assetCache.BufferCache[bufferId.Id];
AttributeAccessor sparseIndices = new AttributeAccessor
{
AccessorId = targetAttribute.Value,
bufferData = bufferData.bufferData,
Offset = (uint)bufferData.ChunkOffset,
};
GLTFHelpers.LoadBufferView(sparseIndices.AccessorId.Value.Sparse.Indices.BufferView.Value,
sparseIndices.Offset, sparseIndices.bufferData, out NativeArray<byte> bufferViewCache2);
switch (targetAttribute.Key)
{
case NormalKey:
sparseNormals = new NumericArray[2];
Accessor.AsSparseFloat3Array(targetAttribute.Value.Value, ref sparseNormals[0],
bufferViewCache1, 0, targetAttribute.Value.Value.Normalized);
Accessor.AsSparseUIntArray(targetAttribute.Value.Value, ref sparseNormals[1],
bufferViewCache2);
break;
case PositionKey:
sparsePositions = new NumericArray[2];
if (hasScale)
Accessor.AsSparseFloat3ArrayConversion(targetAttribute.Value.Value, ref sparsePositions[0],
bufferViewCache1, scaleFactor, 0, targetAttribute.Value.Value.Normalized);
else
Accessor.AsSparseFloat3Array(targetAttribute.Value.Value, ref sparsePositions[0],
bufferViewCache1, 0, targetAttribute.Value.Value.Normalized);
Accessor.AsSparseUIntArray(targetAttribute.Value.Value, ref sparsePositions[1],
bufferViewCache2);
break;
case TangentKey:
sparseTangents = new NumericArray[2];
Accessor.AsSparseFloat3Array(targetAttribute.Value.Value, ref sparseTangents[0],
bufferViewCache1, 0, targetAttribute.Value.Value.Normalized);
Accessor.AsSparseUIntArray(targetAttribute.Value.Value, ref sparseTangents[1],
bufferViewCache2);
break;
}
}
GLTFHelpers.BuildTargetAttributes(ref att, scaleFactor);
if (sparseNormals != null)
{
var current = att[NormalKey].AccessorContent;
NumericArray before = new NumericArray();
before.AsFloat3s = new float3[current.AsFloat3s.Length];
for (int j = 0; j < sparseNormals[1].AsUInts.Length; j++)
{
before.AsFloat3s[sparseNormals[1].AsUInts[j]] = sparseNormals[0].AsFloat3s[j];
}
att[NormalKey].AccessorContent = before;
}
if (sparsePositions != null)
{
var current = att[PositionKey].AccessorContent;
NumericArray before = new NumericArray();
before.AsFloat3s = new float3[current.AsFloat3s.Length];
for (int j = 0; j < sparsePositions[1].AsUInts.Length; j++)
{
before.AsFloat3s[sparsePositions[1].AsUInts[j]] = sparsePositions[0].AsFloat3s[j];
}
att[PositionKey].AccessorContent = before;
}
if (sparseTangents != null)
{
var current = att[TangentKey].AccessorContent;
NumericArray before = new NumericArray();
before.AsFloat3s = new float3[current.AsFloat3s.Length];
for (int j = 0; j < sparseTangents[1].AsUInts.Length; j++)
{
before.AsFloat3s[sparseTangents[1].AsUInts[j]] = sparseTangents[0].AsFloat3s[j];
}
att[TangentKey].AccessorContent = before;
}
TransformTargets(ref att);
}
}
private void FreeUpAccessorContents()
{
if (_gltfRoot.Meshes == null)
return;
for (int meshIndex = 0; meshIndex < _gltfRoot.Meshes.Count; meshIndex++)
{
var gltfMesh = _gltfRoot.Meshes[meshIndex];
for (int primIndex = 0; primIndex < gltfMesh.Primitives.Count; primIndex++)
{
var primCache = _assetCache.MeshCache[meshIndex].Primitives[primIndex];
if (primCache.meshAttributesCreated)
{
foreach (var att in primCache.Attributes)
{
att.Value.AccessorContent = new NumericArray();
}
foreach (var t in primCache.SparseAccessors)
{
t.Value.sparseValues.AccessorContent = new NumericArray();
t.Value.sparseIndices.AccessorContent = new NumericArray();
}
foreach (var t in primCache.Targets)
{
foreach (var att in t)
{
att.Value.AccessorContent = new NumericArray();
}
}
}
}
}
}
private async Task PreparePrimitiveAttributes()
{
if (_gltfRoot.Meshes == null)
return;
for (int meshIndex = 0; meshIndex < _gltfRoot.Meshes.Count; meshIndex++)
{
if (_assetCache.MeshCache[meshIndex] == null)
_assetCache.MeshCache[meshIndex] = new MeshCacheData();
var gltfMesh = _gltfRoot.Meshes[meshIndex];
for (int i = 0; i < gltfMesh.Primitives.Count; i++)
{
await ConstructPrimitiveAttributes(gltfMesh.Primitives[i], meshIndex, i);
if (gltfMesh.Primitives[i].Targets != null)
await ConstructMeshTargetsPrepareBuffers(gltfMesh.Primitives[i], meshIndex, i);
}
#if HAVE_DRACO
if (Context.TryGetPlugin<DracoImportContext>(out _))
{
var anyHadDraco = gltfMesh.Primitives.Any(p =>
p.Extensions != null &&
p.Extensions.ContainsKey(KHR_draco_mesh_compression_Factory.EXTENSION_NAME));
if (anyHadDraco)
{
await PrepareDracoMesh(gltfMesh, meshIndex);
}
}
#endif
}
#if HAVE_DRACO
if (Context.TryGetPlugin<DracoImportContext>(out _))
{
List<DracoDecodeResult> dracoDecodeResults = new List<DracoDecodeResult>();
for (int meshIndex = 0; meshIndex < _gltfRoot.Meshes.Count; meshIndex++)
{
var gltfMesh = _gltfRoot.Meshes[meshIndex];
var anyHadDraco = gltfMesh.Primitives.Any(p =>
p.Extensions != null &&
p.Extensions.ContainsKey(KHR_draco_mesh_compression_Factory.EXTENSION_NAME));
if (anyHadDraco)
{
dracoDecodeResults.Add(ConstructDracoMesh(gltfMesh, meshIndex, CancellationToken.None));
}
}
await Task.WhenAll(dracoDecodeResults.Select(d => d.decodeResults).SelectMany(d => d));
for (int i = 0; i < dracoDecodeResults.Count; i++)
{
int meshIndex = dracoDecodeResults[i].meshIndex;
for (int j = 0; j < dracoDecodeResults[i].decodeResults.Length; j++)
{
var decodeResult = dracoDecodeResults[i].decodeResults[j].Result;
_assetCache.MeshCache[dracoDecodeResults[i].meshIndex].DracoMeshDecodeResult[j] = decodeResult;
if (!decodeResult.success)
{
Debug.Log(LogType.Error, $"Error decoding draco mesh (File: {_gltfFileName})", this);
_assetCache.MeshCache[meshIndex].DracoMeshData.Dispose();
}
Statistics.VertexCount += _assetCache.MeshCache[meshIndex].DracoMeshData[j].vertexCount;
}
}
}
#endif
void BuildMeshesAttributes()
{
for (int meshIndex = 0; meshIndex < _gltfRoot.Meshes.Count; meshIndex++)
{
var gltfMesh = _gltfRoot.Meshes[meshIndex];
for (int primIndex = 0; primIndex < gltfMesh.Primitives.Count; primIndex++)
{
var primCache = _assetCache.MeshCache[meshIndex].Primitives[primIndex];
if (!primCache.meshAttributesCreated)
{
primCache.meshAttributesCreated = true;
GLTFHelpers.BuildMeshAttributes(ref primCache.Attributes,ref primCache.SparseAccessors);
if (gltfMesh.Primitives[primIndex].Targets != null)
ConstructMeshTargets(gltfMesh.Primitives[primIndex], meshIndex, primIndex);
}
}
}
}
if (IsMultithreaded)
await Task.Run(BuildMeshesAttributes);
else
BuildMeshesAttributes();
}
private async Task ConstructMeshAttributes(GLTFMesh mesh, MeshId meshId)
{
int meshIndex = meshId.Id;
if (_assetCache.MeshCache[meshIndex] == null)
_assetCache.MeshCache[meshIndex] = new MeshCacheData();
for (int i = 0; i < mesh.Primitives.Count; ++i)
{
MeshPrimitive primitive = mesh.Primitives[i];
await ConstructPrimitiveAttributes(primitive, meshIndex, i);
var primCache = _assetCache.MeshCache[meshIndex].Primitives[i];
if (!primCache.meshAttributesCreated)
{
primCache.meshAttributesCreated = true;
GLTFHelpers.BuildMeshAttributes(ref primCache.Attributes, ref primCache.SparseAccessors);
if (primitive.Targets != null)
{
// read mesh primitive targets into assetcache
await ConstructMeshTargetsPrepareBuffers(primitive, meshIndex, i);
ConstructMeshTargets(primitive, meshIndex, i);
}
}
if (primitive.Material != null)
{
await ConstructMaterialImageBuffers(primitive.Material.Value);
}
}
}
protected virtual async Task ConstructPrimitiveAttributes(MeshPrimitive primitive, int meshIndex, int primitiveIndex)
{
if (_assetCache.MeshCache[meshIndex].Primitives.Count-1 >= primitiveIndex)
return;
var primData = new MeshCacheData.PrimitiveCacheData();
_assetCache.MeshCache[meshIndex].Primitives.Add(primData);
foreach (var attributePair in primitive.Attributes)
{
if (attributePair.Value.Value.BufferView == null) // When Draco Compression is used, the bufferView is null
continue;
if (!primData.Attributes.ContainsKey(attributePair.Key))
{
var bufferId = attributePair.Value.Value.BufferView.Value.Buffer;
var bufferData = await GetBufferData(bufferId);
primData.Attributes[attributePair.Key] = new AttributeAccessor
{
AccessorId = attributePair.Value,
bufferData = bufferData.bufferData,
Offset = (uint)bufferData.ChunkOffset
};
}
var sparse = attributePair.Value.Value.Sparse;
if (sparse != null)
{
if (!primData.Attributes.ContainsKey(attributePair.Key))
{
var sparseBufferId = sparse.Values.BufferView.Value.Buffer;
var sparseBufferData = await GetBufferData(sparseBufferId);
AttributeAccessor sparseValues = new AttributeAccessor
{
AccessorId = attributePair.Value,
bufferData = sparseBufferData.bufferData,
Offset = (uint)sparseBufferData.ChunkOffset
};
var sparseIndicesBufferId = sparse.Indices.BufferView.Value.Buffer;
var sparseIndicesBufferData = await GetBufferData(sparseIndicesBufferId);
AttributeAccessor sparseIndices = new AttributeAccessor
{
AccessorId = attributePair.Value,
bufferData = sparseIndicesBufferData.bufferData,
Offset = (uint)sparseIndicesBufferData.ChunkOffset
};
primData.SparseAccessors[attributePair.Key] = (sparseIndices, sparseValues);
}
}
}
if (primitive.Indices != null && primitive.Indices.Value.BufferView != null)
{
if (!primData.Attributes.ContainsKey(SemanticProperties.INDICES))
{
var bufferId = primitive.Indices.Value.BufferView.Value.Buffer;
var bufferData = await GetBufferData(bufferId);
primData.Attributes[SemanticProperties.INDICES] = new AttributeAccessor
{
AccessorId = primitive.Indices,
bufferData = bufferData.bufferData,
Offset = (uint)bufferData.ChunkOffset
};
}
}
}
protected void ConvertAttributeAccessorsToUnityTypes(
MeshCacheData.PrimitiveCacheData primData,
UnityMeshData unityData,
uint vertOffset,
int indexOffset)
{
if (unityData.subMeshDataCreated[indexOffset])
return;
unityData.subMeshDataCreated[indexOffset] = true;
var meshAttributes = primData.Attributes;
uint vertexCount = 0;
if (meshAttributes.TryGetValue(SemanticProperties.POSITION, out var attribute))
{
vertexCount = attribute.AccessorId.Value.Count;
}
int[] indices = null;
if (meshAttributes.TryGetValue(SemanticProperties.INDICES, out var indicesAccessor))
{
indices = indicesAccessor.AccessorContent.AsUInts.ToIntArrayRaw();
switch (unityData.DrawModes[indexOffset])
{
case DrawMode.LineLoop:
if (indices[indices.Length - 1] != indices[0])
{
Array.Resize(ref indices, indices.Length + 1);
indices[indices.Length - 1] = indices[0];
}
break;
case DrawMode.Triangles:
SchemaExtensions.FlipTriangleFaces(indices);
break;
case DrawMode.TriangleStrip:
indices = MeshPrimitive.ConvertTriangleStripsToTriangles(indices);
SchemaExtensions.FlipTriangleFaces(indices);
break;
case DrawMode.TriangleFan:
indices = MeshPrimitive.ConvertTriangleFanToTriangles(indices);
SchemaExtensions.FlipTriangleFaces(indices);
break;
}
}
else
{
switch (unityData.DrawModes[indexOffset])
{
case DrawMode.Points:
indices = MeshPrimitive.GeneratePoints((int)vertexCount);
break;
case DrawMode.Lines:
indices = MeshPrimitive.GenerateLines((int)vertexCount);
break;
case DrawMode.LineLoop:
indices = MeshPrimitive.GenerateLineLoop((int)vertexCount);
break;
case DrawMode.LineStrip:
indices = MeshPrimitive.GenerateLineStrip((int)vertexCount);
break;
case DrawMode.Triangles:
indices = MeshPrimitive.GenerateTriangles((int)vertexCount);
break;
case DrawMode.TriangleStrip:
indices = MeshPrimitive.GenerateTriangleStrips((int)vertexCount);
break;
case DrawMode.TriangleFan:
indices = MeshPrimitive.GenerateTriangleFans((int)vertexCount);
break;
}
}
if (indices != null)
unityData.Indices[indexOffset] = indices;
// Only add weight/joint data when it's not already added to the unity mesh data !
if (meshAttributes.ContainsKey(SemanticProperties.Weight[0]) && meshAttributes.ContainsKey(SemanticProperties.Joint[0])
&& !unityData.alreadyAddedAccessors.Contains(meshAttributes[SemanticProperties.Weight[0]].AccessorId.Id))
{
unityData.alreadyAddedAccessors.Add(meshAttributes[SemanticProperties.Weight[0]].AccessorId.Id);
CreateBoneWeightArray(
meshAttributes[SemanticProperties.Joint[0]].AccessorContent.AsFloat4s.ToUnityVector4Raw(),
meshAttributes[SemanticProperties.Weight[0]].AccessorContent.AsFloat4s.ToUnityVector4Raw(),
ref unityData.BoneWeights,
vertOffset);
}
// Only add vertex data when it's not already added to the unity mesh data !
if (meshAttributes.ContainsKey(SemanticProperties.POSITION) && !unityData.alreadyAddedAccessors.Contains(meshAttributes[SemanticProperties.POSITION].AccessorId.Id))
{
if (meshAttributes.TryGetValue(SemanticProperties.POSITION, out var attrPos))
{
unityData.alreadyAddedAccessors.Add(attrPos.AccessorId.Id);
attrPos.AccessorContent.AsFloat3s.ToUnityVector3Raw(unityData.Vertices, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.NORMAL, out var attrNorm))
{
attrNorm.AccessorContent.AsFloat3s.ToUnityVector3Raw(unityData.Normals, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.TANGENT, out var attrTang))
{
attrTang.AccessorContent.AsFloat4s.ToUnityVector4Raw(unityData.Tangents, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.TexCoord[0], out var attrTex0))
{
attrTex0.AccessorContent.AsFloat2s.ToUnityVector2Raw(unityData.Uv1, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.TexCoord[1], out var attrTex1))
{
attrTex1.AccessorContent.AsFloat2s.ToUnityVector2Raw(unityData.Uv2, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.TexCoord[2], out var attrTex2))
{
attrTex2.AccessorContent.AsFloat2s.ToUnityVector2Raw(unityData.Uv3, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.TexCoord[3], out var attrTex3))
{
attrTex3.AccessorContent.AsFloat2s.ToUnityVector2Raw(unityData.Uv4, (int)vertOffset);
}
if (meshAttributes.TryGetValue(SemanticProperties.Color[0], out var attrColor))
{
if (_activeColorSpace == ColorSpace.Gamma)
attrColor.AccessorContent.AsFloat4s.ToUnityColorRaw(unityData.Colors, (int)vertOffset);
else
attrColor.AccessorContent.AsFloat4s.ToUnityColorLinear(unityData.Colors, (int)vertOffset);
}
}
var targets = primData.Targets;
if (targets != null)
{
for (int i = 0; i < targets.Count; ++i)
{
if (targets[i].TryGetValue(SemanticProperties.POSITION, out var tarAttrPos) && !unityData.alreadyAddedAccessors.Contains(tarAttrPos.AccessorId.Id))
{
unityData.alreadyAddedAccessors.Add(tarAttrPos.AccessorId.Id);
tarAttrPos.AccessorContent.AsFloat3s.ToUnityVector3Raw(unityData.MorphTargetVertices[i], (int)vertOffset);
}
if (targets[i].TryGetValue(SemanticProperties.NORMAL, out var tarAttrNorm) && !unityData.alreadyAddedAccessors.Contains(tarAttrNorm.AccessorId.Id))
{
unityData.alreadyAddedAccessors.Add(tarAttrNorm.AccessorId.Id);
tarAttrNorm.AccessorContent.AsFloat3s.ToUnityVector3Raw(unityData.MorphTargetNormals[i], (int)vertOffset);
}
if (targets[i].TryGetValue(SemanticProperties.TANGENT, out var tarAttrTang) && !unityData.alreadyAddedAccessors.Contains(tarAttrTang.AccessorId.Id))
{
unityData.alreadyAddedAccessors.Add(tarAttrTang.AccessorId.Id);
tarAttrTang.AccessorContent.AsFloat3s.ToUnityVector3Raw(unityData.MorphTargetTangents[i], (int)vertOffset);
}
}
}
}
// Flip vectors to Unity coordinate system
private void TransformTargets(ref Dictionary<string, AttributeAccessor> attributeAccessors)
{
if (attributeAccessors.ContainsKey(SemanticProperties.POSITION))
{
AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.POSITION];
SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale);
}
if (attributeAccessors.ContainsKey(SemanticProperties.NORMAL))
{
AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.NORMAL];
SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale);
}
if (attributeAccessors.ContainsKey(SemanticProperties.TANGENT))
{
AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.TANGENT];
SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale);
}
}
private static void AddNewBufferAndViewToAccessor(byte[] data, Accessor accessor, GLTFRoot _gltfRoot)
{
if (_gltfRoot.Buffers == null)
_gltfRoot.Buffers = new List<GLTFBuffer>();
if (_gltfRoot.BufferViews == null)
_gltfRoot.BufferViews = new List<BufferView>();
_gltfRoot.Buffers.Add(new GLTFBuffer() { ByteLength = (uint) data.Length });
_gltfRoot.BufferViews.Add(new BufferView() { ByteLength = (uint) data.Length, ByteOffset = 0, Buffer = new BufferId() { Id = _gltfRoot.Buffers.Count, Root = _gltfRoot } });
accessor.BufferView = new BufferViewId() { Id = _gltfRoot.BufferViews.Count - 1, Root = _gltfRoot };
}
protected MeshTopology GetTopology(DrawMode mode)
{
switch (mode)
{
case DrawMode.Points: return MeshTopology.Points;
case DrawMode.Lines: return MeshTopology.Lines;
case DrawMode.LineStrip: return MeshTopology.LineStrip;
case DrawMode.Triangles: return MeshTopology.Triangles;
case DrawMode.LineLoop: return MeshTopology.LineStrip;
case DrawMode.TriangleStrip: return MeshTopology.Triangles;
case DrawMode.TriangleFan: return MeshTopology.Triangles;
}
throw new Exception("Unity does not support glTF draw mode: " + mode + $" (File: {_gltfFileName})");
}
/// <summary>
/// Allocate a generic type 2D array. The size is depending on the given parameters.
/// </summary>
/// <param name="x">Defines the depth of the arrays first dimension</param>
/// <param name="y">>Defines the depth of the arrays second dimension</param>
/// <returns></returns>
private static T[][] Allocate2dArray<T>(uint x, uint y)
{
var result = new T[x][];
for (var i = 0; i < x; i++) result[i] = new T[y];
return result;
}
private void CheckForMeshDuplicates()
{
if (_gltfRoot.Meshes == null)
return;
Dictionary<int, int> meshDuplicates = new Dictionary<int, int>();
for (int meshIndex = 0; meshIndex < _gltfRoot.Meshes.Count; meshIndex++)
{
if (meshDuplicates.ContainsKey(meshIndex))
continue;
for (int i = meshIndex+1; i < _gltfRoot.Meshes.Count; i++)
{
if (i == meshIndex)
continue;
if (_assetCache.MeshCache[i] == null)
continue;
if (_assetCache.UnityMeshDataCache[i] == null
|| _assetCache.UnityMeshDataCache[meshIndex] == null)
continue;
var meshIsEqual = _assetCache.UnityMeshDataCache[i]
.IsEqual(_assetCache.UnityMeshDataCache[meshIndex]);
if (meshIsEqual)
meshDuplicates[i] = meshIndex;
}
}
foreach (var dm in meshDuplicates)
{
if (_gltfRoot.Nodes == null) continue;
for (int i = 0; i < _gltfRoot.Nodes.Count; i++)
{
if (_gltfRoot.Nodes[i].Mesh != null && _gltfRoot.Nodes[i].Mesh.Id == dm.Key)
{
if (_gltfRoot.Nodes[i].Weights == null && _gltfRoot.Meshes[dm.Value].Weights != null)
_gltfRoot.Nodes[i].Weights = _gltfRoot.Meshes[_gltfRoot.Nodes[i].Mesh.Id].Weights;
_gltfRoot.Nodes[i].Mesh.Id = dm.Value;
}
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7f4930a0205140bbbcfaa67b79ab587b
timeCreated: 1657013019

View File

@@ -0,0 +1,98 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using GLTF;
using GLTF.Schema;
using Unity.Mathematics;
using UnityEngine;
using UnityGLTF.Extensions;
using Matrix4x4 = GLTF.Math.Matrix4x4;
using Vector4 = UnityEngine.Vector4;
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
protected virtual async Task SetupBones(Skin skin, SkinnedMeshRenderer renderer, CancellationToken cancellationToken)
{
var boneCount = skin.Joints.Count;
Transform[] bones = new Transform[boneCount];
// TODO: build bindpose arrays only once per skin, instead of once per node
float4x4[] gltfBindPoses = null;
if (skin.InverseBindMatrices != null)
{
var bufferId = skin.InverseBindMatrices.Value.BufferView.Value.Buffer;
var bufferData = await GetBufferData(bufferId);
AttributeAccessor attributeAccessor = new AttributeAccessor
{
AccessorId = skin.InverseBindMatrices,
bufferData = bufferData.bufferData,
Offset = bufferData.ChunkOffset
};
GLTFHelpers.BuildBindPoseSamplers(ref attributeAccessor);
gltfBindPoses = attributeAccessor.AccessorContent.AsMatrix4x4s;
}
UnityEngine.Matrix4x4[] bindPoses = new UnityEngine.Matrix4x4[boneCount];
for (int i = 0; i < boneCount; i++)
{
var node = await GetNode(skin.Joints[i].Id, cancellationToken);
bones[i] = node.transform;
bindPoses[i] = gltfBindPoses != null ? gltfBindPoses[i].ToUnityMatrix4x4Convert() : UnityEngine.Matrix4x4.identity;
}
if (skin.Skeleton != null)
{
var rootBoneNode = await GetNode(skin.Skeleton.Id, cancellationToken);
renderer.rootBone = rootBoneNode.transform;
}
else
{
var rootBoneId = GLTFHelpers.FindCommonAncestor(skin.Joints);
if (rootBoneId != null)
{
var rootBoneNode = await GetNode(rootBoneId.Id, cancellationToken);
renderer.rootBone = rootBoneNode.transform;
}
else
{
throw new Exception($"glTF skin joints do not share a root node! (File: {_gltfFileName})");
}
}
renderer.sharedMesh.bindposes = bindPoses;
renderer.bones = bones;
}
private void CreateBoneWeightArray(Vector4[] joints, Vector4[] weights, ref BoneWeight[] destArr, uint offset = 0)
{
// normalize weights (built-in normalize function only normalizes three components)
for (int i = 0; i < weights.Length; i++)
{
var weightSum = (weights[i].x + weights[i].y + weights[i].z + weights[i].w);
if (!Mathf.Approximately(weightSum, 0))
{
weights[i] /= weightSum;
}
}
for (int i = 0; i < joints.Length; i++)
{
destArr[offset + i].boneIndex0 = (int)joints[i].x;
destArr[offset + i].boneIndex1 = (int)joints[i].y;
destArr[offset + i].boneIndex2 = (int)joints[i].z;
destArr[offset + i].boneIndex3 = (int)joints[i].w;
destArr[offset + i].weight0 = weights[i].x;
destArr[offset + i].weight1 = weights[i].y;
destArr[offset + i].weight2 = weights[i].z;
destArr[offset + i].weight3 = weights[i].w;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cef61a6932d545e0aca7bb7cd4a819db
timeCreated: 1657012769

View File

@@ -0,0 +1,726 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using GLTF;
using GLTF.Schema;
using GLTF.Utilities;
using Unity.Collections;
using UnityEngine;
using UnityGLTF.Cache;
using UnityGLTF.Extensions;
using UnityGLTF.Loader;
using UnityGLTF.Plugins;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Build;
#endif
namespace UnityGLTF
{
public partial class GLTFSceneImporter
{
internal const string EMPTY_TEXTURE_NAME_SUFFIX = " \0";
private Dictionary<int, int> _imageDeduplicationLinks; // Org Image ID -> distinct Image ID
private class TextureData
{
public Texture Texture = null;
public int TexCoord = 0;
public double DataMultiplier = 1;
public Vector2 Offset = Vector2.zero;
public double Rotation = 0;
public Vector2 Scale = Vector2.one;
public Nullable<int> TexCoordExtra = 0;
}
private async Task CreateNotReferencedTexture(int index)
{
if (Root.Textures == null) return;
if (Root.Textures[index].Source != null
&& Root.Images?.Count > 0
&& Root.Images.Count > Root.Textures[index].Source.Id
&& string.IsNullOrEmpty(Root.Textures[index].Source.Value.Uri))
{
await ConstructImageBuffer(Root.Textures[index], index);
await ConstructTexture(Root.Textures[index], index, !KeepCPUCopyOfTexture, true, false);
}
}
private async Task<TextureData> FromTextureInfo(TextureInfo textureInfo, bool isNormal)
{
var result = new TextureData();
if (textureInfo?.Index?.Value == null) return result;
TextureId textureId = textureInfo.Index;
await ConstructTexture(textureId.Value, textureId.Id, !KeepCPUCopyOfTexture, true, isNormal);
result.Texture = _assetCache.TextureCache[textureId.Id].Texture;
result.TexCoord = textureInfo.TexCoord;
if (textureInfo is NormalTextureInfo nti)
result.DataMultiplier = nti.Scale;
if (textureInfo is OcclusionTextureInfo oti)
result.DataMultiplier = oti.Strength;
var ext = GetTextureTransform(textureInfo);
if (ext != null)
{
result.Offset = ext.Offset.ToUnityVector2Raw();
result.Rotation = ext.Rotation;
result.Scale = ext.Scale.ToUnityVector2Raw();
result.TexCoordExtra = ext.TexCoord;
}
return result;
}
private static int ComputeHash(byte[] data)
{
unchecked
{
const int p = 16777619;
int hash = (int)2166136261;
for (int i = 0; i < data.Length; i++)
hash = (hash ^ data[i]) * p;
return hash;
}
}
private Dictionary<int, int> CollectImageHashes()
{
if (_gltfRoot.Images == null) return new Dictionary<int, int>();
Dictionary<int, int> hashes = new Dictionary<int, int>();
int index = -1;
foreach (var image in _gltfRoot.Images)
{
index++;
if (image.Uri == null)
{
if (image.BufferView == null)
continue;
var stream = GetImageStream(image, -1);
if (stream == null || stream.Length == 0)
continue;
// Create a hash for the stream data
var bufferData = new byte[stream.Length];
stream.Read(bufferData, 0, bufferData.Length);
int streamHash = ComputeHash(bufferData);
hashes.Add(index, streamHash);
}
else
{
hashes.Add(index, image.Uri.GetHashCode());
}
}
return hashes;
}
private void CheckForDuplicateImages()
{
var hashes = CollectImageHashes();
var reverseHashes = new Dictionary<int, int>();
foreach (var h in hashes)
reverseHashes[h.Value] = h.Key;
_imageDeduplicationLinks = new Dictionary<int, int>();
foreach (var h in hashes)
_imageDeduplicationLinks[h.Key] = reverseHashes[h.Value];
}
private Stream GetImageStream(GLTFImage image, int imageCacheIndex)
{
Stream stream = null;
if (image.Uri == null)
{
if (image.BufferView == null)
return null;
var bufferView = image.BufferView.Value;
BufferCacheData bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id];
if (bufferContents.bufferData.IsCreated)
{
bufferContents.Stream.Position = bufferView.ByteOffset + bufferContents.ChunkOffset;
stream = new SubStream(bufferContents.Stream, 0, bufferView.ByteLength);
}
}
else
{
string uri = image.Uri;
byte[] bufferData;
URIHelper.TryParseBase64(uri, out bufferData);
if (bufferData != null)
{
stream = new MemoryStream(bufferData, 0, bufferData.Length, false, true);
}
else
{
stream = _assetCache.ImageStreamCache[imageCacheIndex];
}
}
return stream;
}
protected async Task ConstructImage(GLTFImage image, int imageCacheIndex, bool markGpuOnly, bool isLinear, bool isNormal)
{
if (_assetCache.InvalidImageCache[imageCacheIndex])
return;
if (_assetCache.ImageCache[imageCacheIndex] == null)
{
Stream stream = GetImageStream(image, imageCacheIndex);
await YieldOnTimeoutAndThrowOnLowMemory();
await ConstructUnityTexture(stream, markGpuOnly, isLinear, isNormal, image, imageCacheIndex);
}
}
protected async Task ConstructImageBuffer(GLTFTexture texture, int textureIndex)
{
int sourceId = GetTextureSourceId(texture);
if (_assetCache.ImageStreamCache[sourceId] == null)
{
GLTFImage image = _gltfRoot.Images[sourceId];
// we only load the streams if not a base64 uri, meaning the data is in the uri
if (image.Uri != null && !URIHelper.IsBase64Uri(image.Uri))
{
_assetCache.ImageStreamCache[sourceId] = await _options.DataLoader.LoadStreamAsync(image.Uri);
}
else if (image.Uri == null && image.BufferView != null && _assetCache.BufferCache[image.BufferView.Value.Buffer.Id] == null)
{
int bufferIndex = image.BufferView.Value.Buffer.Id;
await ConstructBuffer(_gltfRoot.Buffers[bufferIndex], bufferIndex);
}
}
if (_assetCache.TextureCache[textureIndex] == null)
{
_assetCache.TextureCache[textureIndex] = new TextureCacheData
{
TextureDefinition = texture
};
}
}
// With using KTX, we need to return a new Texture2D instance at the moment. Unity KTX package does not support loading into existing one
async Task<Texture2D> CheckMimeTypeAndLoadImage(GLTFImage image, Texture2D texture, NativeArray<byte> data, bool markGpuOnly, bool isLinear)
{
bool textureWillBeCompressed = false;
#if UNITY_EDITOR
if (Context.SourceImporter != null)
#endif
{
if (_options.RuntimeTextureCompression != RuntimeTextureCompression.None)
textureWillBeCompressed = true;
}
switch (image.MimeType)
{
case "image/png":
case "image/jpeg":
// NOTE: the second parameter of LoadImage() marks non-readable, but we can't mark it until after we call Apply()
texture.LoadImage(data.ToArray(), markGpuOnly && !textureWillBeCompressed);
break;
case "image/exr":
Debug.Log(LogType.Warning, $"EXR images are not supported. The texture {texture.name} won't be imported. File: {_gltfFileName}");
break;
case "image/ktx2":
string textureName = texture.name;
#if HAVE_KTX
if (Context.TryGetPlugin<Ktx2ImportContext>(out _))
{
#if UNITY_EDITOR
Texture.DestroyImmediate(texture);
#else
Texture.Destroy(texture);
#endif
var ktxTexture = new KtxUnity.KtxTexture();
var resultTextureData = await ktxTexture.LoadFromBytes(data, isLinear, mipChain: GenerateMipMapsForTextures);
texture = resultTextureData.texture;
texture.name = textureName;
}
else
#endif
{
Debug.Log(LogType.Warning, $"Can't import texture \"{image.Name}\" from \"{_gltfFileName}\" because it is a KTX2 file using the KHR_texture_basisu extension. Add the package \"com.unity.cloud.ktx\" version v1.3+ to your project to import KTX2 textures.");
await Task.CompletedTask;
texture = null;
}
break;
default:
texture.LoadImage(data.ToArray(), markGpuOnly && !textureWillBeCompressed);
break;
}
// assign default values
if (texture)
{
texture.wrapMode = TextureWrapMode.Repeat;
texture.wrapModeV = TextureWrapMode.Repeat;
texture.wrapModeU = TextureWrapMode.Repeat;
texture.filterMode = FilterMode.Bilinear;
}
#if UNITY_EDITOR
if (Context.SourceImporter != null)
#endif
{
// Only when this import is not an Asset Import
if (_options.RuntimeTextureCompression != RuntimeTextureCompression.None)
{
// Texture need to be readable to compress it
texture.Compress(_options.RuntimeTextureCompression == RuntimeTextureCompression.HighQuality);
if (markGpuOnly)
texture.Apply(true, true);
}
}
await Task.CompletedTask;
return texture;
}
protected virtual async Task ConstructUnityTexture(Stream stream, bool markGpuOnly, bool isLinear, bool isNormal, GLTFImage image, int imageCacheIndex)
{
bool convertToDxt5nmFormat = false;
#if UNITY_EDITOR
if (stream is AssetDatabaseStream assetDatabaseStream)
{
var tx = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(assetDatabaseStream.AssetUri);
progressStatus.TextureLoaded++;
progress?.Report(progressStatus);
_assetCache.ImageCache[imageCacheIndex] = tx;
return;
}
if (isNormal && Context.SourceImporter != null)
{
BuildTargetGroup activeTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
#if UNITY_2023_1_OR_NEWER
if (PlayerSettings.GetNormalMapEncoding(NamedBuildTarget.FromBuildTargetGroup(activeTargetGroup)) == NormalMapEncoding.DXT5nm)
#else
if (PlayerSettings.GetNormalMapEncoding(activeTargetGroup) == NormalMapEncoding.DXT5nm)
#endif
{
convertToDxt5nmFormat = true;
}
}
#endif
Texture2D texture = new Texture2D(4, 4, TextureFormat.RGBA32, GenerateMipMapsForTextures, isLinear);
texture.name = string.IsNullOrEmpty(image.Name) ? Path.GetFileNameWithoutExtension(image.Uri) : image.Name;
if (string.IsNullOrEmpty(texture.name))
texture.name = $"Texture_{imageCacheIndex.ToString()}{EMPTY_TEXTURE_NAME_SUFFIX}";
var newTextureObject = texture;
#if UNITY_EDITOR
var haveRemappedTexture = false;
if (Context.SourceImporter != null)
{
// check for remapping, we don't even need to attempt loading the texture in that case.
var externalObjects = Context.SourceImporter.GetExternalObjectMap();
var sourceIdentifier = new AssetImporter.SourceAssetIdentifier(texture);
externalObjects.TryGetValue(sourceIdentifier, out var o);
if (o is Texture2D remappedTexture)
{
if (remappedTexture)
{
texture = remappedTexture;
haveRemappedTexture = true;
}
else
{
Context.SourceImporter.RemoveRemap(sourceIdentifier);
}
}
}
if (haveRemappedTexture)
{
// nothing to do here, the texture has already been remapped
}
else
#endif
if (stream is FileLoader.InvalidStream invalidStream)
{
// ignore - we still need a valid texture so that we can properly remap
// texture = null;
// We need some way to track these mock textures, so that we can get rid of them again after import
// TODO we should still set it to null here, and save the importer definition names for remapping instead.
// This way here we'll get into weird code for Runtime import, as we would still import mock textures...
// Or we add another option to avoid that.
texture = null;
Debug.Log(LogType.Error, "Buffer file " + invalidStream.RelativeFilePath + " not found in path: " + invalidStream.AbsoluteFilePath+ $" (File: {_gltfFileName})");
}
else if (stream != null && _nativeBuffers.TryGetValue(stream, out var nativeData))
{
var bufferView = await GetBufferData(image.BufferView.Value.Buffer);
await YieldOnTimeoutAndThrowOnLowMemory();
texture = await CheckMimeTypeAndLoadImage(image, texture, bufferView.bufferData, markGpuOnly, isLinear);
}
else if (stream is MemoryStream)
{
using (MemoryStream memoryStream = stream as MemoryStream)
{
await YieldOnTimeoutAndThrowOnLowMemory();
using (var memoryStreamData = new NativeArray<byte>(memoryStream.ToArray(), Allocator.TempJob))
{
texture = await CheckMimeTypeAndLoadImage(image, texture, memoryStreamData, markGpuOnly, isLinear);
}
}
}
else if (stream != null)
{
byte[] buffer = new byte[stream.Length];
// todo: potential optimization is to split stream read into multiple frames (or put it on a thread?)
if (stream.Length > int.MaxValue)
{
throw new Exception($"Stream is larger than can be copied into byte array (File: {_gltfFileName})");
}
stream.Read(buffer, 0, (int)stream.Length);
await YieldOnTimeoutAndThrowOnLowMemory();
using (NativeArray<byte> bufferNative = new NativeArray<byte>(buffer, Allocator.TempJob))
{
texture = await CheckMimeTypeAndLoadImage(image, texture, bufferNative, markGpuOnly, isLinear);
}
}
if (texture && convertToDxt5nmFormat)
{
texture = await NormalMapEncodingConverter.ConvertToDxt5nmAndCheckTextureFormatAsync(texture);
if (texture != newTextureObject)
newTextureObject = texture;
texture.Apply();
}
if (!texture)
_assetCache.InvalidImageCache[imageCacheIndex] = newTextureObject;
if (_assetCache.ImageCache[imageCacheIndex] != null) Debug.Log(LogType.Assert, "ImageCache should not be loaded multiple times");
if (texture)
{
progressStatus.TextureLoaded++;
progress?.Report(progressStatus);
}
_assetCache.ImageCache[imageCacheIndex] = texture;
}
protected virtual int GetTextureSourceId(GLTFTexture texture)
{
if (texture.Extensions != null && texture.Extensions.ContainsKey(KHR_texture_basisu.EXTENSION_NAME))
{
return ((KHR_texture_basisu)texture.Extensions[KHR_texture_basisu.EXTENSION_NAME]).source.Id;
}
int id = texture.Source?.Id ?? 0;
if (_imageDeduplicationLinks != null)
{
if (_imageDeduplicationLinks.TryGetValue(id, out int replacedId))
id = replacedId;
}
return id;
}
protected virtual bool IsTextureFlipped(GLTFTexture texture)
{
if (texture.Extensions != null && texture.Extensions.ContainsKey(KHR_texture_basisu.EXTENSION_NAME))
{
return true;
}
return false;
}
/// <summary>
/// Creates a texture from a glTF texture
/// </summary>
/// <param name="texture">The texture to load</param>
/// <param name="textureIndex">The index in the texture cache</param>
/// <param name="markGpuOnly">Whether the texture is GPU only, instead of keeping a CPU copy</param>
/// <param name="isLinear">Whether the texture is linear rather than sRGB</param>
/// <returns>The loading task</returns>
public virtual async Task LoadTextureAsync(GLTFTexture texture, int textureIndex, bool markGpuOnly, bool isLinear)
{
try
{
lock (this)
{
if (_isRunning)
{
throw new GLTFLoadException($"Cannot CreateTexture while GLTFSceneImporter is already running (File: {_gltfFileName})");
}
_isRunning = true;
}
if (_options.ThrowOnLowMemory)
{
_memoryChecker = new MemoryChecker();
}
if (_gltfRoot == null)
{
await LoadJson(_gltfFileName);
}
if (_assetCache == null)
{
_assetCache = new AssetCache(_gltfRoot);
}
await ConstructImageBuffer(texture, textureIndex);
await ConstructTexture(texture, textureIndex, markGpuOnly, isLinear, false);
}
finally
{
lock (this)
{
_isRunning = false;
}
_gltfStream.Stream.Close();
}
}
public virtual Task LoadTextureAsync(GLTFTexture texture, int textureIndex, bool isLinear)
{
return LoadTextureAsync(texture, textureIndex, !KeepCPUCopyOfTexture, isLinear);
}
/// <summary>
/// Gets texture that has been loaded from CreateTexture
/// </summary>
/// <param name="textureIndex">The texture to get</param>
/// <returns>Created texture</returns>
public virtual Texture GetTexture(int textureIndex)
{
if (_assetCache == null)
{
throw new GLTFLoadException($"Asset cache needs initialized before calling GetTexture (File: {_gltfFileName})");
}
if (_assetCache.TextureCache[textureIndex] == null)
{
return null;
}
return _assetCache.TextureCache[textureIndex].Texture;
}
protected virtual async Task ConstructTexture(GLTFTexture texture, int textureIndex, bool markGpuOnly, bool isLinear, bool isNormal)
{
if (_assetCache.TextureCache[textureIndex].Texture == null)
{
int sourceId = GetTextureSourceId(texture);
GLTFImage image = _gltfRoot.Images[sourceId];
bool isFirstInstance = _assetCache.ImageCache[sourceId] == null;
await ConstructImage(image, sourceId, markGpuOnly, isLinear, isNormal);
var source = _assetCache.ImageCache[sourceId];
if (!source) return;
FilterMode desiredFilterMode;
TextureWrapMode desiredWrapModeS, desiredWrapModeT;
if (texture.Sampler != null)
{
var sampler = texture.Sampler.Value;
switch (sampler.MinFilter)
{
case MinFilterMode.Nearest:
case MinFilterMode.NearestMipmapNearest:
case MinFilterMode.NearestMipmapLinear:
desiredFilterMode = FilterMode.Point;
break;
case MinFilterMode.Linear:
case MinFilterMode.LinearMipmapNearest:
desiredFilterMode = FilterMode.Bilinear;
break;
case MinFilterMode.LinearMipmapLinear:
desiredFilterMode = FilterMode.Trilinear;
break;
default:
Debug.Log(LogType.Warning, "Unsupported Sampler.MinFilter: " + sampler.MinFilter+ $" (File: {_gltfFileName})");
desiredFilterMode = FilterMode.Bilinear;
break;
}
TextureWrapMode UnityWrapMode(GLTF.Schema.WrapMode gltfWrapMode)
{
switch (gltfWrapMode)
{
case GLTF.Schema.WrapMode.ClampToEdge:
return TextureWrapMode.Clamp;
case GLTF.Schema.WrapMode.Repeat:
return TextureWrapMode.Repeat;
case GLTF.Schema.WrapMode.MirroredRepeat:
return TextureWrapMode.Mirror;
default:
Debug.Log(LogType.Warning, "Unsupported Sampler.Wrap: " + gltfWrapMode+ $" (File: {_gltfFileName})");
return TextureWrapMode.Repeat;
}
}
desiredWrapModeS = UnityWrapMode(sampler.WrapS);
desiredWrapModeT = UnityWrapMode(sampler.WrapT);
}
else
{
desiredFilterMode = FilterMode.Bilinear;
desiredWrapModeS = TextureWrapMode.Repeat;
desiredWrapModeT = TextureWrapMode.Repeat;
}
if (isFirstInstance)
{
source.filterMode = desiredFilterMode;
source.wrapModeU = desiredWrapModeS;
source.wrapModeV = desiredWrapModeT;
}
var matchSamplerState = source.filterMode == desiredFilterMode && source.wrapModeU == desiredWrapModeS && source.wrapModeV == desiredWrapModeT;
if (matchSamplerState || markGpuOnly)
{
if (_assetCache.TextureCache[textureIndex].Texture != null) Debug.Log(LogType.Assert, "Texture should not be reset to prevent memory leaks"+ $" (File: {_gltfFileName})");
_assetCache.TextureCache[textureIndex].Texture = source;
if (!matchSamplerState)
{
Debug.Log(LogType.Warning, $"Ignoring sampler; filter mode: source {source.filterMode}, desired {desiredFilterMode}; wrap mode: source {source.wrapModeU}x{source.wrapModeV}, desired {desiredWrapModeS}x{desiredWrapModeT}"+ $" (File: {_gltfFileName})");
}
}
else
#if UNITY_EDITOR
if (!UnityEditor.AssetDatabase.Contains(source))
#endif
{
Texture2D unityTexture;
if (!source.isReadable)
{
unityTexture = new Texture2D(source.width, source.height, source.format, source.mipmapCount, isLinear);
Graphics.CopyTexture(source, unityTexture);
}
else
unityTexture = Object.Instantiate(source);
unityTexture.name = string.IsNullOrEmpty(image.Name) ?
string.IsNullOrEmpty(texture.Name) ?
Path.GetFileNameWithoutExtension(image.Uri) :
texture.Name :
image.Name;
if (string.IsNullOrEmpty(unityTexture.name))
unityTexture.name = $"Texture_{textureIndex.ToString()}{EMPTY_TEXTURE_NAME_SUFFIX}";
unityTexture.filterMode = desiredFilterMode;
unityTexture.wrapModeU = desiredWrapModeS;
unityTexture.wrapModeV = desiredWrapModeT;
if (_assetCache.TextureCache[textureIndex].Texture != null) Debug.Log(LogType.Assert, $"Texture should not be reset to prevent memory leaks (File: {_gltfFileName})");
_assetCache.TextureCache[textureIndex].Texture = unityTexture;
}
#if UNITY_EDITOR
else
{
// don't warn for just filter mode, user choice
if (source.wrapModeU != desiredWrapModeS || source.wrapModeV != desiredWrapModeT)
Debug.Log(LogType.Warning, ($"Sampler state doesn't match but source texture is non-readable. Results might not be correct if textures are used multiple times with different sampler states. {source.filterMode} == {desiredFilterMode} && {source.wrapModeU} == {desiredWrapModeS} && {source.wrapModeV} == {desiredWrapModeT} (File: {_gltfFileName})"));
_assetCache.TextureCache[textureIndex].Texture = source;
}
#endif
}
_assetCache.TextureCache[textureIndex].IsLinear = isLinear;
_assetCache.TextureCache[textureIndex].IsNormal = isNormal;
try
{
var tex = _assetCache.TextureCache[textureIndex].Texture;
foreach (var plugin in Context.Plugins)
{
plugin.OnAfterImportTexture(texture, textureIndex, tex);
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
protected virtual void ConstructImageFromGLB(GLTFImage image, int imageCacheIndex)
{
var texture = new Texture2D(0, 0);
texture.name = nameof(GLTFSceneImporter) + (image.Name != null ? ("." + image.Name) : "");
var bufferView = image.BufferView.Value;
var data = new byte[bufferView.ByteLength];
var bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id];
bufferContents.Stream.Position = bufferView.ByteOffset + bufferContents.ChunkOffset;
bufferContents.Stream.Read(data, 0, data.Length);
texture.LoadImage(data);
if (_assetCache.ImageCache[imageCacheIndex] != null) Debug.Log(LogType.Assert, "ImageCache should not be loaded multiple times");
progressStatus.TextureLoaded++;
progress?.Report(progressStatus);
_assetCache.ImageCache[imageCacheIndex] = texture;
}
#if UNITY_EDITOR
internal class AssetDatabaseStream : Stream
{
public string AssetUri { get; }
public AssetDatabaseStream(string imageUri)
{
AssetUri = imageUri;
}
public override void Flush() => throw new NotImplementedException();
public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override bool CanRead { get; }
public override bool CanSeek { get; }
public override bool CanWrite { get; }
public override long Length { get; }
public override long Position { get; set; }
}
#endif
}
internal static class MatHelper
{
internal static void SetKeyword(this Material material, string keyword, bool state)
{
if (state)
material.EnableKeyword(keyword + "_ON");
else
material.DisableKeyword(keyword + "_ON");
if (material.HasProperty(keyword))
material.SetFloat(keyword, state ? 1 : 0);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 859436f420e4466daa129c3fc03f4480
timeCreated: 1657012139

View File

@@ -0,0 +1,6 @@
using UnityEngine;
namespace UnityGLTF
{
public class MaterialLibrary: ScriptableObject { }
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dae4451576d948249604afd3847354bb
timeCreated: 1727343650

View File

@@ -0,0 +1,107 @@
using System.Threading.Tasks;
using UnityEngine;
namespace UnityGLTF
{
internal static class NormalMapEncodingConverter
{
public static async Task<Texture2D> ConvertToDxt5nmAndCheckTextureFormatAsync(Texture2D source)
{
Texture2D dest = source;
Color[] pixels;
void CreateDestinationTexture()
{
dest = new Texture2D(source.width, source.height, TextureFormat.RGBA32, source.mipmapCount > 0, true);
dest.wrapMode = source.wrapMode;
dest.wrapModeU = source.wrapModeU;
dest.wrapModeV = source.wrapModeV;
dest.wrapModeW = source.wrapModeW;
dest.filterMode = source.filterMode;
dest.anisoLevel = source.anisoLevel;
dest.mipMapBias = source.mipMapBias;
dest.name = source.name;
}
void DestroySourceTexture()
{
#if UNITY_EDITOR
Texture.DestroyImmediate(source);
#else
Texture.Destroy(source);
#endif
}
if (!source.isReadable)
{
CreateDestinationTexture();
var destRenderTexture = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
var previousSRGBState = GL.sRGBWrite;
GL.sRGBWrite = false;
Graphics.Blit(source, destRenderTexture);
dest.ReadPixels(new Rect(0, 0, destRenderTexture.width, destRenderTexture.height), 0, 0);
GL.sRGBWrite = previousSRGBState;
RenderTexture.ReleaseTemporary(destRenderTexture);
pixels = dest.GetPixels();
DestroySourceTexture();
}
else
{
pixels = source.GetPixels();
if (source.format != TextureFormat.RGBA32)
{
CreateDestinationTexture();
DestroySourceTexture();
}
}
await Task.Run(() =>
{
for (var i = 0; i < pixels.Length; i++)
{
var c = pixels[i];
pixels[i] = new Color(1, c.g, 1, c.r);
}
});
dest.SetPixels(pixels);
return dest;
}
public static void ConvertToDxt5nm(Texture2D source, Texture2D dest)
{
var pixels = source.GetPixels();
for (var i = 0; i < pixels.Length; i++)
{
var c = pixels[i];
pixels[i] = new Color(1, c.g, 1, c.r);
}
dest.SetPixels(pixels);
}
public static async Task ConvertToDxt5nmAsync(Texture2D texture)
{
var pixels = texture.GetPixels();
await Task.Run(() =>
{
for (var i = 0; i < pixels.Length; i++)
{
var c = pixels[i];
pixels[i] = new Color(1, c.g, 1, c.r);
}
});
texture.SetPixels(pixels);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 870ecf89bfc64980a38e2977a56943bb
timeCreated: 1705055769

View File

@@ -0,0 +1,119 @@
using System.Collections.Generic;
using GLTF.Schema;
using UnityEngine;
namespace UnityGLTF
{
public class UnityMeshData
{
public bool[] subMeshDataCreated;
public Vector3[] Vertices;
public Vector3[] Normals;
public Vector4[] Tangents;
public Vector2[] Uv1;
public Vector2[] Uv2;
public Vector2[] Uv3;
public Vector2[] Uv4;
public Color[] Colors;
public BoneWeight[] BoneWeights;
public Vector3[][] MorphTargetVertices;
public Vector3[][] MorphTargetNormals;
public Vector3[][] MorphTargetTangents;
public MeshTopology[] Topology;
public DrawMode[] DrawModes;
public int[][] Indices;
public HashSet<int> alreadyAddedAccessors = new HashSet<int>();
public uint[] subMeshVertexOffset;
public void Clear()
{
Vertices = null;
Normals = null;
Tangents = null;
Uv1 = null;
Uv2 = null;
Uv3 = null;
Uv4 = null;
Colors = null;
BoneWeights = null;
MorphTargetVertices = null;
MorphTargetNormals = null;
MorphTargetTangents = null;
Topology = null;
Indices = null;
subMeshVertexOffset = null;
}
public bool IsEqual(UnityMeshData other)
{
bool CompareArray<T>(T[] array1, T[] array2)
{
if (array1 == null && array2 == null)
return true;
if (array1 == array2)
return true;
if (array1?.Length != array2?.Length)
return false;
for (int i = 0; i < array1.Length; i++)
{
if (!array1[i].Equals(array2[i]))
return false;
}
return true;
}
bool CompareArray2<T>(T[][] array1, T[][] array2)
{
if (array1 == null && array2 == null)
return true;
if (array1 == array2)
return true;
if (array1?.Length != array2?.Length)
return false;
for (int i = 0; i < array1.Length; i++)
{
if (array1[i] == null && array2[i] == null)
continue;
if (array1[i] == array2[i])
continue;
if (array1[i].Length != array2[i].Length)
return false;
for (int j = 0; j < array1.Length; j++)
{
if (!array1[i][j].Equals(array2[i][j]))
return false;
}
}
return true;
}
if (!CompareArray(Vertices, other.Vertices)
|| !CompareArray(Normals, other.Normals)
|| !CompareArray(Tangents, other.Tangents)
|| !CompareArray(Uv1, other.Uv1)
|| !CompareArray(Uv2, other.Uv2)
|| !CompareArray(Uv3, other.Uv3)
|| !CompareArray(Uv4, other.Uv4)
|| !CompareArray(Colors, other.Colors)
|| !CompareArray(BoneWeights, other.BoneWeights)
|| !CompareArray(Topology, other.Topology)
|| !CompareArray(DrawModes, other.DrawModes)
|| !CompareArray2(MorphTargetVertices, other.MorphTargetVertices)
|| !CompareArray2(MorphTargetNormals, other.MorphTargetNormals)
|| !CompareArray2(MorphTargetTangents, other.MorphTargetTangents)
|| !CompareArray2(Indices, other.Indices))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5142460d60f647e0b377dd3cc0c0a909
timeCreated: 1724924750