Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Resource Deduplication on import #772

Closed
wants to merge 10 commits into from
5 changes: 4 additions & 1 deletion Editor/Scripts/GLTFImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ private static void EnsureShadersAreLoaded()

[Tooltip("Turn this off to create an explicit GameObject for the glTF scene. A scene root will always be created if there's more than one root node.")]
[SerializeField] internal bool _removeEmptyRootObjects = true;
[SerializeField] internal float _scaleFactor = 1.0f;
[SerializeField] internal float _scaleFactor = 1.0f;
[Tooltip("Reduces identical resources. e.g. when identical meshes are found, only one will be imported.")]
[SerializeField] internal DeduplicateOptions _deduplicateResources = DeduplicateOptions.None;
[SerializeField] internal int _maximumLod = 300;
[SerializeField] internal bool _readWriteEnabled = true;
[SerializeField] internal bool _generateColliders = false;
Expand Down Expand Up @@ -915,6 +917,7 @@ private void CreateGLTFScene(GLTFImportContext context, out GameObject scene,
ImportBlendShapeNames = _importBlendShapeNames,
BlendShapeFrameWeight = _blendShapeFrameWeight,
CameraImport = _importCamera,
DeduplicateResources = _deduplicateResources,
};

using (var stream = File.OpenRead(projectFilePath))
Expand Down
1 change: 1 addition & 0 deletions Editor/Scripts/GLTFImporterInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private void ModelInspectorGUI()
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(GLTFImporter._removeEmptyRootObjects)));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(GLTFImporter._scaleFactor)));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(GLTFImporter._importCamera)));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(GLTFImporter._deduplicateResources)));
// EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(GLTFImporter._maximumLod)), new GUIContent("Maximum Shader LOD"));
EditorGUILayout.Separator();

Expand Down
2 changes: 1 addition & 1 deletion Editor/Scripts/ShaderGraph/MaterialEditorBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private static void OnImmutableMaterialChanged(Material material)
// after importing a changed material, which can be confusing. Could be caching inside PBRGraphGUI
AssetDatabase.Refresh();

EditorApplication.update += () =>
EditorApplication.delayCall += () =>
{
// Repaint Inspector, newly imported values can be different if we're not perfectly round tripping
foreach (var editor in ActiveEditorTracker.sharedTracker.activeEditors)
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,13 @@ Animation components and their legacy clips can also be exported.

### KHR_animation_pointer support

UnityGLTF supports exporting animations with the KHR_animation_pointer extension.
UnityGLTF supports importing and exporting animations with the KHR_animation_pointer extension.
The core glTF spec only allows animation node transforms and blend shape weights, while this extension allows animating arbitrary properties in the glTF file –
including material and object properties, even in custom extensions and script components.

Exporting with KHR_animation_pointer can be turned on in `Project Settings > UnityGLTF` by enabling the KHR_animation_pointer plugin.

> **Note:** The exported files can be viewed with Gestaltor, Babylon Sandbox, and Needle Engine, but currently not with three.js / model-viewer. See https://github.com/mrdoob/three.js/pull/24108. They can also not be reimported into UnityGLTF at this point.
> **Note:** The exported files can be viewed with Gestaltor, Babylon Sandbox, and Needle Engine, but currently not with three.js / model-viewer. See https://github.com/mrdoob/three.js/pull/24108.

## Blend Shape Export

Expand Down Expand Up @@ -359,9 +359,8 @@ If your plugin reads/writes custom extension data, you need to also implement `G

## Known Issues

- Blend shapes with in-betweens are currently not supported.
- Blend shape in-betweens will not be exported as glTF does not have that functionality.
- Support for glTF files with multiple scenes is limited.
- Some material options (e.g. MAOS maps) are currently not exported correctly.

## Contributing

Expand Down
80 changes: 31 additions & 49 deletions Runtime/Scripts/GLTFSceneImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@
using UnityGLTF.Loader;
using UnityGLTF.Plugins;
using Quaternion = UnityEngine.Quaternion;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
using Vector4 = UnityEngine.Vector4;
#if !WINDOWS_UWP && !UNITY_WEBGL
using ThreadPriority = System.Threading.ThreadPriority;
#endif
using WrapMode = UnityEngine.WrapMode;

namespace UnityGLTF
{
[Flags]
public enum DeduplicateOptions
{
None = 0,
Meshes = 1,
Textures = 2,
}

public class ImportOptions
{
#pragma warning disable CS0618 // Type or member is obsolete
Expand All @@ -40,7 +46,7 @@ public class ImportOptions
public AnimationMethod AnimationMethod = AnimationMethod.Legacy;
public bool AnimationLoopTime = true;
public bool AnimationLoopPose = false;

public DeduplicateOptions DeduplicateResources = DeduplicateOptions.None;
public bool SwapUVs = false;
public GLTFImporterNormals ImportNormals = GLTFImporterNormals.Import;
public GLTFImporterNormals ImportTangents = GLTFImporterNormals.Import;
Expand Down Expand Up @@ -73,51 +79,7 @@ public enum AnimationMethod
Mecanim,
MecanimHumanoid,
}

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 struct ImportProgress
{
public bool IsDownloaded;
Expand Down Expand Up @@ -496,7 +458,9 @@ public void Dispose()
DisposeNativeBuffers();

onLoadComplete?.Invoke(null, ExceptionDispatchInfo.Capture(ex));
Debug.Log(LogType.Error, $"Error loading file: {_gltfFileName}");
Debug.Log(LogType.Error, $"Error loading file: {_gltfFileName}"
+ System.Environment.NewLine + "Message: " + ex.Message
+ System.Environment.NewLine + "StackTrace: " + ex.StackTrace);
throw;
}
finally
Expand Down Expand Up @@ -701,6 +665,24 @@ private void InitializeGltfTopLevelObject()

// Free up some Memory, Accessor contents are no longer needed
FreeUpAccessorContents();

if (_options.DeduplicateResources != DeduplicateOptions.None)
{
if (IsMultithreaded)
{
if (_options.DeduplicateResources.HasFlag(DeduplicateOptions.Meshes))
await Task.Run(CheckForMeshDuplicates, cancellationToken);
if (_options.DeduplicateResources.HasFlag(DeduplicateOptions.Textures))
await Task.Run(CheckForDuplicateImages, cancellationToken);
}
else
{
if (_options.DeduplicateResources.HasFlag(DeduplicateOptions.Meshes))
CheckForMeshDuplicates();
if (_options.DeduplicateResources.HasFlag(DeduplicateOptions.Textures))
CheckForDuplicateImages();
}
}

await ConstructScene(scene, showSceneObj, cancellationToken);

Expand Down
45 changes: 45 additions & 0 deletions Runtime/Scripts/SceneImporter/ImporterMeshes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1361,5 +1361,50 @@ private static T[][] Allocate2dArray<T>(uint x, uint y)
for (var i = 0; i < x; i++) result[i] = new T[y];
return result;
}

private void CheckForMeshDuplicates()
{
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)
{
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;
}
}
}

}
}
}
Loading