├── .gitignore
├── Runtime
├── AssemblyInfo.cs
├── Extensions.meta
├── Helpers.meta
├── Interfaces.meta
├── Shaders.meta
├── Unity.AutoLOD.asmdef.meta
├── Utilities.meta
├── Shaders
│ ├── SimpleBatcher.shader.meta
│ └── SimpleBatcher.shader
├── Helpers
│ ├── MeshLOD.cs.meta
│ ├── MonoBehaviourHelper.cs.meta
│ ├── RequiresLayerAttribute.cs
│ ├── WorkingMesh.cs.meta
│ ├── TimedEnumerator.cs.meta
│ ├── RequiresLayerAttribute.cs.meta
│ ├── RequiresTagAttribute.cs.meta
│ ├── OptionalDependencyAttribute.cs.meta
│ ├── RequiresTagAttribute.cs
│ ├── OptionalDependencyAttribute.cs
│ ├── MeshLOD.cs
│ ├── TimedEnumerator.cs
│ ├── MonoBehaviourHelper.cs
│ └── WorkingMesh.cs
├── LODVolume.cs.meta
├── Extensions
│ ├── IEnumeratorExtensions.cs
│ ├── IEnumeratorExtensions.cs.meta
│ ├── LODGroupExtensions.cs.meta
│ └── LODGroupExtensions.cs
├── Interfaces
│ ├── IBatcher.cs.meta
│ ├── IPreferences.cs.meta
│ ├── IMeshSimplifier.cs.meta
│ ├── IPreferences.cs
│ ├── IBatcher.cs
│ └── IMeshSimplifier.cs
├── AssemblyInfo.cs.meta
├── Utilities
│ ├── ObjectUtils.cs.meta
│ └── ObjectUtils.cs
├── Unity.AutoLOD.asmdef
└── LODVolume.cs
├── CHANGELOG.md.meta
├── package.json.meta
├── Runtime.meta
├── Editor
├── Helpers.meta
├── MeshSimplifiers
│ ├── Simplygon.meta
│ ├── Simplygon
│ │ ├── Unity.AutoLOD.Editor.Simplygon.asmdef.meta
│ │ ├── SimplygonMeshSimplifier.cs.meta
│ │ ├── Unity.AutoLOD.Editor.Simplygon.asmdef
│ │ └── SimplygonMeshSimplifier.cs
│ ├── QuadricMeshSimplifier.cs.meta
│ ├── SimulatedMeshSimplifier.cs.meta
│ ├── InstaLODMeshSimplifier.cs.meta
│ ├── InstaLODMeshSimplifier.cs
│ ├── SimulatedMeshSimplifier.cs
│ └── QuadricMeshSimplifier.cs
├── Batchers.meta
├── MeshSimplifiers.meta
├── Unity.AutoLOD.Editor.asmdef.meta
├── AutoLOD.cs.meta
├── GridPlacementUtility.cs.meta
├── LODData.cs.meta
├── SceneLOD.cs.meta
├── LODDataEditor.cs.meta
├── TagManager.cs.meta
├── TextureAtlas.cs.meta
├── LODImportSettings.cs.meta
├── TextureAtlas.cs
├── TextureAtlasModule.cs.meta
├── Batchers
│ ├── SimpleBatcher.cs.meta
│ ├── MaterialPreservingBatcher.cs.meta
│ ├── MaterialPreservingBatcher.cs
│ └── SimpleBatcher.cs
├── LODImportSettingsDrawer.cs.meta
├── Helpers
│ ├── ProfileAnimation.cs.meta
│ └── ProfileAnimation.cs
├── LODImportSettings.cs
├── ModelImporterLODGenerator.cs.meta
├── Unity.AutoLOD.Editor.asmdef
├── LODData.cs
├── LODImportSettingsDrawer.cs
├── TextureAtlasModule.cs
├── LODDataEditor.cs
├── TagManager.cs
├── GridPlacementUtility.cs
├── ModelImporterLODGenerator.cs
└── SceneLOD.cs
├── Editor.meta
├── LICENSE.meta
├── README.md.meta
├── LICENSE
├── package.json
├── CHANGELOG.md
├── .gitattributes
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /Generated*
2 |
--------------------------------------------------------------------------------
/Runtime/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("Unity.AutoLOD.Editor")]
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e5af2bc35b5b4ca4e94f428655f2447e
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 07638e4f8d5370d4cb709121dd1daa1a
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cd116cf9a374a6245b0ca348f8002c58
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Helpers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 45d5d1fca3645ec468dbb5de17ddd796
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/Simplygon.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 101213ffbe5a97d4ca5e7ecb05df1983
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 88d7e24f96e246e429af69ae050eea91
3 | folderAsset: yes
4 | timeCreated: 1462802844
5 | licenseType: Pro
6 | DefaultImporter:
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a5b43fbbf79161e42b5091bd4bde0812
3 | timeCreated: 1515567475
4 | licenseType: Pro
5 | DefaultImporter:
6 | externalObjects: {}
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ad0ff3d2bc11c4e40b30a87f2514283b
3 | timeCreated: 1515567475
4 | licenseType: Pro
5 | TextScriptImporter:
6 | externalObjects: {}
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/Simplygon/Unity.AutoLOD.Editor.Simplygon.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 97faba6440a10904e87153778a1fea59
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Editor/Batchers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 707cce36264dcae4b8d2cf8c0b97ec64
3 | folderAsset: yes
4 | timeCreated: 1508522805
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Runtime/Extensions.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 998d806ec01d2cc43964b28f5c2720b4
3 | folderAsset: yes
4 | timeCreated: 1505416682
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Runtime/Helpers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7e6794de137081844a1e2e8c74c7bdd5
3 | folderAsset: yes
4 | timeCreated: 1507575992
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Runtime/Interfaces.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 67ac06348aaa9384987cf9f80e5c86e6
3 | folderAsset: yes
4 | timeCreated: 1508522777
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Runtime/Shaders.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a83363c27a32fa14c974145e7f0ddd61
3 | folderAsset: yes
4 | timeCreated: 1502931758
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Runtime/Unity.AutoLOD.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 26eed724cfea581479b00aaf8bbfa2f3
3 | timeCreated: 1515458120
4 | licenseType: Pro
5 | AssemblyDefinitionImporter:
6 | externalObjects: {}
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Runtime/Utilities.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 09680376c4849894a91805e88a6c15e7
3 | folderAsset: yes
4 | timeCreated: 1505949608
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8b1f2a654fd490b4bb40341957d21d66
3 | folderAsset: yes
4 | timeCreated: 1508522959
5 | licenseType: Pro
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Editor/Unity.AutoLOD.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 35c6e7274632ace409c481af8db3c121
3 | timeCreated: 1515459014
4 | licenseType: Pro
5 | AssemblyDefinitionImporter:
6 | externalObjects: {}
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Runtime/Shaders/SimpleBatcher.shader.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e7df92906c2f269408fd4c66cc493ae3
3 | timeCreated: 1509145668
4 | licenseType: Pro
5 | ShaderImporter:
6 | externalObjects: {}
7 | defaultTextures: []
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Runtime/Helpers/MeshLOD.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b38e559e4600f644ca4bb46d55fb9ab3
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/AutoLOD.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 15dd88ae16c8e874ea95998aa36db2b2
3 | timeCreated: 1463063939
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Runtime/LODVolume.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9f582a16ceb3c9742a2ebce863ca04a8
3 | timeCreated: 1462797761
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Editor/GridPlacementUtility.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 26c561be99b335047819a499fbfbe160
3 | timeCreated: 1462912386
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Runtime/Extensions/IEnumeratorExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 |
3 | namespace Unity.AutoLOD
4 | {
5 | public static class IEnumeratorExtensions
6 | {
7 | public static void ExecuteCompletely(this IEnumerator enumerator)
8 | {
9 | while (enumerator.MoveNext()) ;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Runtime/Interfaces/IBatcher.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a01d0cd6a09a1634f82d298338fe16d8
3 | timeCreated: 1462976162
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Runtime/Interfaces/IPreferences.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 04006abb58ee28a4597decea5d2af0b4
3 | timeCreated: 1462976162
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Runtime/Helpers/MonoBehaviourHelper.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 79807ece484b0134e8b63635baa2c449
3 | timeCreated: 1460591574
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Runtime/Interfaces/IMeshSimplifier.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8fc9a723cf4be24bb2e3c8b4ee81b81
3 | timeCreated: 1462976162
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Editor/LODData.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e927627221206e04dba83232f13d1949
3 | timeCreated: 1508523305
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/SceneLOD.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 43d209e41f42c2d49a38d5324316a439
3 | timeCreated: 1506550058
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/RequiresLayerAttribute.cs:
--------------------------------------------------------------------------------
1 | // From: https://github.com/Unity-Technologies/EditorVR
2 | using System;
3 |
4 | namespace Unity.AutoLOD
5 | {
6 | sealed class RequiresLayerAttribute : Attribute
7 | {
8 | public string layer;
9 |
10 | public RequiresLayerAttribute(string layer)
11 | {
12 | this.layer = layer;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Editor/LODDataEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fe83869f5248b394d839673943a693ee
3 | timeCreated: 1508524272
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/QuadricMeshSimplifier.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fb37518376344af48948102fb0185c79
3 | timeCreated: 1462976430
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/SimulatedMeshSimplifier.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ce1496897eb8ac6468e0bd409034f12a
3 | timeCreated: 1462976430
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Editor/TagManager.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 07d3d7fabc3cf4748bcf119a79e56fcd
3 | timeCreated: 1507575828
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/TextureAtlas.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 66dc9fb3daab1dc49a560146f353eebc
3 | timeCreated: 1507937763
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/AssemblyInfo.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 95963fadf18f84442b9ba2a420881cc2
3 | timeCreated: 1507576729
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/LODImportSettings.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c558ef89a6859364597449a053ee8b24
3 | timeCreated: 1508523605
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/TextureAtlas.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace Unity.AutoLOD
4 | {
5 | [CreateAssetMenu(fileName = "TextureAtlas", menuName = "AutoLOD/Texture Atlas")]
6 | public class TextureAtlas : ScriptableObject
7 | {
8 | public Texture2D textureAtlas;
9 | public Texture2D[] textures;
10 | public Rect[] uvs;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Editor/TextureAtlasModule.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 850c77e2dfeb69748b1b1ddefbf3a032
3 | timeCreated: 1508175761
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/Batchers/SimpleBatcher.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cd3861774217b694684b9f12414cb95e
3 | timeCreated: 1503352811
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/LODImportSettingsDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e2d5bfbf1bc85d9408bfeff5e560f152
3 | timeCreated: 1508523605
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/WorkingMesh.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 45ba66b279be27a4f9ebb0c46e4e830b
3 | timeCreated: 1506025355
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Utilities/ObjectUtils.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 557ede46cf8107244a3e7a6fd1f23e99
3 | timeCreated: 1505949608
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/Helpers/ProfileAnimation.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 392192f7e93302c4f86061375caf8c82
3 | timeCreated: 1512760307
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/LODImportSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Unity.AutoLOD
4 | {
5 | [Serializable]
6 | public class LODImportSettings
7 | {
8 | public bool generateOnImport;
9 | public string meshSimplifier;
10 | public string batcher;
11 | public int maxLODGenerated;
12 | public int initialLODMaxPolyCount;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Editor/ModelImporterLODGenerator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a1ea41f51e17b63479d49a614b9c389f
3 | timeCreated: 1502928510
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/TimedEnumerator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e836967d92633a248bf7cf9ebe6d581a
3 | timeCreated: 1506556717
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Extensions/IEnumeratorExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1003775d992f7b941abe4495b46adb45
3 | timeCreated: 1507934476
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Extensions/LODGroupExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 493e5e35d9e265f499004a07237f579c
3 | timeCreated: 1504815833
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/RequiresLayerAttribute.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 407163f8ad39afc4bbc0c8d0dad6501c
3 | timeCreated: 1507575992
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/RequiresTagAttribute.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1488099044ca7d44699b1db1041f98cd
3 | timeCreated: 1507575992
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/Batchers/MaterialPreservingBatcher.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 191ca6974b9b9424995a6ed76e714a17
3 | timeCreated: 1503352811
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/InstaLODMeshSimplifier.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3a83df15799a8fc41bcc11c614762965
3 | timeCreated: 1506536618
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/OptionalDependencyAttribute.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0051087bfec815e4d93d39aa7d0ce8f3
3 | timeCreated: 1515451464
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/Simplygon/SimplygonMeshSimplifier.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bc6d450447299b644bb82aa7c5b59c29
3 | timeCreated: 1512153951
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Runtime/Helpers/RequiresTagAttribute.cs:
--------------------------------------------------------------------------------
1 | // From: https://github.com/Unity-Technologies/EditorVR
2 | using System;
3 |
4 | namespace Unity.AutoLOD
5 | {
6 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
7 | sealed class RequiresTagAttribute : Attribute
8 | {
9 | public string tag;
10 |
11 | public RequiresTagAttribute(string tag)
12 | {
13 | this.tag = tag;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Runtime/Interfaces/IPreferences.cs:
--------------------------------------------------------------------------------
1 | namespace Unity.AutoLOD
2 | {
3 | public interface IPreferences
4 | {
5 | ///
6 | /// Callback from AutoLOD preferences GUI to show GUI options related to a simplifier / batcher.
7 | /// Settings should be saved here as well and also utilized by the simplifier / batcher.
8 | ///
9 | void OnPreferencesGUI();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Runtime/Interfaces/IBatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using UnityEngine;
3 |
4 | namespace Unity.AutoLOD
5 | {
6 | public interface IBatcher
7 | {
8 | ///
9 | /// Combine children renderers of this GameObject (NOTE: Runs as a coroutine)
10 | ///
11 | /// GameObject hierarchy to batch
12 | IEnumerator Batch(GameObject go);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | AutoLOD copyright © 2018 Unity Technologies ApS
2 |
3 | Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
4 |
5 | Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
6 |
--------------------------------------------------------------------------------
/Editor/Unity.AutoLOD.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity.AutoLOD.Editor",
3 | "references": [
4 | "Unity.AutoLOD",
5 | "Whinarn.UnityMeshSimplifier.Runtime"
6 | ],
7 | "optionalUnityReferences": [],
8 | "includePlatforms": [
9 | "Editor"
10 | ],
11 | "excludePlatforms": [],
12 | "allowUnsafeCode": false,
13 | "overrideReferences": false,
14 | "precompiledReferences": [],
15 | "autoReferenced": true,
16 | "defineConstraints": []
17 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.unity.autolod",
3 | "displayName": "AutoLOD",
4 | "version": "0.0.2",
5 | "unity": "2018.4",
6 | "description": "Automatic LOD generation",
7 | "keywords": [
8 | "autolod",
9 | "lod",
10 | "3d",
11 | "mesh",
12 | "polygon",
13 | "triangle",
14 | "simplification",
15 | "decimation",
16 | "reduction",
17 | "optimization"
18 | ],
19 | "author": {
20 | "name": "Unity"
21 | },
22 | "bugs": "https://github.com/Unity-Technologies/AutoLOD/issues"
23 | }
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/Simplygon/Unity.AutoLOD.Editor.Simplygon.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity.AutoLOD.Editor.Simplygon",
3 | "references": [
4 | "Unity.AutoLOD.Editor",
5 | "Unity.AutoLOD",
6 | "Unity.Formats.USD.Runtime"
7 | ],
8 | "includePlatforms": [
9 | "Editor"
10 | ],
11 | "excludePlatforms": [],
12 | "allowUnsafeCode": false,
13 | "overrideReferences": false,
14 | "precompiledReferences": [],
15 | "autoReferenced": true,
16 | "defineConstraints": [],
17 | "versionDefines": [],
18 | "noEngineReferences": false
19 | }
--------------------------------------------------------------------------------
/Runtime/Unity.AutoLOD.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity.AutoLOD",
3 | "references": [],
4 | "optionalUnityReferences": [],
5 | "includePlatforms": [
6 | "Editor",
7 | "LinuxStandalone32",
8 | "LinuxStandalone64",
9 | "LinuxStandaloneUniversal",
10 | "macOSStandalone",
11 | "WindowsStandalone32",
12 | "WindowsStandalone64"
13 | ],
14 | "excludePlatforms": [],
15 | "allowUnsafeCode": false,
16 | "overrideReferences": false,
17 | "precompiledReferences": [],
18 | "autoReferenced": true,
19 | "defineConstraints": []
20 | }
--------------------------------------------------------------------------------
/Runtime/Helpers/OptionalDependencyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace Unity.AutoLOD
5 | {
6 | [Conditional("UNITY_CCU")]
7 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
8 | sealed class OptionalDependencyAttribute : Attribute
9 | {
10 | public string dependentClass;
11 | public string define;
12 |
13 | public OptionalDependencyAttribute(string dependentClass, string define)
14 | {
15 | this.dependentClass = dependentClass;
16 | this.define = define;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Runtime/Interfaces/IMeshSimplifier.cs:
--------------------------------------------------------------------------------
1 | namespace Unity.AutoLOD
2 | {
3 | public interface IMeshSimplifier
4 | {
5 | ///
6 | /// Simplify an input mesh to quality threshold (NOTE: Runs on a separate thread)
7 | ///
8 | /// A working copy of the input mesh
9 | /// Where the output mesh should be stored
10 | /// Percentage of quality requested in relation to the input mesh
11 | void Simplify(WorkingMesh inputMesh, WorkingMesh outputMesh, float quality);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Editor/Batchers/MaterialPreservingBatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using UnityEngine;
3 |
4 | namespace Unity.AutoLOD
5 | {
6 | ///
7 | /// A batcher that preserves materials when combining meshes (does not reduce draw calls)
8 | ///
9 | class MaterialPreservingBatcher : IBatcher
10 | {
11 | public IEnumerator Batch(GameObject go)
12 | {
13 | var renderers = go.GetComponentsInChildren();
14 | foreach (var r in renderers)
15 | {
16 | r.enabled = true;
17 | yield return null;
18 | }
19 | StaticBatchingUtility.Combine(go);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this package will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## 0.0.2 - 2021-08-13
8 | ### Added
9 | - Update SimplygonMeshSimplifier to support Simplygon9 (requires USD 2.0.0-exp.1 package version)
10 |
11 | ### Fixed
12 | - Fixed WorkingMesh name length termination
13 | - Fixed GetTriangleRange not respecting subMeshCount
14 | - Fixed missing MeshFilter causing Unity editor window updates to stall
15 |
16 | ### Changed
17 | - Removed MeshDecimator simplifier since the project has been archived
18 |
19 | ## 0.0.1
20 | - First internal build.
21 |
22 | #Contributors
23 | - Amir Ebrahimi
24 | - Elliot Cuzzillo
25 | - Yuangguang Liao
--------------------------------------------------------------------------------
/Editor/Helpers/ProfileAnimation.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using UnityEditor;
3 | using UnityEditorInternal;
4 | using UnityEngine;
5 | using UnityEngine.Playables;
6 |
7 | namespace Unity.AutoLOD
8 | {
9 | public class ProfileAnimation : MonoBehaviour
10 | {
11 | public PlayableDirector playableDirector;
12 | public bool showProfilerWindow = true;
13 |
14 | IEnumerator Start()
15 | {
16 | var profilerWindowType = typeof(EditorApplication).Assembly.GetType("UnityEditor.ProfilerWindow");
17 |
18 | ProfilerDriver.ClearAllFrames();
19 |
20 | // Skip first frame stutter
21 | ProfilerDriver.enabled = false;
22 | yield return null;
23 | ProfilerDriver.enabled = true;
24 |
25 | if (showProfilerWindow)
26 | {
27 | var window = EditorWindow.GetWindow(profilerWindowType);
28 | window.maximized = true;
29 | }
30 |
31 | while (playableDirector.playableGraph.IsValid() && playableDirector.playableGraph.IsPlaying())
32 | yield return null;
33 |
34 | ProfilerDriver.enabled = false;
35 | EditorApplication.isPlaying = false;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Runtime/Shaders/SimpleBatcher.shader:
--------------------------------------------------------------------------------
1 | Shader "Custom/AutoLOD/SimpleBatcher" {
2 | Properties {
3 | _Color ("Color", Color) = (1,1,1,1)
4 | _MainTex ("Albedo (RGB)", 2D) = "white" {}
5 | _Glossiness("Smoothness", Range(0,1)) = 0.5
6 | _Metallic("Metallic", Range(0,1)) = 0.0
7 | }
8 | SubShader {
9 | Tags { "RenderType"="Opaque" }
10 | LOD 200
11 |
12 | CGPROGRAM
13 | // Physically based Standard lighting model, and enable shadows on all light types
14 | #pragma surface surf Standard vertex:vert fullforwardshadows
15 |
16 | // Use shader model 3.0 target, to get nicer looking lighting
17 | #pragma target 3.0
18 |
19 | sampler2D _MainTex;
20 |
21 | struct Input {
22 | float2 uv_MainTex;
23 | float3 vertexColor; // Vertex color stored here by vert() method
24 | };
25 |
26 | half _Glossiness;
27 | half _Metallic;
28 | fixed4 _Color;
29 |
30 | half4 LightingUnlit(SurfaceOutput s, half3 lightDir, half atten)
31 | {
32 | half4 c;
33 | c.rgb = s.Albedo;
34 | c.a = s.Alpha;
35 | return c;
36 | }
37 |
38 | // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
39 | // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
40 | // #pragma instancing_options assumeuniformscaling
41 | UNITY_INSTANCING_BUFFER_START(Props)
42 | // put more per-instance properties here
43 | UNITY_INSTANCING_BUFFER_END(Props)
44 |
45 | void vert(inout appdata_full v, out Input o)
46 | {
47 | UNITY_INITIALIZE_OUTPUT(Input, o);
48 | o.vertexColor = v.color.rgb; // Save the Vertex Color in the Input for the surf() method
49 | }
50 |
51 | void surf (Input IN, inout SurfaceOutputStandard o) {
52 | // Albedo comes from a texture tinted by color
53 | fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
54 | o.Albedo = c.rgb * IN.vertexColor;
55 | // Metallic and smoothness come from slider variables
56 | o.Metallic = _Metallic;
57 | o.Smoothness = _Glossiness;
58 | o.Alpha = c.a;
59 | }
60 | ENDCG
61 | }
62 | FallBack "Diffuse"
63 | }
64 |
--------------------------------------------------------------------------------
/Editor/LODData.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace Unity.AutoLOD
4 | {
5 | public class LODData : ScriptableObject
6 | {
7 | public const int MaxLOD = 7;
8 |
9 | public bool overrideDefaults;
10 | public LODImportSettings importSettings;
11 | public Renderer[] lod0;
12 | public Renderer[] lod1;
13 | public Renderer[] lod2;
14 | public Renderer[] lod3;
15 | public Renderer[] lod4;
16 | public Renderer[] lod5;
17 | public Renderer[] lod6;
18 | public Renderer[] lod7;
19 |
20 | public Renderer[] this[int index]
21 | {
22 | get
23 | {
24 | switch (index)
25 | {
26 | default:
27 | return lod0;
28 | case 1:
29 | return lod1;
30 | case 2:
31 | return lod2;
32 | case 3:
33 | return lod3;
34 | case 4:
35 | return lod4;
36 | case 5:
37 | return lod5;
38 | case 6:
39 | return lod6;
40 | case 7:
41 | return lod7;
42 | }
43 | }
44 | set
45 | {
46 | switch (index)
47 | {
48 | default:
49 | lod0 = value;
50 | break;
51 | case 1:
52 | lod1 = value;
53 | break;
54 | case 2:
55 | lod2 = value;
56 | break;
57 | case 3:
58 | lod3 = value;
59 | break;
60 | case 4:
61 | lod4 = value;
62 | break;
63 | case 5:
64 | lod5 = value;
65 | break;
66 | case 6:
67 | lod6 = value;
68 | break;
69 | case 7:
70 | lod7 = value;
71 | break;
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/InstaLODMeshSimplifier.cs:
--------------------------------------------------------------------------------
1 | #if ENABLE_INSTALOD
2 | using System;
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using System.Threading;
6 | using InstaLOD;
7 | using UnityEditor;
8 | using UnityEngine;
9 | using Unity.AutoLOD;
10 | using UnityObject = UnityEngine.Object;
11 | #endif
12 |
13 | #if ENABLE_INSTALOD
14 | namespace Unity.AutoLOD
15 | {
16 | public struct InstaLODMeshSimplifier : IMeshSimplifier
17 | {
18 | static object executionLock = new object();
19 |
20 | public void Simplify(WorkingMesh inputMesh, WorkingMesh outputMesh, float quality)
21 | {
22 | Renderer renderer = null;
23 |
24 | MonoBehaviourHelper.ExecuteOnMainThread(() =>
25 | {
26 | var go = EditorUtility.CreateGameObjectWithHideFlags("Temp", HideFlags.HideAndDontSave, typeof(MeshRenderer), typeof(MeshFilter));
27 | var mf = go.GetComponent();
28 | var mesh = new Mesh();
29 | inputMesh.ApplyToMesh(mesh);
30 | mf.sharedMesh = mesh;
31 | renderer = go.GetComponent();
32 | var material = new Material(Shader.Find("Standard"));
33 | var sharedMaterials = new Material[mesh.subMeshCount];
34 | for (int i = 0; i < mesh.subMeshCount; i++)
35 | sharedMaterials[i] = material;
36 | renderer.sharedMaterials = sharedMaterials;
37 | renderer.enabled = false;
38 | });
39 |
40 | var settings = new InstaLODOptimizeSettings(quality);
41 | settings.PercentTriangles = quality;
42 | var nativeMeshSettings = new InstaLODNativeMeshOperationSettings(true);
43 | nativeMeshSettings.hideSourceGameObjects = false;
44 |
45 | lock (executionLock)
46 | {
47 | if (!MonoBehaviourHelper.IsMainThread())
48 | {
49 | while (InstaLODNative.currentMeshOperationState != null)
50 | Thread.Sleep(100);
51 | }
52 |
53 | MonoBehaviourHelper.ExecuteOnMainThread(() =>
54 | {
55 | EditorWindow.GetWindow(); // Necessary for background processing
56 | InstaLODNative.Optimize(new List() { renderer }, settings, nativeMeshSettings);
57 | Selection.activeGameObject = null; // Necessary to avoid errors from InstaLOD trying to add settings component to imported model
58 | });
59 | }
60 |
61 | while (InstaLODNative.currentMeshOperationState != null)
62 | {
63 | if (MonoBehaviourHelper.IsMainThread())
64 | InstaLODMainThreadAction.RunMainThreadActions();
65 | else
66 | Thread.Sleep(100);
67 | }
68 |
69 | MonoBehaviourHelper.ExecuteOnMainThread(() =>
70 | {
71 | var mf = renderer.GetComponent();
72 | mf.sharedMesh.ApplyToWorkingMesh(outputMesh);
73 | UnityObject.DestroyImmediate(mf.gameObject);
74 | });
75 | }
76 | }
77 | }
78 | #endif
--------------------------------------------------------------------------------
/Editor/LODImportSettingsDrawer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Unity.AutoLOD.Utilities;
4 | using UnityEditor;
5 | using UnityEngine;
6 |
7 | namespace Unity.AutoLOD
8 | {
9 | [CustomPropertyDrawer(typeof(LODImportSettings))]
10 | public class LODImportSettingsDrawer : PropertyDrawer
11 | {
12 | // Draw the property inside the given rect
13 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
14 | {
15 | // Using BeginProperty / EndProperty on the parent property means that
16 | // prefab override logic works on the entire property.
17 | EditorGUI.BeginProperty(position, label, property);
18 |
19 | // Draw label
20 | EditorGUILayout.PrefixLabel(label);
21 |
22 | // Draw fields - passs GUIContent.none to each so they are drawn without labels
23 | var generateOnImportProperty = property.FindPropertyRelative("generateOnImport");
24 | EditorGUILayout.PropertyField(generateOnImportProperty, new GUIContent("Generate on Import"));
25 |
26 | if (generateOnImportProperty.boolValue)
27 | {
28 | PropertyTypeField(property.FindPropertyRelative("meshSimplifier"));
29 | PropertyTypeField(property.FindPropertyRelative("batcher"));
30 |
31 | var maxLODProperty = property.FindPropertyRelative("maxLODGenerated");
32 | var maxLODValues = Enumerable.Range(0, LODData.MaxLOD + 1).ToArray();
33 | EditorGUI.BeginChangeCheck();
34 | int maxLODGenerated = EditorGUILayout.IntPopup("Maximum LOD Generated", maxLODProperty.intValue, maxLODValues.Select(v => v.ToString()).ToArray(), maxLODValues);
35 | if (EditorGUI.EndChangeCheck())
36 | maxLODProperty.intValue = maxLODGenerated;
37 |
38 | var initialLODMaxPolyCountProperty = property.FindPropertyRelative("initialLODMaxPolyCount");
39 | EditorGUI.BeginChangeCheck();
40 | var maxPolyCount = EditorGUILayout.IntField("Initial LOD Max Poly Count", initialLODMaxPolyCountProperty.intValue);
41 | if (EditorGUI.EndChangeCheck())
42 | initialLODMaxPolyCountProperty.intValue = maxPolyCount;
43 | }
44 |
45 | EditorGUILayout.Space();
46 | EditorGUILayout.Space();
47 | EditorGUILayout.Space();
48 |
49 | EditorGUI.EndProperty();
50 | }
51 |
52 | void PropertyTypeField(SerializedProperty property)
53 | {
54 | var implementations = ObjectUtils.GetImplementationsOfInterface(typeof(T)).ToList();
55 | var type = Type.GetType(property.stringValue);
56 | if (type == null && implementations.Count > 0)
57 | type = Type.GetType(implementations[0].AssemblyQualifiedName);
58 |
59 | var displayedOptions = implementations.Select(t => t.Name).ToArray();
60 | EditorGUI.BeginChangeCheck();
61 | var selected = EditorGUILayout.Popup(property.displayName, Array.IndexOf(displayedOptions, type.Name), displayedOptions);
62 | if (EditorGUI.EndChangeCheck())
63 | property.stringValue = implementations[selected].AssemblyQualifiedName;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Editor/TextureAtlasModule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using UnityEditor;
7 | using UnityEngine;
8 | using UnityObject = UnityEngine.Object;
9 |
10 | namespace Unity.AutoLOD
11 | {
12 | public class TextureAtlasModule : ScriptableSingleton
13 | {
14 | List m_Atlases = new List();
15 |
16 | void OnEnable()
17 | {
18 | m_Atlases.Clear();
19 |
20 | var atlases = Resources.FindObjectsOfTypeAll();
21 | m_Atlases.AddRange(atlases);
22 | }
23 |
24 | static void SaveUniqueAtlasAsset(UnityObject asset)
25 | {
26 | var directory = "Assets/AutoLOD/Generated/Atlases/";
27 | if (!Directory.Exists(directory))
28 | Directory.CreateDirectory(directory);
29 |
30 | var path = directory + Path.GetRandomFileName();
31 | path = Path.ChangeExtension(path, "asset");
32 | AssetDatabase.CreateAsset(asset, path);
33 | }
34 |
35 | public IEnumerator GetTextureAtlas(Texture2D[] textures, Action callback)
36 | {
37 | TextureAtlas atlas = null;
38 |
39 | // Clear out any atlases that were removed
40 | m_Atlases.RemoveAll(a => a == null);
41 | yield return null;
42 |
43 | foreach (var a in m_Atlases)
44 | {
45 | // At a minimum the atlas should have all of the textures requested, but can be a superset
46 | if (!textures.Except(a.textures).Any())
47 | {
48 | atlas = a;
49 | break;
50 | }
51 |
52 | yield return null;
53 | }
54 |
55 | if (!atlas)
56 | {
57 | atlas = ScriptableObject.CreateInstance();
58 |
59 | foreach (var t in textures)
60 | {
61 | var assetImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(t));
62 | var textureImporter = assetImporter as TextureImporter;
63 | if (textureImporter && !textureImporter.isReadable)
64 | {
65 | textureImporter.isReadable = true;
66 | textureImporter.SaveAndReimport();
67 | }
68 | else if (!assetImporter)
69 | {
70 | // In-memory textures need to be saved to disk in order to be referenced by the texture atlas
71 | SaveUniqueAtlasAsset(t);
72 | }
73 | yield return null;
74 | }
75 |
76 | var textureAtlas = new Texture2D(0, 0, TextureFormat.RGB24, true, PlayerSettings.colorSpace == ColorSpace.Linear);
77 | var uvs = textureAtlas.PackTextures(textures.ToArray(), 0, 1024, true);
78 |
79 | if (uvs != null)
80 | {
81 | atlas.textureAtlas = textureAtlas;
82 | atlas.uvs = uvs;
83 | atlas.textures = textures;
84 |
85 | SaveUniqueAtlasAsset(textureAtlas);
86 | SaveUniqueAtlasAsset(atlas);
87 |
88 | m_Atlases.Add(atlas);
89 | }
90 |
91 | yield return null;
92 | }
93 |
94 | if (callback != null)
95 | callback(atlas);
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Editor/LODDataEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using Unity.AutoLOD;
3 | using UnityEngine;
4 |
5 | namespace Unity.AutoLOD
6 | {
7 | [CustomEditor(typeof(LODData))]
8 | [CanEditMultipleObjects]
9 | public class LODDataEditor : Editor
10 | {
11 | public static int maxLODGenerated { set; get; }
12 | public static int initialLODMaxPolyCount { set; get; }
13 | public static string meshSimplifier;
14 | public static string batcher;
15 |
16 | SerializedProperty m_OverrideDefaults;
17 | SerializedProperty m_ImportSettings;
18 | SerializedProperty[] m_LODs = new SerializedProperty[LODData.MaxLOD + 1];
19 |
20 | void OnEnable()
21 | {
22 | m_OverrideDefaults = serializedObject.FindProperty("overrideDefaults");
23 | m_ImportSettings = serializedObject.FindProperty("importSettings");
24 | for (int i = 0; i < m_LODs.Length; i++)
25 | {
26 | m_LODs[i] = serializedObject.FindProperty("lod" + i);
27 | }
28 | }
29 |
30 | public override void OnInspectorGUI()
31 | {
32 | serializedObject.Update();
33 |
34 | EditorGUI.BeginChangeCheck();
35 | var settingsOverridden = m_OverrideDefaults.boolValue;
36 | EditorGUI.BeginChangeCheck();
37 | EditorGUILayout.PropertyField(m_OverrideDefaults, new GUIContent("Override Defaults"));
38 | if (EditorGUI.EndChangeCheck() && m_OverrideDefaults.boolValue)
39 | {
40 | m_ImportSettings.FindPropertyRelative("generateOnImport").boolValue = true;
41 | m_ImportSettings.FindPropertyRelative("meshSimplifier").stringValue = meshSimplifier;
42 | m_ImportSettings.FindPropertyRelative("batcher").stringValue = batcher;
43 | m_ImportSettings.FindPropertyRelative("maxLODGenerated").intValue = maxLODGenerated;
44 | m_ImportSettings.FindPropertyRelative("initialLODMaxPolyCount").intValue = initialLODMaxPolyCount;
45 | }
46 |
47 | if (settingsOverridden)
48 | {
49 | EditorGUILayout.PropertyField(m_ImportSettings, new GUIContent("Import Settings"), true);
50 | }
51 |
52 | EditorGUI.BeginDisabledGroup(!settingsOverridden || m_ImportSettings.FindPropertyRelative("generateOnImport").boolValue);
53 | for (int i = 0; i < m_LODs.Length; i++)
54 | {
55 | var lod = m_LODs[i];
56 | if (lod.arraySize > 0)
57 | {
58 | EditorGUI.BeginDisabledGroup(i == 0);
59 | EditorGUILayout.PropertyField(lod, new GUIContent("LOD" + i), true);
60 | EditorGUI.EndDisabledGroup();
61 | }
62 | else
63 | {
64 | if (settingsOverridden)
65 | {
66 | EditorGUILayout.BeginHorizontal();
67 | EditorGUI.BeginDisabledGroup(i == 1);
68 | if (GUILayout.Button("Remove LOD"))
69 | m_LODs[i - 1].ClearArray();
70 | EditorGUI.EndDisabledGroup();
71 | if (GUILayout.Button("Add LOD"))
72 | lod.InsertArrayElementAtIndex(0);
73 | EditorGUILayout.EndHorizontal();
74 | }
75 | break;
76 | }
77 | }
78 | EditorGUI.EndDisabledGroup();
79 |
80 | if (EditorGUI.EndChangeCheck())
81 | serializedObject.ApplyModifiedProperties();
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Editor/TagManager.cs:
--------------------------------------------------------------------------------
1 | // From: https://github.com/Unity-Technologies/EditorVR
2 | using System.Collections.Generic;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace Unity.AutoLOD.Utilities
7 | {
8 | static class TagManager
9 | {
10 | const int k_MaxLayer = 31;
11 | const int k_MinLayer = 8;
12 |
13 | ///
14 | /// Add a tag to the tag manager if it doesn't already exist
15 | ///
16 | /// Tag to add
17 | public static void AddTag(string tag)
18 | {
19 | SerializedObject so;
20 | var tags = GetTagManagerProperty("tags", out so);
21 | if (tags != null)
22 | {
23 | var found = false;
24 | for (var i = 0; i < tags.arraySize; i++)
25 | {
26 | if (tags.GetArrayElementAtIndex(i).stringValue == tag)
27 | {
28 | found = true;
29 | break;
30 | }
31 | }
32 |
33 | if (!found)
34 | {
35 | var arraySize = tags.arraySize;
36 | tags.InsertArrayElementAtIndex(arraySize);
37 | tags.GetArrayElementAtIndex(arraySize - 1).stringValue = tag;
38 | }
39 | so.ApplyModifiedProperties();
40 | so.Update();
41 | }
42 | }
43 |
44 | ///
45 | /// Add a layer to the tag manager if it doesn't already exist
46 | /// Start at layer 31 (max) and work down
47 | ///
48 | ///
49 | public static void AddLayer(string layerName)
50 | {
51 | SerializedObject so;
52 | var layers = GetTagManagerProperty("layers", out so);
53 | if (layers != null)
54 | {
55 | var found = false;
56 | for (var i = 0; i < layers.arraySize; i++)
57 | {
58 | if (layers.GetArrayElementAtIndex(i).stringValue == layerName)
59 | {
60 | found = true;
61 | break;
62 | }
63 | }
64 |
65 | if (!found)
66 | {
67 | var added = false;
68 | for (var i = k_MaxLayer; i >= k_MinLayer; i--)
69 | {
70 | var layer = layers.GetArrayElementAtIndex(i);
71 | if (!string.IsNullOrEmpty(layer.stringValue))
72 | continue;
73 |
74 | layer.stringValue = layerName;
75 | added = true;
76 | break;
77 | }
78 |
79 | if (!added)
80 | Debug.LogWarning("Could not add layer " + layerName + " because there are no free layers");
81 | }
82 | so.ApplyModifiedProperties();
83 | so.Update();
84 | }
85 | }
86 |
87 | public static SerializedProperty GetTagManagerProperty(string name, out SerializedObject so)
88 | {
89 | var asset = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset");
90 | if ((asset != null) && (asset.Length > 0))
91 | {
92 | so = new SerializedObject(asset[0]);
93 | return so.FindProperty(name);
94 | }
95 |
96 | so = null;
97 | return null;
98 | }
99 |
100 | public static List GetRequiredTags()
101 | {
102 | var requiredTags = new List();
103 | ObjectUtils.ForEachType(t =>
104 | {
105 | var tagAttributes = (RequiresTagAttribute[])t.GetCustomAttributes(typeof(RequiresTagAttribute), true);
106 | foreach (var attribute in tagAttributes)
107 | requiredTags.Add(attribute.tag);
108 | });
109 | return requiredTags;
110 | }
111 |
112 | public static List GetRequiredLayers()
113 | {
114 | var requiredLayers = new List();
115 | ObjectUtils.ForEachType(t =>
116 | {
117 | var layerAttributes = (RequiresLayerAttribute[])t.GetCustomAttributes(typeof(RequiresLayerAttribute), true);
118 | foreach (var attribute in layerAttributes)
119 | requiredLayers.Add(attribute.layer);
120 | });
121 | return requiredLayers;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Editor/GridPlacementUtility.cs:
--------------------------------------------------------------------------------
1 | using Unity.AutoLOD.Utilities;
2 | using UnityEditor;
3 | using UnityEngine;
4 |
5 | namespace Unity.AutoLOD
6 | {
7 | public class GridPlacementUtility : EditorWindow
8 | {
9 | PrimitiveType m_PrimitiveType = PrimitiveType.Cube;
10 | Vector3 m_Number = Vector3.one;
11 | Vector3 m_Spacing = Vector3.one;
12 | GameObject m_Prefab;
13 | Material m_Material;
14 | bool m_UniqueMaterials;
15 |
16 | [MenuItem("GameObject/AutoLOD/Grid Placement Utility")]
17 | static void Init()
18 | {
19 | EditorWindow.GetWindow(true, "Grid Placement Utility").Show();
20 | }
21 |
22 | void OnGUI()
23 | {
24 | EditorGUILayout.Space();
25 |
26 | EditorGUI.BeginChangeCheck();
27 | m_Prefab = (GameObject)EditorGUILayout.ObjectField("Prefab", m_Prefab, typeof(GameObject), true);
28 | if (EditorGUI.EndChangeCheck())
29 | {
30 | var bounds = ObjectUtils.GetBounds(m_Prefab.transform);
31 | m_Spacing = bounds.size;
32 | }
33 |
34 | if (!m_Prefab)
35 | {
36 | GUILayout.Label("OR");
37 | m_PrimitiveType = (PrimitiveType)EditorGUILayout.EnumPopup("Primitive", m_PrimitiveType);
38 | }
39 |
40 | EditorGUILayout.Space();
41 | EditorGUILayout.Space();
42 |
43 |
44 | m_Number = EditorGUILayout.Vector3Field("Number", m_Number);
45 | m_Spacing = EditorGUILayout.Vector3Field("Spacing", m_Spacing);
46 |
47 | EditorGUILayout.Space();
48 | EditorGUILayout.Space();
49 |
50 | m_UniqueMaterials = EditorGUILayout.Toggle("Unique Materials", m_UniqueMaterials);
51 | m_Material = (Material)EditorGUILayout.ObjectField("Material", m_Material, typeof(Material), true);
52 |
53 | EditorGUILayout.Space();
54 | EditorGUILayout.Space();
55 |
56 | if (GUILayout.Button("Create"))
57 | {
58 | GameObject parent = new GameObject("Primitives");
59 | for (int i = 0; i < Mathf.FloorToInt(m_Number.x); i++)
60 | {
61 | for (int j = 0; j < Mathf.FloorToInt(m_Number.y); j++)
62 | {
63 | for (int k = 0; k < Mathf.FloorToInt(m_Number.z); k++)
64 | {
65 | GameObject go = m_Prefab ? (GameObject)PrefabUtility.InstantiatePrefab(m_Prefab) : GameObject.CreatePrimitive(m_PrimitiveType);
66 | go.name = string.Format("{0} {1}_{2}_{3}", m_Prefab ? m_Prefab.name : m_PrimitiveType.ToString(), i, j, k);
67 | go.transform.position = Vector3.right * (m_Spacing.x * i + i) + Vector3.up * (m_Spacing.y * j + j) + Vector3.forward * (m_Spacing.z * k + k);
68 | go.transform.parent = parent.transform;
69 |
70 | var meshRenderers = go.GetComponentsInChildren();
71 | foreach (var mr in meshRenderers)
72 | {
73 | var sharedMaterials = mr.sharedMaterials;
74 | for (int m = 0; m < sharedMaterials.Length; m++)
75 | {
76 | var material = m_Material ? m_Material : sharedMaterials[m];
77 | sharedMaterials[m] = m_UniqueMaterials ? Instantiate(material) : material;
78 | }
79 | mr.sharedMaterials = sharedMaterials;
80 | }
81 | }
82 | }
83 | }
84 | Close();
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/SimulatedMeshSimplifier.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using Mesh = Unity.AutoLOD.WorkingMesh;
5 |
6 | namespace Unity.AutoLOD
7 | {
8 | [HideInInspector]
9 | public struct SimulatedMeshSimplifier : IMeshSimplifier
10 | {
11 | public void Simplify(Mesh inputMesh, Mesh outputMesh, float quality)
12 | {
13 | Vector3[] vertices = inputMesh.vertices;
14 | Vector2[] uv = inputMesh.uv;
15 | Vector2[] uv2 = inputMesh.uv2;
16 | Color[] colors = inputMesh.colors;
17 | Vector3[] normals = inputMesh.normals;
18 | Vector4[] tangents = inputMesh.tangents;
19 |
20 | var usedVertices = new Dictionary();
21 | var submeshTriangles = new Dictionary>();
22 | for (int i = 0; i < inputMesh.subMeshCount; i++)
23 | {
24 | var triangles = new List(inputMesh.GetTriangles(i));
25 | var targetCount = Mathf.FloorToInt(triangles.Count * quality);
26 | targetCount = Mathf.Max(0, targetCount - targetCount % 3);
27 |
28 | var random = new System.Random();
29 | while (triangles.Count > targetCount)
30 | {
31 | var randomTriangle = Mathf.CeilToInt((float)random.NextDouble() * (triangles.Count - 1));
32 | randomTriangle -= randomTriangle % 3;
33 |
34 | triangles.RemoveRange(randomTriangle, 3);
35 | }
36 |
37 | for (int t = 0; t < triangles.Count; t++)
38 | {
39 | var index = triangles[t];
40 | usedVertices[index] = vertices[index];
41 | }
42 |
43 | submeshTriangles[i] = triangles;
44 | }
45 |
46 | var trimmedVertices = new List();
47 | var trimmedUVs = new List();
48 | var trimmedUV2s = new List();
49 | var trimmedColors = new List();
50 | var trimmedNormals = new List();
51 | var trimmedTangents = new List();
52 |
53 | var vertexRemap = new Dictionary();
54 | for (int i = 0; i < vertices.Length; i++)
55 | {
56 | Vector3 vertex;
57 | if (usedVertices.TryGetValue(i, out vertex))
58 | {
59 | vertexRemap[i] = trimmedVertices.Count;
60 | trimmedVertices.Add(vertex);
61 |
62 | if (uv.Length > 0)
63 | trimmedUVs.Add(uv[i]);
64 |
65 | if (uv2.Length > 0)
66 | trimmedUV2s.Add(uv2[i]);
67 |
68 | if (colors.Length > 0)
69 | trimmedColors.Add(colors[i]);
70 |
71 | if (normals.Length > 0)
72 | trimmedNormals.Add(normals[i]);
73 |
74 | if (tangents.Length > 0)
75 | trimmedTangents.Add(tangents[i]);
76 | }
77 | }
78 |
79 | outputMesh.vertices = trimmedVertices.ToArray();
80 |
81 | for (int i = 0; i < inputMesh.subMeshCount; i++)
82 | {
83 | var triangles = submeshTriangles[i];
84 |
85 | for (int t = 0; t < triangles.Count; t++)
86 | {
87 | triangles[t] = vertexRemap[triangles[t]];
88 | }
89 |
90 | outputMesh.SetTriangles(triangles.ToArray(), i);
91 | }
92 |
93 | outputMesh.uv = trimmedUVs.ToArray();
94 | outputMesh.uv2 = trimmedUV2s.ToArray();
95 | outputMesh.colors = trimmedColors.ToArray();
96 | outputMesh.normals = trimmedNormals.ToArray();
97 | outputMesh.tangents = trimmedTangents.ToArray();
98 |
99 | outputMesh.RecalculateBounds();
100 | outputMesh.RecalculateNormals();
101 | outputMesh.RecalculateTangents();
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ## From https://gist.github.com/MattOstgard/046b122d7d14b6c10b7083c732efb93c w/ modifications
2 |
3 | # Set the default behavior, in case people don't have core.autocrlf set.
4 | * text=auto
5 |
6 | # Unity
7 | *.cginc text
8 | *.cs diff=csharp text eol=lf
9 | *.shader text
10 |
11 |
12 | # Unity YAML
13 | *.anim merge=unityyamlmerge eol=lf
14 | *.controller merge=unityyamlmerge eol=lf
15 | *.mat merge=unityyamlmerge eol=lf
16 | *.meta merge=unityyamlmerge eol=lf
17 | *.physicMaterial merge=unityyamlmerge eol=lf
18 | *.physicMaterial2D merge=unityyamlmerge eol=lf
19 | *.prefab merge=unityyamlmerge eol=lf
20 | *.unity merge=unityyamlmerge eol=lf
21 |
22 |
23 | # Unity LFS
24 | *.lfs.* filter=lfs diff=lfs merge=lfs -crlf
25 | *.cubemap filter=lfs diff=lfs merge=lfs -text
26 | *.unitypackage filter=lfs diff=lfs merge=lfs -text
27 |
28 |
29 | # Image
30 | *.ai filter=lfs diff=lfs merge=lfs -text
31 | *.apng filter=lfs diff=lfs merge=lfs -text
32 | *.astc filter=lfs diff=lfs merge=lfs -text
33 | *.bmp filter=lfs diff=lfs merge=lfs -text
34 | *.dds filter=lfs diff=lfs merge=lfs -text
35 | *.eps filter=lfs diff=lfs merge=lfs -text
36 | *.exr filter=lfs diff=lfs merge=lfs -text
37 | *.gif filter=lfs diff=lfs merge=lfs -text
38 | *.hdr filter=lfs diff=lfs merge=lfs -text
39 | *.jpeg filter=lfs diff=lfs merge=lfs -text
40 | *.jpg filter=lfs diff=lfs merge=lfs -text
41 | *.ktx filter=lfs diff=lfs merge=lfs -text
42 | *.png filter=lfs diff=lfs merge=lfs -text
43 | *.psd filter=lfs diff=lfs merge=lfs -text
44 | *.pvr filter=lfs diff=lfs merge=lfs -text
45 | *.svg filter=lfs diff=lfs merge=lfs -text
46 | *.svgz filter=lfs diff=lfs merge=lfs -text
47 | *.tga filter=lfs diff=lfs merge=lfs -text
48 | *.tif filter=lfs diff=lfs merge=lfs -text
49 | *.tiff filter=lfs diff=lfs merge=lfs -text
50 | *.webm filter=lfs diff=lfs merge=lfs -text
51 | *.webp filter=lfs diff=lfs merge=lfs -text
52 |
53 |
54 | # Audio
55 | *.aif filter=lfs diff=lfs merge=lfs -text
56 | *.m4a filter=lfs diff=lfs merge=lfs -text
57 | *.mp3 filter=lfs diff=lfs merge=lfs -text
58 | *.ogg filter=lfs diff=lfs merge=lfs -text
59 | *.wav filter=lfs diff=lfs merge=lfs -text
60 |
61 |
62 | # Video
63 | *.asf filter=lfs diff=lfs merge=lfs -text
64 | *.avi filter=lfs diff=lfs merge=lfs -text
65 | *.flv filter=lfs diff=lfs merge=lfs -text
66 | *.mov filter=lfs diff=lfs merge=lfs -text
67 | *.mp4 filter=lfs diff=lfs merge=lfs -text
68 | *.mpeg filter=lfs diff=lfs merge=lfs -text
69 | *.mpg filter=lfs diff=lfs merge=lfs -text
70 | *.ogv filter=lfs diff=lfs merge=lfs -text
71 | *.wmv filter=lfs diff=lfs merge=lfs -text
72 |
73 |
74 | # 3D Object
75 | *.blend filter=lfs diff=lfs merge=lfs -text
76 | *.dxf filter=lfs diff=lfs merge=lfs -text
77 | *.fbx filter=lfs diff=lfs merge=lfs -text
78 | *.lxo filter=lfs diff=lfs merge=lfs -text
79 | *.ma filter=lfs diff=lfs merge=lfs -text
80 | *.max filter=lfs diff=lfs merge=lfs -text
81 | *.mb filter=lfs diff=lfs merge=lfs -text
82 | *.obj filter=lfs diff=lfs merge=lfs -text
83 |
84 |
85 | # Compressed Archive
86 | *.7z filter=lfs diff=lfs merge=lfs -text
87 | *.bz2 filter=lfs diff=lfs merge=lfs -text
88 | *.gz filter=lfs diff=lfs merge=lfs -text
89 | *.rar filter=lfs diff=lfs merge=lfs -text
90 | *.tar filter=lfs diff=lfs merge=lfs -text
91 | *.zip filter=lfs diff=lfs merge=lfs -text
92 |
93 |
94 | # Compiled Dynamic Library
95 | *.dll filter=lfs diff=lfs merge=lfs -text
96 | *.pdb filter=lfs diff=lfs merge=lfs -text
97 | *.so filter=lfs diff=lfs merge=lfs -text
98 |
99 |
100 | # Compiled Static Library
101 | *.a filter=lfs diff=lfs merge=lfs -text
102 | *.la filter=lfs diff=lfs merge=lfs -text
103 | *.lai filter=lfs diff=lfs merge=lfs -text
104 | *.lib filter=lfs diff=lfs merge=lfs -text
105 | *.llblgenproj filter=lfs diff=lfs merge=lfs -text
106 |
107 |
108 | # Font
109 | *.otf filter=lfs diff=lfs merge=lfs -text
110 | *.ttf filter=lfs diff=lfs merge=lfs -text
111 |
112 |
113 | # Executable/Installer
114 | *.apk filter=lfs diff=lfs merge=lfs -text
115 | *.exe filter=lfs diff=lfs merge=lfs -text
116 |
117 |
118 | # Documents
119 | *.pdf filter=lfs diff=lfs merge=lfs -text
120 |
121 |
122 | # Other
123 | *.reason filter=lfs diff=lfs merge=lfs -text
124 | *.rns filter=lfs diff=lfs merge=lfs -text
--------------------------------------------------------------------------------
/Runtime/Helpers/MeshLOD.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using Unity.Collections;
6 | using Unity.Jobs;
7 | using UnityEngine;
8 |
9 | namespace Unity.AutoLOD
10 | {
11 | interface IMeshGenerateLODJob : IJob, IDisposable
12 | {
13 | WorkingMesh InputMesh { set; }
14 | WorkingMesh OutputMesh { get; set; }
15 | float Quality { set; }
16 | }
17 |
18 | interface IMeshLOD
19 | {
20 | Mesh InputMesh { set; }
21 | Mesh OutputMesh { get; set; }
22 | float Quality { set; }
23 |
24 | IEnumerator GenerateAfterDependencies(List jobDependencies);
25 | JobHandle Generate();
26 | }
27 |
28 | struct MeshGenerateLODJob : IMeshGenerateLODJob
29 | where TSimplifier : struct, IMeshSimplifier
30 | {
31 | public WorkingMesh InputMesh { get; set; }
32 | public WorkingMesh OutputMesh { get; set; }
33 | public float Quality { get; set; }
34 |
35 | public void Execute()
36 | {
37 | var meshSimplifier = default(TSimplifier);
38 | meshSimplifier.Simplify(InputMesh, OutputMesh, Quality);
39 | }
40 |
41 | public void Dispose()
42 | {
43 | InputMesh.Dispose();
44 | OutputMesh.Dispose();
45 | }
46 | }
47 |
48 | struct MeshLOD : IMeshLOD
49 | where T : struct, IMeshGenerateLODJob
50 | {
51 | public Mesh InputMesh { get; set; }
52 | public Mesh OutputMesh { get; set; }
53 | public float Quality { get; set; }
54 |
55 | IEnumerator UpdateMesh(JobHandle jobHandle, IMeshGenerateLODJob job)
56 | {
57 | while (!jobHandle.IsCompleted)
58 | yield return new WaitForSecondsRealtime(0.5f);
59 |
60 | jobHandle.Complete();
61 |
62 | var finalMesh = OutputMesh;
63 | var jobOutputMesh = job.OutputMesh;
64 | jobOutputMesh.name = finalMesh.name;
65 | jobOutputMesh.ApplyToMesh(OutputMesh);
66 | finalMesh.RecalculateBounds();
67 |
68 | job.Dispose();
69 | }
70 |
71 | public IEnumerator GenerateAfterDependencies(List jobDependencies)
72 | {
73 | while (jobDependencies.Count > 0)
74 | {
75 | var jobDependency = jobDependencies[0];
76 | if (!jobDependency.IsCompleted)
77 | yield return new WaitForSecondsRealtime(0.5f);
78 | else
79 | jobDependencies.Remove(jobDependency);
80 | }
81 |
82 | Generate();
83 | }
84 |
85 | public JobHandle Generate()
86 | {
87 | // A NOP to make sure we have an instance before launching into threads that may need to execute on the main thread
88 | MonoBehaviourHelper.ExecuteOnMainThread(() => { });
89 |
90 | var job = default(T);
91 | var inputMesh = InputMesh;
92 | job.InputMesh = inputMesh.ToWorkingMesh(Allocator.Persistent);
93 | job.Quality = Quality;
94 | // Allocate a persistent working mesh for output, so that we can apply it after the job completes (i.e. memory
95 | // allocated in a job is freed when the job completes)
96 | var workingMesh = new WorkingMesh(Allocator.Persistent, inputMesh.vertexCount, inputMesh.GetTriangleCount(),
97 | inputMesh.subMeshCount, inputMesh.blendShapeCount);
98 | workingMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
99 | job.OutputMesh = workingMesh;
100 |
101 | var jobHandle = job.Schedule();
102 | MonoBehaviourHelper.StartCoroutine(UpdateMesh(jobHandle, job));
103 |
104 | return jobHandle;
105 | }
106 | }
107 |
108 | static class MeshLOD
109 | {
110 | static Type GetGenericType(Type meshSimplifierType)
111 | {
112 | var genericJobType = typeof(MeshGenerateLODJob<>).MakeGenericType(meshSimplifierType);
113 | var genericType = typeof(MeshLOD<>).MakeGenericType(genericJobType);
114 |
115 | return genericType;
116 | }
117 |
118 | public static IMeshLOD GetGenericInstance(Type meshSimplifierType)
119 | {
120 | return (IMeshLOD)Activator.CreateInstance(GetGenericType(meshSimplifierType));
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/Runtime/Helpers/TimedEnumerator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using UnityEngine;
5 | using Debug = UnityEngine.Debug;
6 |
7 | namespace Unity.AutoLOD
8 | {
9 | public class TimedEnumerator : IEnumerator
10 | {
11 | public float? maxIterationTimeMS { get; set; }
12 | public bool logEnabled { get; set; }
13 | public float totalExecutionTime { get; private set; }
14 | public float selfExecutionTime { get; private set; }
15 | public float iterationExecutionTime { get; private set; }
16 |
17 | float? m_FirstIterationTime;
18 | float? m_FinalIterationTime;
19 | Stack m_EnumeratorStack = new Stack();
20 |
21 | public bool MoveNext()
22 | {
23 | var sw = new Stopwatch();
24 | sw.Start();
25 |
26 | // We won't be able to capture time spent in other YieldInstructions because those are objects that get waited
27 | // on separately, so we keep track of absolute time until the iterator is complete
28 | if (!m_FirstIterationTime.HasValue)
29 | m_FirstIterationTime = Time.realtimeSinceStartup;
30 |
31 | var routine = m_EnumeratorStack.Peek();
32 | var result = true;
33 | if (maxIterationTimeMS.HasValue)
34 | {
35 | var maxTime = maxIterationTimeMS * 0.001f * Stopwatch.Frequency;
36 |
37 | // Execute through as many yields as time permits
38 | do
39 | {
40 | result &= routine.MoveNext();
41 |
42 | if (!result)
43 | {
44 | if (m_EnumeratorStack.Count > 1)
45 | {
46 | // Nested coroutine ended
47 | m_EnumeratorStack.Pop();
48 | result = true;
49 | }
50 | else
51 | break;
52 | }
53 | else
54 | {
55 | var current = Current;
56 | if (current is YieldInstruction || current is CustomYieldInstruction)
57 | {
58 | // We have to leave these to Unity to resolve
59 | break;
60 | }
61 |
62 | // Handle nesting
63 | var enumerator = current as IEnumerator;
64 | if (enumerator != null)
65 | {
66 | m_EnumeratorStack.Push(enumerator);
67 | }
68 | }
69 |
70 | routine = m_EnumeratorStack.Peek();
71 | } while (sw.ElapsedTicks < maxTime);
72 | }
73 | else
74 | {
75 | result = routine.MoveNext();
76 | }
77 |
78 | sw.Stop();
79 | var endTime = Time.realtimeSinceStartup;
80 |
81 | if (!result && !m_FinalIterationTime.HasValue)
82 | m_FinalIterationTime = endTime;
83 |
84 | iterationExecutionTime = (float)sw.ElapsedTicks / Stopwatch.Frequency;
85 | selfExecutionTime += iterationExecutionTime;
86 |
87 | if (m_FinalIterationTime.HasValue)
88 | totalExecutionTime = m_FinalIterationTime.Value - m_FirstIterationTime.Value;
89 | else
90 | totalExecutionTime = endTime - m_FirstIterationTime.Value;
91 |
92 | if (logEnabled)
93 | Debug.LogFormat("Iteration: {0} / Self: {1} / Total: {2}", iterationExecutionTime, selfExecutionTime, totalExecutionTime);
94 |
95 | return result;
96 | }
97 |
98 | public void Reset()
99 | {
100 | while (m_EnumeratorStack.Count > 1)
101 | m_EnumeratorStack.Pop();
102 |
103 | m_EnumeratorStack.Peek().Reset();
104 | iterationExecutionTime = 0f;
105 | selfExecutionTime = 0f;
106 | totalExecutionTime = 0f;
107 | m_FirstIterationTime = null;
108 | m_FinalIterationTime = null;
109 | }
110 |
111 | public object Current
112 | {
113 | get { return m_EnumeratorStack.Count > 0 ? m_EnumeratorStack.Peek().Current : null; }
114 | }
115 |
116 | public TimedEnumerator(IEnumerator routine, float? maxIterationTimeMS = null)
117 | {
118 | m_EnumeratorStack.Push(routine);
119 |
120 | if (maxIterationTimeMS.HasValue)
121 | this.maxIterationTimeMS = maxIterationTimeMS;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Runtime/Extensions/LODGroupExtensions.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace Unity.AutoLOD
4 | {
5 | public static class LODGroupExtensions
6 | {
7 | public static void SetEnabled(this LODGroup lodGroup, bool enabled)
8 | {
9 | if (lodGroup.enabled != enabled)
10 | {
11 | lodGroup.enabled = enabled;
12 | lodGroup.SetRenderersEnabled(enabled);
13 | }
14 | }
15 |
16 | public static void SetRenderersEnabled(this LODGroup lodGroup, bool enabled)
17 | {
18 | var lods = lodGroup.GetLODs();
19 | SetRenderersEnabled(lods, enabled);
20 | }
21 |
22 | public static void SetEnabled(this LODVolume.LODGroupHelper lodGroupHelper, bool enabled)
23 | {
24 | var lodGroup = lodGroupHelper.lodGroup;
25 | if (lodGroup == null || lodGroup.enabled != enabled)
26 | {
27 | if (lodGroup)
28 | lodGroup.enabled = enabled;
29 | SetRenderersEnabled(lodGroupHelper.lods, enabled);
30 | }
31 | }
32 |
33 | static void SetRenderersEnabled(LOD[] lods, bool enabled)
34 | {
35 | for (var i = 0; i < lods.Length; i++)
36 | {
37 | var lod = lods[i];
38 |
39 | var renderers = lod.renderers;
40 | foreach (var r in renderers)
41 | {
42 | if (r)
43 | r.enabled = enabled;
44 | }
45 | }
46 | }
47 |
48 | public static int GetMaxLOD(this LODGroup lodGroup)
49 | {
50 | return lodGroup.lodCount - 1;
51 | }
52 |
53 | public static int GetCurrentLOD(this LODGroup lodGroup, Camera camera = null)
54 | {
55 | var lods = lodGroup.GetLODs();
56 | var relativeHeight = lodGroup.GetRelativeHeight(camera ?? Camera.current);
57 |
58 | var lodIndex = GetCurrentLOD(lods, lodGroup.GetMaxLOD(), relativeHeight, camera);
59 |
60 | return lodIndex;
61 | }
62 |
63 | public static int GetMaxLOD(this LODVolume.LODGroupHelper lodGroupHelper)
64 | {
65 | return lodGroupHelper.maxLOD;
66 | }
67 |
68 | public static int GetCurrentLOD(this LODVolume.LODGroupHelper lodGroupHelper, Camera camera = null, Vector3? cameraPosition = null)
69 | {
70 | var lods = lodGroupHelper.lods;
71 | camera = camera ?? Camera.current;
72 | cameraPosition = cameraPosition ?? camera.transform.position;
73 | var relativeHeight = lodGroupHelper.GetRelativeHeight(camera, cameraPosition.Value);
74 | return GetCurrentLOD(lods, lodGroupHelper.GetMaxLOD(), relativeHeight, camera);
75 | }
76 |
77 | public static float GetWorldSpaceSize(this LODGroup lodGroup)
78 | {
79 | return GetWorldSpaceScale(lodGroup.transform) * lodGroup.size;
80 | }
81 |
82 | static int GetCurrentLOD(LOD[] lods, int maxLOD, float relativeHeight, Camera camera = null)
83 | {
84 | var lodIndex = maxLOD;
85 |
86 | for (var i = 0; i < lods.Length; i++)
87 | {
88 | var lod = lods[i];
89 |
90 | if (relativeHeight >= lod.screenRelativeTransitionHeight)
91 | {
92 | lodIndex = i;
93 | break;
94 | }
95 | }
96 |
97 | return lodIndex;
98 | }
99 |
100 | static float GetWorldSpaceScale(Transform t)
101 | {
102 | var scale = t.lossyScale;
103 | float largestAxis = Mathf.Abs(scale.x);
104 | largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.y));
105 | largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.z));
106 | return largestAxis;
107 | }
108 |
109 | static float DistanceToRelativeHeight(Camera camera, float distance, float size)
110 | {
111 | if (camera.orthographic)
112 | return size * 0.5F / camera.orthographicSize;
113 |
114 | var halfAngle = Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5F);
115 | var relativeHeight = size * 0.5F / (distance * halfAngle);
116 | return relativeHeight;
117 | }
118 |
119 | static float GetRelativeHeight(this LODGroup lodGroup, Camera camera)
120 | {
121 | var distance = (lodGroup.transform.TransformPoint(lodGroup.localReferencePoint) - camera.transform.position).magnitude;
122 | return DistanceToRelativeHeight(camera, distance, lodGroup.GetWorldSpaceSize());
123 | }
124 |
125 | static float GetRelativeHeight(this LODVolume.LODGroupHelper lodGroupHelper, Camera camera, Vector3 cameraPosition)
126 | {
127 | var distance = (lodGroupHelper.referencePoint - cameraPosition).magnitude;
128 | return DistanceToRelativeHeight(camera, distance, lodGroupHelper.worldSpaceSize);
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AutoLOD
2 | Automatic LOD generation + scene optimization - Initial release was on January 12, 2018 via [blogpost](https://blogs.unity3d.com/2018/01/12/unity-labs-autolod-experimenting-with-automatic-performance-improvements/)
3 |
4 | AutoLOD is primarily a framework for enabling automatic post-processing of geometrical model assets on import to create simplified levels-of-detail (LOD). A [default mesh simplifier](https://github.com/Whinarn/UnityMeshSimplifier/) is included, but can be swapped out with other simplifiers and on a per-model basis if needed. Additionally, a whole scene can be hierarchically chunked into LODs with [SceneLOD](https://github.com/Unity-Technologies/AutoLOD/wiki/Scenelod).
5 |
6 | ## Experimental Status
7 | It’s important to note that AutoLOD is an experimental feature. As such, there is no formal support (e.g. FogBugz, support@unity3d.com, Premium Support, etc.) offered, so please do not use these channels. Instead, post your questions, comments, suggestions, and issues here on GitHub.
8 |
9 | **As with anything experimental/preview/alpha/beta, it is always a good idea to make a backup of your project before using.**
10 |
11 | Experimental means this:
12 | - Namespaces, classes, software architecture, prefabs, etc. can change at any point. If you are writing your own tools, then you might need to update them as these things change.
13 | - There won’t always be an upgrade path from one release to the next, so you might need to fix things manually, which leads to the next point...
14 | - Stuff can and will break (!)
15 | - There’s **no guarantee** that this project will move out of experimental status within any specific timeframe.
16 | - As such, there is no guarantee that this will remain an actively supported project.
17 |
18 | ## Features
19 | - LOD generation on model import with sensible [defaults](https://github.com/Unity-Technologies/AutoLOD/wiki/Home)
20 | - Project-wide and per-model LOD import settings
21 | - Asynchronous, pluggable LOD generation framework with built-in support for:
22 | - [UnityMeshSimplifier](https://github.com/Whinarn/UnityMeshSimplifier/)
23 | - [Simplygon](https://simplygon.com/)
24 | - [InstaLOD](https://instalod.com/)
25 | - Hierarchical LOD support via [SceneLOD](https://github.com/Unity-Technologies/AutoLOD/wiki/Scenelod) -> [watch a [quick tutorial video](http://www.youtube.com/watch?v=EuBeZvzVwrw "SceneLOD Tutorial")]
26 |
27 | ### Useful classes (for your own projects, too!)
28 | - [MonoBehaviourHelper](Scripts/Helpers/MonoBehaviourHelper.cs) - a way to run coroutines in the editor + main thread execution from worker threads
29 | - [LODGroupExtensions](Scripts/Extensions/LODGroupExtensions.cs) - useful extension methods (e.g. GetCurrentLOD)
30 | - [TimedEnumerator](Scripts/Helpers/TimedEnumerator.cs) - a way to control maximum execution time of coroutines
31 | - [TextureAtlasModule](Scripts/Editor/TextureAtlasModule.cs) - automatically generate texture atlases
32 | - [WorkingMesh](Scripts/Helpers/WorkingMesh.cs) - a thread-safe mesh (_and now job-friendly!_) struct
33 |
34 | ## Evaluating
35 | Unity 2018.4 (LTS) or a later version is required
36 |
37 | ### Install via package manager
38 | - Using the UI
39 | 1. Follow the steps provided [here](https://docs.unity3d.com/Manual/upm-ui-giturl.html).
40 | 2. The Git URL to use is `https://github.com/Unity-Technologies/AutoLOD.git`
41 | - Manually through editing manifest.json
42 | 1. Read the instructions from the official documentation [here](https://docs.unity3d.com/Manual/upm-git.html).
43 | 2. Open up *manifest.json* inside the *Packages* directory in your Unity project using a text editor.
44 | 3. Under the dependencies section of this file, you should add the following line at the top:
45 | ```"com.unity.autolod": "https://github.com/Unity-Technologies/AutoLOD.git",```
46 | 1. You should now see something like this:
47 | ```json
48 | {
49 | "dependencies": {
50 | "com.unity.autolod": "https://github.com/Unity-Technologies/AutoLOD.git",
51 | "com.unity.ads": "2.0.8",
52 | "com.unity.analytics": "3.2.3",
53 | "com.unity.collab-proxy": "1.2.15",
54 | "...": "...",
55 | }
56 | }
57 | ```
58 |
59 |
60 | ### Cloning locally to your project (requires [git-lfs](https://git-lfs.github.com/))
61 | 1. Create a new Unity project or use an existing one
62 | 2. From the command line change directory to your project's `Packages` directory.
63 | 3. Run `git lfs clone https://github.com/Unity-Technologies/AutoLOD`
64 |
65 | ### Project Settings
66 | If you plan on making changes to AutoLOD and/or contributing back, then you'll need to set the `Asset Serialization` property under Edit->Project Settings->Editor to `Force Text`.
67 |
68 | ## License
69 | Unity Companion License (see [LICENSE](LICENSE))
70 |
71 | ## All contributions are subject to the [Unity Contribution Agreement (UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement)
72 | By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions.
73 |
74 | ## Initial Contributors
75 | [Amir Ebrahimi](https://github.com/amirebrahimi/)
76 |
[Elliot Cuzzillo](https://github.com/ecuzzillo)
77 |
[Yuangguang Liao](https://github.com/liaoyg)
78 |
79 | ## Community Contributors
80 | [@Camarent](https://github.com/Camarent), [@LoneDev6](https://github.com/LoneDev6), [@marwie](https://github.com/marwie), [@msellens](https://github.com/msellens), [@redwyre](https://github.com/redwyre)
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/QuadricMeshSimplifier.cs:
--------------------------------------------------------------------------------
1 | #if ENABLE_UNITYMESHSIMPLIFIER
2 | using System;
3 | using UnityEditor;
4 | using UnityEngine;
5 | using UnityMeshSimplifier;
6 | using Mesh = Unity.AutoLOD.WorkingMesh;
7 | #endif
8 |
9 | #if ENABLE_UNITYMESHSIMPLIFIER
10 | namespace Unity.AutoLOD
11 | {
12 | public struct QuadricMeshSimplifier : IMeshSimplifier, IPreferences
13 | {
14 | [InitializeOnLoad]
15 | class Preferences : ScriptableSingleton
16 | {
17 | const string k_Options = "AutoLOD.QuadricMeshSimplifier.Options";
18 |
19 | public static SimplificationOptions Options;
20 |
21 | // Needed for SerializedObject/SerializedProperty
22 | [SerializeField]
23 | internal SimplificationOptions m_Options;
24 |
25 | // Load the options statically, so they can be used in jobs
26 | static Preferences()
27 | {
28 | EditorApplication.delayCall += () =>
29 | {
30 | var preferences = Preferences.instance;
31 | Debug.Assert(!preferences.Equals(default), "QuadricMeshSimplifier preferences should never be the default struct");
32 | };
33 | }
34 |
35 | void OnEnable()
36 | {
37 | // It's okay that it doesn't save, but don't hide it otherwise the inspector GUI won't work
38 | hideFlags = HideFlags.DontSave;
39 |
40 | var savedPrefs = EditorPrefs.GetString(k_Options, null);
41 | if (string.IsNullOrEmpty(savedPrefs))
42 | m_Options = SimplificationOptions.Default;
43 | else
44 | m_Options = JsonUtility.FromJson(savedPrefs);
45 |
46 | // Update the static version
47 | Options = m_Options;
48 | }
49 |
50 | public void ResetToDefaults()
51 | {
52 | m_Options = SimplificationOptions.Default;
53 | Save();
54 | }
55 |
56 | public void Save()
57 | {
58 | var savedPrefs = JsonUtility.ToJson(m_Options);
59 | EditorPrefs.SetString(k_Options, savedPrefs);
60 | Options = m_Options;
61 | }
62 | }
63 |
64 | SerializedObject m_SerializedObject;
65 |
66 | public void Simplify(Mesh inputMesh, Mesh outputMesh, float quality)
67 | {
68 | var meshSimplifier = new MeshSimplifier();
69 | meshSimplifier.SimplificationOptions = Preferences.Options;
70 | meshSimplifier.Vertices = inputMesh.vertices;
71 | meshSimplifier.Normals = inputMesh.normals;
72 | meshSimplifier.Tangents = inputMesh.tangents;
73 | meshSimplifier.UV1 = inputMesh.uv;
74 | meshSimplifier.UV2 = inputMesh.uv2;
75 | meshSimplifier.UV3 = inputMesh.uv3;
76 | meshSimplifier.UV4 = inputMesh.uv4;
77 | meshSimplifier.Colors = inputMesh.colors;
78 |
79 | var triangles = new int[inputMesh.subMeshCount][];
80 | for (var submesh = 0; submesh < inputMesh.subMeshCount; submesh++)
81 | {
82 | triangles[submesh] = inputMesh.GetTriangles(submesh);
83 | }
84 | meshSimplifier.AddSubMeshTriangles(triangles);
85 |
86 | meshSimplifier.SimplifyMesh(quality);
87 |
88 | outputMesh.vertices = meshSimplifier.Vertices;
89 | outputMesh.normals = meshSimplifier.Normals;
90 | outputMesh.tangents = meshSimplifier.Tangents;
91 | outputMesh.uv = meshSimplifier.UV1;
92 | outputMesh.uv2 = meshSimplifier.UV2;
93 | outputMesh.uv3 = meshSimplifier.UV3;
94 | outputMesh.uv4 = meshSimplifier.UV4;
95 | outputMesh.colors = meshSimplifier.Colors;
96 | outputMesh.subMeshCount = meshSimplifier.SubMeshCount;
97 | for (var submesh = 0; submesh < outputMesh.subMeshCount; submesh++)
98 | {
99 | outputMesh.SetTriangles(meshSimplifier.GetSubMeshTriangles(submesh), submesh);
100 | }
101 | }
102 |
103 | public void OnPreferencesGUI()
104 | {
105 | var preferences = Preferences.instance;
106 | if (m_SerializedObject == null)
107 | m_SerializedObject = new SerializedObject(preferences);
108 |
109 | m_SerializedObject.Update();
110 | var property = m_SerializedObject.FindProperty("m_Options");
111 |
112 | EditorGUI.BeginChangeCheck();
113 |
114 | EditorGUILayout.BeginHorizontal();
115 | EditorGUILayout.PropertyField(property);
116 | if (property.isExpanded)
117 | {
118 | GUI.enabled = !preferences.m_Options.Equals(SimplificationOptions.Default);
119 | if (GUILayout.Button("Reset", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
120 | {
121 | preferences.ResetToDefaults();
122 | EditorGUIUtility.ExitGUI();
123 | }
124 | GUI.enabled = true;
125 | }
126 | EditorGUILayout.EndHorizontal();
127 | if (property.isExpanded)
128 | {
129 | EditorGUI.indentLevel++;
130 | property.NextVisible(true);
131 | do
132 | {
133 | EditorGUILayout.BeginHorizontal();
134 | EditorGUILayout.LabelField(property.name, GUILayout.Width(200f));
135 | EditorGUILayout.PropertyField(property, GUIContent.none, true, GUILayout.ExpandWidth(false));
136 | EditorGUILayout.EndHorizontal();
137 | } while (property.NextVisible(false));
138 | EditorGUI.indentLevel--;
139 | }
140 |
141 | if (EditorGUI.EndChangeCheck())
142 | {
143 | m_SerializedObject.ApplyModifiedProperties();
144 | preferences.Save();
145 | }
146 | }
147 | }
148 | }
149 | #endif
--------------------------------------------------------------------------------
/Editor/Batchers/SimpleBatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using UnityEditor;
6 | using UnityEngine;
7 | using UnityEngine.Rendering;
8 |
9 | namespace Unity.AutoLOD
10 | {
11 | ///
12 | /// A simple batcher that combines textures into an atlas and meshes (non material-preserving)
13 | ///
14 | class SimpleBatcher : IBatcher
15 | {
16 | Texture2D whiteTexture
17 | {
18 | get
19 | {
20 | if (!m_WhiteTexture)
21 | {
22 | var path = "Assets/AutoLOD/Generated/Atlases/white.asset";
23 | m_WhiteTexture = AssetDatabase.LoadAssetAtPath(path);
24 | if (!m_WhiteTexture)
25 | {
26 | m_WhiteTexture = Object.Instantiate(Texture2D.whiteTexture);
27 | var directory = Path.GetDirectoryName(path);
28 | if (!Directory.Exists(directory))
29 | Directory.CreateDirectory(directory);
30 |
31 | AssetDatabase.CreateAsset(m_WhiteTexture, path);
32 | }
33 | }
34 |
35 | return m_WhiteTexture;
36 | }
37 | }
38 |
39 | Texture2D m_WhiteTexture;
40 |
41 | public IEnumerator Batch(GameObject go)
42 | {
43 | var renderers = go.GetComponentsInChildren();
44 | var materials = new HashSet(renderers.SelectMany(r => r.sharedMaterials));
45 |
46 | var textures = new HashSet(materials.Select(m =>
47 | {
48 | if (m)
49 | return m.mainTexture as Texture2D;
50 |
51 | return null;
52 | }).Where(t => t != null)).ToList();
53 | textures.Add(whiteTexture);
54 |
55 | TextureAtlas atlas = null;
56 | yield return TextureAtlasModule.instance.GetTextureAtlas(textures.ToArray(), a => atlas = a);
57 |
58 | var atlasLookup = new Dictionary();
59 | var atlasTextures = atlas.textures;
60 | for (int i = 0; i < atlasTextures.Length; i++)
61 | {
62 | atlasLookup[atlasTextures[i]] = atlas.uvs[i];
63 | }
64 |
65 | MeshFilter[] meshFilters = go.GetComponentsInChildren();
66 | var combine = new List();
67 | for (int i = 0; i < meshFilters.Length; i++)
68 | {
69 | var mf = meshFilters[i];
70 | var sharedMesh = mf.sharedMesh;
71 |
72 | if (!sharedMesh)
73 | continue;
74 |
75 | if (!sharedMesh.isReadable)
76 | {
77 | var assetPath = AssetDatabase.GetAssetPath(sharedMesh);
78 | if (!string.IsNullOrEmpty(assetPath))
79 | {
80 | var importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
81 | if (importer)
82 | {
83 | importer.isReadable = true;
84 | importer.SaveAndReimport();
85 | }
86 | }
87 | }
88 |
89 | var ci = new CombineInstance();
90 |
91 | var mesh = Object.Instantiate(sharedMesh);
92 |
93 | var mr = mf.GetComponent();
94 | var sharedMaterials = mr.sharedMaterials;
95 | var uv = mesh.uv;
96 | var colors = mesh.colors;
97 | if (colors == null || colors.Length == 0)
98 | colors = new Color[uv.Length];
99 | var updated = new bool[uv.Length];
100 | var triangles = new List();
101 |
102 | // Some meshes have submeshes that either aren't expected to render or are missing a material, so go ahead and skip
103 | var subMeshCount = Mathf.Min(mesh.subMeshCount, sharedMaterials.Length);
104 | for (int j = 0; j < subMeshCount; j++)
105 | {
106 | var sharedMaterial = sharedMaterials[Mathf.Min(j, sharedMaterials.Length - 1)];
107 | var mainTexture = whiteTexture;
108 | var materialColor = Color.white;
109 |
110 | if (sharedMaterial)
111 | {
112 | var texture = sharedMaterial.mainTexture as Texture2D;
113 | if (texture)
114 | mainTexture = texture;
115 |
116 | if (sharedMaterial.HasProperty("_Color"))
117 | materialColor = sharedMaterial.color;
118 | }
119 |
120 | if (mesh.GetTopology(j) != MeshTopology.Triangles)
121 | {
122 | Debug.LogWarning("Mesh must have triangles", mf);
123 | continue;
124 | }
125 |
126 | triangles.Clear();
127 | mesh.GetTriangles(triangles, j);
128 | var uvOffset = atlasLookup[mainTexture];
129 | foreach (var t in triangles)
130 | {
131 | if (!updated[t])
132 | {
133 | var uvCoord = uv[t];
134 | if (mainTexture == whiteTexture)
135 | {
136 | // Sample at center of white texture to avoid sampling edge colors incorrectly
137 | uvCoord.x = 0.5f;
138 | uvCoord.y = 0.5f;
139 | }
140 |
141 | uvCoord.x = Mathf.Lerp(uvOffset.xMin, uvOffset.xMax, uvCoord.x);
142 | uvCoord.y = Mathf.Lerp(uvOffset.yMin, uvOffset.yMax, uvCoord.y);
143 | uv[t] = uvCoord;
144 |
145 | if (mainTexture == whiteTexture)
146 | colors[t] = materialColor;
147 | else
148 | colors[t] = Color.white;
149 |
150 | updated[t] = true;
151 | }
152 | }
153 |
154 | yield return null;
155 | }
156 | mesh.uv = uv;
157 | mesh.uv2 = null;
158 | mesh.colors = colors;
159 |
160 | ci.mesh = mesh;
161 | ci.transform = mf.transform.localToWorldMatrix;
162 | combine.Add(ci);
163 |
164 | mf.gameObject.SetActive(false);
165 |
166 | yield return null;
167 | }
168 |
169 | var combinedMesh = new Mesh();
170 | combinedMesh.indexFormat = IndexFormat.UInt32;
171 | combinedMesh.CombineMeshes(combine.ToArray(), true, true);
172 | combinedMesh.RecalculateBounds();
173 | var meshFilter = go.AddComponent();
174 | meshFilter.sharedMesh = combinedMesh;
175 |
176 | for (int i = 0; i < meshFilters.Length; i++)
177 | {
178 | Object.DestroyImmediate(meshFilters[i].gameObject);
179 | }
180 |
181 | var meshRenderer = go.AddComponent();
182 | var material = new Material(Shader.Find("Custom/AutoLOD/SimpleBatcher"));
183 | material.mainTexture = atlas.textureAtlas;
184 | meshRenderer.sharedMaterial = material;
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/Runtime/Helpers/MonoBehaviourHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using Unity.AutoLOD.Utilities;
6 | using UnityEditor;
7 | using UnityEngine;
8 |
9 | namespace Unity.AutoLOD
10 | {
11 | // Useful for launching co-routines in the Editor or executing something on the main thread
12 | #if UNITY_EDITOR
13 | [InitializeOnLoad]
14 | #else
15 | public class ScriptableSingleton : ScriptableObject where T : ScriptableObject
16 | {
17 | static T s_Instance;
18 |
19 | public ScriptableSingleton()
20 | {
21 | if (s_Instance != null)
22 | Debug.LogError((object) "ScriptableSingleton already exists. Did you query the singleton in a constructor?");
23 | else
24 | s_Instance = this as T;
25 | }
26 |
27 | public static T instance
28 | {
29 | get
30 | {
31 | if (s_Instance == null)
32 | CreateInstance().hideFlags = HideFlags.HideAndDontSave;
33 |
34 | return s_Instance;
35 | }
36 | }
37 | }
38 | #endif
39 |
40 | public class MonoBehaviourHelper : ScriptableSingleton
41 | {
42 | partial class Surrogate : MonoBehaviour { }
43 |
44 | Surrogate m_MonoBehaviour;
45 |
46 | MonoBehaviour monoBehaviour
47 | {
48 | get
49 | {
50 | if (!m_MonoBehaviour)
51 | {
52 | #if UNITY_EDITOR
53 | var go = EditorUtility.CreateGameObjectWithHideFlags("MonoBehaviourHelper", HideFlags.DontSave, typeof(Surrogate));
54 | #else
55 | var go = new GameObject("MonoBehaviourHelper", typeof(Surrogate));
56 | #endif
57 | m_MonoBehaviour = go.GetComponent();
58 | #if UNITY_EDITOR
59 | m_MonoBehaviour.runInEditMode = true;
60 | #endif
61 | }
62 |
63 | return m_MonoBehaviour;
64 | }
65 | }
66 |
67 | public static float? maxSharedExecutionTimeMS { get; set; }
68 |
69 | List m_Coroutines = new List();
70 | readonly Queue m_MainThreadActionQueue = new Queue();
71 |
72 | static int s_MainThreadId;
73 |
74 | public static Coroutine StartCoroutine(IEnumerator routine, float? maxIterationTimeMS = null)
75 | {
76 | return instance.monoBehaviour.StartCoroutine(CoroutineWrapper(routine, maxIterationTimeMS));
77 | }
78 |
79 | static IEnumerator CoroutineWrapper(IEnumerator routine, float? maxIterationTimeMS = null)
80 | {
81 | if (maxSharedExecutionTimeMS.HasValue)
82 | {
83 | if (maxIterationTimeMS.HasValue)
84 | Debug.Log("Overriding specified iteration time because there is a shared time set.");
85 |
86 | maxIterationTimeMS = maxSharedExecutionTimeMS / (instance.m_Coroutines.Count + 1);
87 | }
88 |
89 | var timedEnumerator = new TimedEnumerator(routine, maxIterationTimeMS);
90 |
91 | //timedEnumerator.logEnabled = true;
92 | instance.m_Coroutines.Add(timedEnumerator);
93 | yield return timedEnumerator;
94 | instance.m_Coroutines.Remove(timedEnumerator);
95 | }
96 |
97 | static IEnumerator ActionCoroutine(Action action)
98 | {
99 | action();
100 | yield break;
101 | }
102 |
103 | // Simplified version of https://github.com/PimDeWitte/UnityMainThreadDispatcher
104 | public static void ExecuteOnMainThread(Action action)
105 | {
106 | if (IsMainThread())
107 | {
108 | if (instance)
109 | action();
110 | }
111 | else
112 | {
113 | var completed = false;
114 | Action completionWrapper = () =>
115 | {
116 | action();
117 | completed = true;
118 | };
119 |
120 | var actionQueue = instance.m_MainThreadActionQueue;
121 | lock (actionQueue)
122 | actionQueue.Enqueue(completionWrapper);
123 |
124 | while (!completed)
125 | Thread.Sleep(100);
126 | }
127 | }
128 |
129 | public static bool IsMainThread()
130 | {
131 | return Thread.CurrentThread.ManagedThreadId == s_MainThreadId;
132 | }
133 |
134 | static MonoBehaviourHelper()
135 | {
136 | s_MainThreadId = Thread.CurrentThread.ManagedThreadId;
137 | }
138 |
139 | void OnEnable()
140 | {
141 | #if UNITY_EDITOR
142 | EditorApplication.update += EditorUpdate;
143 | #endif
144 | }
145 |
146 | void OnDisable()
147 | {
148 | #if UNITY_EDITOR
149 | EditorApplication.update -= EditorUpdate;
150 | #endif
151 |
152 | if (m_MonoBehaviour)
153 | DestroyImmediate(m_MonoBehaviour.gameObject);
154 | }
155 |
156 | #if !UNITY_EDITOR
157 | void Update()
158 | {
159 | EditorUpdate();
160 | }
161 | #endif
162 |
163 | static void EditorUpdate()
164 | {
165 | var actionQueue = instance.m_MainThreadActionQueue;
166 | lock (actionQueue)
167 | {
168 | while (actionQueue.Count > 0)
169 | StartCoroutine(ActionCoroutine(actionQueue.Dequeue()));
170 | }
171 |
172 | var coroutines = instance.m_Coroutines;
173 | if (coroutines.Count > 0)
174 | {
175 | if (maxSharedExecutionTimeMS.HasValue)
176 | {
177 | var timeShare = maxSharedExecutionTimeMS / coroutines.Count;
178 | foreach (var co in coroutines)
179 | {
180 | co.maxIterationTimeMS = timeShare;
181 | }
182 | }
183 |
184 | #if UNITY_EDITOR
185 | // this is necessary to cause our GameObject co-routines to tick at a stable frequency
186 | EditorApplication.QueuePlayerLoopUpdate();
187 | #endif
188 | }
189 | }
190 |
191 | partial class Surrogate
192 | {
193 | //
194 | // Coroutine tests
195 | //
196 | [ContextMenu("CoTest")]
197 | void CoTest()
198 | {
199 | //StartCoroutine(TestRoutine());
200 | StartCoroutine(PrintNumbersSlowly());
201 | }
202 |
203 | [ContextMenu("MaxTimeTest")]
204 | void MaxTimeTest()
205 | {
206 | maxSharedExecutionTimeMS = 1f;
207 |
208 | //((MonoBehaviour)instance).StartCoroutine(ContinuallyFindObjects());
209 | //StartCoroutine(ContinuallyFindObjects());
210 | //StartCoroutine(ContinuallyFindObjects(), 1f);
211 | //StartCoroutine(ContinuallyFindObjects(), 1f);
212 |
213 | MonoBehaviourHelper.StartCoroutine(BurnCycles(), 1f);
214 | MonoBehaviourHelper.StartCoroutine(BurnCycles(), 1f);
215 |
216 | //((MonoBehaviour)instance).StartCoroutine(ContinuallyFindObjects());
217 | }
218 |
219 | IEnumerator ContinuallyFindObjects()
220 | {
221 | List renderers = new List();
222 |
223 | var counter = 0;
224 | while (true)
225 | {
226 | counter++;
227 | renderers.Clear();
228 |
229 | yield return ObjectUtils.FindObjectsOfType(renderers);
230 |
231 | Debug.Log("Renderers: " + renderers.Count);
232 |
233 | if (counter > 1000)
234 | break;
235 | }
236 | }
237 |
238 | IEnumerator BurnCycles()
239 | {
240 | var counter = 0;
241 | while (true)
242 | {
243 | counter++;
244 | yield return null;
245 |
246 | if (counter > 10000)
247 | break;
248 | }
249 | }
250 |
251 | IEnumerator TestRoutine()
252 | {
253 | yield return StartCoroutine(PrintNumbersSlowly());
254 | }
255 |
256 | IEnumerator PrintNumbersSlowly()
257 | {
258 | for (int i = 0; i < 10; i++)
259 | {
260 | Debug.Log(i);
261 | yield return new WaitForSecondsRealtime(1f);
262 | }
263 | }
264 | }
265 | }
266 | }
--------------------------------------------------------------------------------
/Editor/MeshSimplifiers/Simplygon/SimplygonMeshSimplifier.cs:
--------------------------------------------------------------------------------
1 | #if ENABLE_SIMPLYGON
2 | using Simplygon;
3 | using Simplygon.Unity.EditorPlugin;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using UnityEditor;
8 | using UnityEngine;
9 | using Unity.Formats.USD;
10 | using USD.NET;
11 | using USD.NET.Unity;
12 |
13 | namespace Unity.AutoLOD
14 | {
15 | public struct SimplygonMeshSimplifier : IMeshSimplifier
16 | {
17 | static pxr.TfToken s_MaterialBindToken = new pxr.TfToken("materialBind");
18 | static pxr.TfToken s_SubMeshesToken = new pxr.TfToken("subMeshes");
19 |
20 | static object s_ExecutionLock = new object();
21 | static ISimplygon s_Simplygon;
22 | static int s_ReferenceCount;
23 |
24 | public void Simplify(WorkingMesh inputMesh, WorkingMesh outputMesh, float quality)
25 | {
26 | // We can only have one instance of Simplygon, but we can use it from multiple threads once initialized
27 | lock (s_ExecutionLock)
28 | {
29 | if (s_Simplygon == null)
30 | {
31 | s_Simplygon = Loader.InitSimplygon(out var simplygonErrorCode, out var simplygonErrorMessage);
32 | if (s_Simplygon == null && simplygonErrorCode != EErrorCodes.NoError)
33 | Debug.Log($"Initializing failed! {simplygonErrorCode}: {simplygonErrorMessage}");
34 | }
35 |
36 | s_ReferenceCount++;
37 | }
38 |
39 | var simplygon = s_Simplygon;
40 | if (simplygon != null)
41 | {
42 | string exportTempDirectory = SimplygonUtils.GetNewTempDirectory();
43 |
44 | using (spScene sgScene = ExportSimplygonScene(simplygon, exportTempDirectory, inputMesh))
45 | {
46 | using (spReductionPipeline reductionPipeline = simplygon.CreateReductionPipeline())
47 | using (spReductionSettings reductionSettings = reductionPipeline.GetReductionSettings())
48 | {
49 | reductionSettings.SetReductionTargets(EStopCondition.All, true, false, false, false);
50 | reductionSettings.SetReductionTargetTriangleRatio(quality);
51 |
52 | reductionPipeline.RunScene(sgScene, EPipelineRunMode.RunInThisProcess);
53 |
54 | MonoBehaviourHelper.ExecuteOnMainThread(() =>
55 | {
56 | string baseFolder = "Assets/SimplygonTemp";
57 | if (!AssetDatabase.IsValidFolder(baseFolder))
58 | AssetDatabase.CreateFolder("Assets", "SimplygonTemp");
59 |
60 | string meshName = inputMesh.name;
61 | string assetFolderGuid = AssetDatabase.CreateFolder(baseFolder, meshName);
62 | string assetFolderPath = AssetDatabase.GUIDToAssetPath(assetFolderGuid);
63 |
64 | int startingLodIndex = 0;
65 | List importedGameObjects = new List();
66 | SimplygonImporter.Import(simplygon, reductionPipeline, ref startingLodIndex,
67 | assetFolderPath, meshName, importedGameObjects);
68 |
69 | Debug.Assert(importedGameObjects.Count == 1, "AutoLOD: There should only be one imported mesh.");
70 | if (importedGameObjects.Count == 1)
71 | {
72 | GameObject go = importedGameObjects[0];
73 | MeshFilter mf = go.GetComponentInChildren();
74 | mf.sharedMesh.ApplyToWorkingMesh(ref outputMesh);
75 | }
76 |
77 | foreach (var go in importedGameObjects)
78 | {
79 | GameObject.DestroyImmediate(go);
80 | }
81 |
82 | AssetDatabase.DeleteAsset(baseFolder);
83 | });
84 | }
85 | }
86 | }
87 |
88 | lock (s_ExecutionLock)
89 | {
90 | s_ReferenceCount--;
91 |
92 | // Clean up on our way out if we are the last thread using the Simplygon singleton
93 | if (s_Simplygon != null && s_ReferenceCount == 0)
94 | {
95 | s_Simplygon.Dispose();
96 | s_Simplygon = null;
97 | }
98 | }
99 | }
100 |
101 | static spScene ExportSimplygonScene(ISimplygon simplygon, string tempDirectory, WorkingMesh mesh)
102 | {
103 | if (string.IsNullOrEmpty(tempDirectory))
104 | return (spScene)null;
105 |
106 | string filePath = Path.Combine(tempDirectory, "export.usd");
107 | InitUsd.Initialize();
108 | Scene scene = Scene.Create(filePath);
109 |
110 | var context = new ExportContext();
111 | context.scene = scene;
112 | context.basisTransform = BasisTransformation.SlowAndSafe;
113 | // context.exportRoot = root.transform.parent;
114 |
115 | ExportMesh(context, mesh);
116 |
117 | scene.Save();
118 | scene.Close();
119 |
120 | using (spSceneImporter sceneImporter = simplygon.CreateSceneImporter())
121 | {
122 | sceneImporter.SetImportFilePath(filePath);
123 | sceneImporter.RunImport();
124 | // SimplygonExporter.ExportSelectionSetsInSelection(simplygon, sceneImporter.GetScene(), selectedGameObjects, rootName);
125 | return sceneImporter.GetScene();
126 | }
127 | }
128 |
129 | static void ExportMesh(ExportContext exportContext, WorkingMesh mesh)
130 | {
131 | // path = /build_bighouse_02/build_bighouse_01_dragonhead_01_LOD0
132 | var path = new pxr.SdfPath($"/{mesh.name}");
133 |
134 | var scene = exportContext.scene;
135 | bool slowAndSafeConversion = exportContext.basisTransform == BasisTransformation.SlowAndSafe;
136 | var sample = new MeshSample();
137 |
138 | if (mesh.bounds.center == Vector3.zero && mesh.bounds.extents == Vector3.zero)
139 | {
140 | mesh.RecalculateBounds();
141 | }
142 |
143 | sample.extent = mesh.bounds;
144 |
145 | if (slowAndSafeConversion)
146 | {
147 | // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of
148 | // basis is required. There are shortcuts, but this is fully general.
149 | sample.ConvertTransform();
150 | sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center);
151 | }
152 |
153 | // TODO: Technically a mesh could be the root transform, which is not handled correctly here.
154 | // It should have the same logic for root prims as in ExportXform.
155 | // sample.transform = XformExporter.GetLocalTransformMatrix(
156 | // null,
157 | // scene.UpAxis == Scene.UpAxes.Z,
158 | // new pxr.SdfPath(path).IsRootPrimPath(),
159 | // exportContext.basisTransform);
160 |
161 | sample.normals = mesh.normals;
162 | sample.points = mesh.vertices;
163 | sample.tangents = mesh.tangents;
164 |
165 | sample.colors = mesh.colors;
166 | if (sample.colors != null && sample.colors.Length == 0)
167 | {
168 | sample.colors = null;
169 | }
170 |
171 | // Gah. There is no way to inspect a meshes UVs.
172 | sample.st = mesh.uv;
173 |
174 | // Set face vertex counts and indices.
175 | var tris = mesh.triangles;
176 |
177 | if (slowAndSafeConversion)
178 | {
179 | // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change
180 | // of basis is required. There are shortcuts, but this is fully general.
181 |
182 | for (int i = 0; i < sample.points.Length; i++)
183 | {
184 | sample.points[i] = UnityTypeConverter.ChangeBasis(sample.points[i]);
185 | if (sample.normals != null && sample.normals.Length == sample.points.Length)
186 | {
187 | sample.normals[i] = UnityTypeConverter.ChangeBasis(sample.normals[i]);
188 | }
189 |
190 | if (sample.tangents != null && sample.tangents.Length == sample.points.Length)
191 | {
192 | var w = sample.tangents[i].w;
193 | var t = UnityTypeConverter.ChangeBasis(sample.tangents[i]);
194 | sample.tangents[i] = new Vector4(t.x, t.y, t.z, w);
195 | }
196 | }
197 |
198 | for (int i = 0; i < tris.Length; i += 3)
199 | {
200 | var t = tris[i];
201 | tris[i] = tris[i + 1];
202 | tris[i + 1] = t;
203 | }
204 |
205 | sample.SetTriangles(tris);
206 |
207 | scene.Write(path, sample);
208 |
209 | // TODO: this is a bit of a half-measure, we need real support for primvar interpolation.
210 | // Set interpolation based on color count.
211 | if (sample.colors != null && sample.colors.Length == 1)
212 | {
213 | pxr.UsdPrim usdPrim = scene.GetPrimAtPath(path);
214 | var colorPrimvar =
215 | new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayColor));
216 | colorPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant);
217 | var opacityPrimvar =
218 | new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayOpacity));
219 | opacityPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant);
220 | }
221 |
222 | // In USD subMeshes are represented as UsdGeomSubsets.
223 | // When there are multiple subMeshes, convert them into UsdGeomSubsets.
224 | if (mesh.subMeshCount > 1)
225 | {
226 | // Build a table of face indices, used to convert the subMesh triangles to face indices.
227 | var faceTable = new Dictionary();
228 | for (int i = 0; i < tris.Length; i += 3)
229 | {
230 | if (!slowAndSafeConversion)
231 | {
232 | faceTable.Add(new Vector3(tris[i], tris[i + 1], tris[i + 2]), i / 3);
233 | }
234 | else
235 | {
236 | // Under slow and safe export, index 0 and 1 are swapped.
237 | // This swap will not be present in the subMesh indices, so must be undone here.
238 | faceTable.Add(new Vector3(tris[i + 1], tris[i], tris[i + 2]), i / 3);
239 | }
240 | }
241 |
242 | var usdPrim = scene.GetPrimAtPath(path);
243 | var usdGeomMesh = new pxr.UsdGeomMesh(usdPrim);
244 |
245 | // Process each subMesh and create a UsdGeomSubset of faces this subMesh targets.
246 | for (int si = 0; si < mesh.subMeshCount; si++)
247 | {
248 | int[] indices = mesh.GetTriangles(si);
249 | int[] faceIndices = new int[indices.Length / 3];
250 |
251 | for (int i = 0; i < indices.Length; i += 3)
252 | {
253 | faceIndices[i / 3] = faceTable[new Vector3(indices[i], indices[i + 1], indices[i + 2])];
254 | }
255 |
256 | var vtIndices = UnityTypeConverter.ToVtArray(faceIndices);
257 | var subset = pxr.UsdGeomSubset.CreateUniqueGeomSubset(
258 | usdGeomMesh, // The object of which this subset belongs.
259 | s_SubMeshesToken, // An arbitrary name for the subset.
260 | pxr.UsdGeomTokens.face, // Indicator that these represent face indices
261 | vtIndices, // The actual face indices.
262 | s_MaterialBindToken // familyName = "materialBind"
263 | );
264 | }
265 | }
266 | }
267 | }
268 | }
269 | }
270 | #endif
--------------------------------------------------------------------------------
/Runtime/Utilities/ObjectUtils.cs:
--------------------------------------------------------------------------------
1 | // From: https://github.com/Unity-Technologies/EditorVR
2 |
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Reflection;
8 | using UnityEditor;
9 | using UnityEngine;
10 | using UnityEngine.SceneManagement;
11 | using UnityObject = UnityEngine.Object;
12 |
13 | namespace Unity.AutoLOD.Utilities
14 | {
15 | ///
16 | /// Object related EditorVR utilities
17 | ///
18 | public static class ObjectUtils
19 | {
20 | public static HideFlags hideFlags
21 | {
22 | get { return s_HideFlags; }
23 | set { s_HideFlags = value; }
24 | }
25 |
26 | static HideFlags s_HideFlags = HideFlags.DontSave;
27 | static List s_RootGameObjects = new List();
28 |
29 | public static GameObject Instantiate(GameObject prefab, Transform parent = null, bool worldPositionStays = true, bool runInEditMode = true, bool active = true)
30 | {
31 | var go = UnityObject.Instantiate(prefab, parent, worldPositionStays);
32 | if (worldPositionStays)
33 | {
34 | var goTransform = go.transform;
35 | var prefabTransform = prefab.transform;
36 | goTransform.position = prefabTransform.position;
37 | goTransform.rotation = prefabTransform.rotation;
38 | }
39 |
40 | go.SetActive(active);
41 | if (!Application.isPlaying && runInEditMode)
42 | {
43 | SetRunInEditModeRecursively(go, runInEditMode);
44 | go.hideFlags = hideFlags;
45 | }
46 |
47 | return go;
48 | }
49 |
50 | public static void RemoveAllChildren(GameObject obj)
51 | {
52 | var children = new List();
53 | foreach (Transform child in obj.transform)
54 | children.Add(child.gameObject);
55 |
56 | foreach (var child in children)
57 | UnityObject.Destroy(child);
58 | }
59 |
60 | public static bool IsInLayer(GameObject o, string s)
61 | {
62 | return o.layer == LayerMask.NameToLayer(s);
63 | }
64 |
65 | ///
66 | /// Create an empty VR GameObject.
67 | ///
68 | /// Name of the new GameObject
69 | /// Transform to parent new object under
70 | /// The newly created empty GameObject
71 | public static GameObject CreateEmptyGameObject(string name = null, Transform parent = null)
72 | {
73 | GameObject empty = null;
74 | if (string.IsNullOrEmpty(name))
75 | name = "New Game Object";
76 |
77 | #if UNITY_EDITOR
78 | empty = EditorUtility.CreateGameObjectWithHideFlags(name, hideFlags);
79 | #else
80 | empty = new GameObject(name);
81 | empty.hideFlags = hideFlags;
82 | #endif
83 | empty.transform.parent = parent;
84 | empty.transform.localPosition = Vector3.zero;
85 |
86 | return empty;
87 | }
88 |
89 | public static T CreateGameObjectWithComponent(Transform parent = null, bool worldPositionStays = true) where T : Component
90 | {
91 | return (T)CreateGameObjectWithComponent(typeof(T), parent, worldPositionStays);
92 | }
93 |
94 | public static Component CreateGameObjectWithComponent(Type type, Transform parent = null, bool worldPositionStays = true)
95 | {
96 | #if UNITY_EDITOR
97 | var component = EditorUtility.CreateGameObjectWithHideFlags(type.Name, hideFlags, type).GetComponent(type);
98 | if (!Application.isPlaying)
99 | SetRunInEditModeRecursively(component.gameObject, true);
100 | #else
101 | var component = new GameObject(type.Name).AddComponent(type);
102 | #endif
103 | component.transform.SetParent(parent, worldPositionStays);
104 |
105 | return component;
106 | }
107 |
108 | public static void SetLayerRecursively(GameObject root, int layer)
109 | {
110 | var transforms = root.GetComponentsInChildren();
111 | for (var i = 0; i < transforms.Length; i++)
112 | transforms[i].gameObject.layer = layer;
113 | }
114 |
115 | public static Bounds GetBounds(Transform[] transforms)
116 | {
117 | Bounds? bounds = null;
118 | foreach (var go in transforms)
119 | {
120 | var goBounds = GetBounds(go);
121 | if (!bounds.HasValue)
122 | {
123 | bounds = goBounds;
124 | }
125 | else
126 | {
127 | goBounds.Encapsulate(bounds.Value);
128 | bounds = goBounds;
129 | }
130 | }
131 | return bounds ?? new Bounds();
132 | }
133 |
134 | public static Bounds GetBounds(Transform transform)
135 | {
136 | var b = new Bounds(transform.position, Vector3.zero);
137 | var renderers = transform.GetComponentsInChildren();
138 | for (int i = 0; i < renderers.Length; i++)
139 | {
140 | var r = renderers[i];
141 | if (r.bounds.size != Vector3.zero)
142 | b.Encapsulate(r.bounds);
143 | }
144 |
145 | // As a fallback when there are no bounds, collect all transform positions
146 | if (b.size == Vector3.zero)
147 | {
148 | var transforms = transform.GetComponentsInChildren();
149 | foreach (var t in transforms)
150 | b.Encapsulate(t.position);
151 | }
152 |
153 | return b;
154 | }
155 |
156 | public static IEnumerator GetBounds(List renderers, Action callback)
157 | {
158 | Bounds bounds = new Bounds();
159 | for (int i = 0; i < renderers.Count; i++)
160 | {
161 | var r = renderers[i];
162 | if (i == 0)
163 | bounds = r.bounds;
164 | else
165 | bounds.Encapsulate(r.bounds);
166 |
167 | yield return null;
168 | }
169 |
170 | callback(bounds);
171 | }
172 |
173 | public static void SetRunInEditModeRecursively(GameObject go, bool enabled)
174 | {
175 | #if UNITY_EDITOR
176 | var monoBehaviours = go.GetComponents();
177 | foreach (var mb in monoBehaviours)
178 | {
179 | if (mb)
180 | mb.runInEditMode = enabled;
181 | }
182 |
183 | foreach (Transform child in go.transform)
184 | {
185 | SetRunInEditModeRecursively(child.gameObject, enabled);
186 | }
187 | #endif
188 | }
189 |
190 | public static T AddComponent(GameObject go) where T : Component
191 | {
192 | return (T)AddComponent(typeof(T), go);
193 | }
194 |
195 | public static Component AddComponent(Type type, GameObject go)
196 | {
197 | var component = go.AddComponent(type);
198 | SetRunInEditModeRecursively(go, true);
199 | return component;
200 | }
201 |
202 | static IEnumerable GetAssignableTypes(Type type, Func predicate = null)
203 | {
204 | var list = new List();
205 | ForEachType(t =>
206 | {
207 | if (type.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && (predicate == null || predicate(t))
208 | && t.GetCustomAttribute() == null)
209 | list.Add(t);
210 | });
211 |
212 | return list;
213 | }
214 |
215 | public static void ForEachAssembly(Action callback)
216 | {
217 | var assemblies = AppDomain.CurrentDomain.GetAssemblies();
218 | foreach (var assembly in assemblies)
219 | {
220 | try
221 | {
222 | callback(assembly);
223 | }
224 | catch (ReflectionTypeLoadException)
225 | {
226 | // Skip any assemblies that don't load properly
227 | continue;
228 | }
229 | }
230 | }
231 |
232 | public static void ForEachType(Action callback)
233 | {
234 | ForEachAssembly(assembly =>
235 | {
236 | var types = assembly.GetTypes();
237 | foreach (var t in types)
238 | callback(t);
239 | });
240 | }
241 |
242 | public static IEnumerable GetImplementationsOfInterface(Type type)
243 | {
244 | if (type.IsInterface)
245 | return GetAssignableTypes(type);
246 |
247 | return Enumerable.Empty();
248 | }
249 |
250 | public static IEnumerable GetExtensionsOfClass(Type type)
251 | {
252 | if (type.IsClass)
253 | return GetAssignableTypes(type);
254 |
255 | return Enumerable.Empty();
256 | }
257 |
258 | public static void Destroy(UnityObject o, float t = 0f)
259 | {
260 | if (Application.isPlaying)
261 | {
262 | UnityObject.Destroy(o, t);
263 | }
264 | #if UNITY_EDITOR && UNITY_EDITORVR
265 | else
266 | {
267 | if (Mathf.Approximately(t, 0f))
268 | UnityObject.DestroyImmediate(o);
269 | else
270 | VRView.StartCoroutine(DestroyInSeconds(o, t));
271 | }
272 | #endif
273 | }
274 |
275 | static IEnumerator DestroyInSeconds(UnityObject o, float t)
276 | {
277 | var startTime = Time.realtimeSinceStartup;
278 | while (Time.realtimeSinceStartup <= startTime + t)
279 | yield return null;
280 |
281 | UnityObject.DestroyImmediate(o);
282 | }
283 |
284 | ///
285 | /// Strip "PPtr<> and $ from a string for getting a System.Type from SerializedProperty.type
286 | /// TODO: expose internal SerializedProperty.objectReferenceTypeString to remove this hack
287 | ///
288 | /// Type string
289 | /// Nicified type string
290 | public static string NicifySerializedPropertyType(string type)
291 | {
292 | return type.Replace("PPtr<", "").Replace(">", "").Replace("$", "");
293 | }
294 |
295 | ///
296 | /// Search through all assemblies in the current AppDomain for a class that is assignable to UnityObject and matches the given weak name
297 | /// TODO: expose internal SerialzedProperty.ValidateObjectReferenceValue to remove his hack
298 | ///
299 | /// Weak type name
300 | /// Best guess System.Type
301 | public static Type TypeNameToType(string name)
302 | {
303 | return AppDomain.CurrentDomain.GetAssemblies()
304 | .SelectMany(x => x.GetTypes())
305 | .FirstOrDefault(x => x.Name.Equals(name) && typeof(UnityObject).IsAssignableFrom(x));
306 | }
307 |
308 | #if UNITY_EDITOR
309 | public static IEnumerator GetAssetPreview(UnityObject obj, Action callback)
310 | {
311 | var texture = AssetPreview.GetAssetPreview(obj);
312 |
313 | while (AssetPreview.IsLoadingAssetPreview(obj.GetInstanceID()))
314 | {
315 | texture = AssetPreview.GetAssetPreview(obj);
316 | yield return null;
317 | }
318 |
319 | if (!texture)
320 | texture = AssetPreview.GetMiniThumbnail(obj);
321 |
322 | callback(texture);
323 | }
324 |
325 | public static void CreateAssetFromObjects(UnityObject[] objects, string path)
326 | {
327 | var method = typeof(AssetDatabase).GetMethod("CreateAssetFromObjects", BindingFlags.NonPublic | BindingFlags.Static);
328 | method.Invoke(null, new object[] { objects, path });
329 | }
330 | #endif
331 |
332 | public static IEnumerator FindObjectsOfType(List objects) where T : Component
333 | {
334 | var scene = SceneManager.GetActiveScene();
335 | s_RootGameObjects.Clear();
336 | scene.GetRootGameObjects(s_RootGameObjects);
337 | yield return null;
338 |
339 | foreach (var go in s_RootGameObjects)
340 | {
341 | var children = go.GetComponentsInChildren();
342 | objects.AddRange(children);
343 |
344 | yield return null;
345 | }
346 | }
347 |
348 | public static IEnumerator FindGameObject(string name, Action callback, GameObject root = null)
349 | {
350 | if (root)
351 | {
352 | if (root.name == name)
353 | {
354 | callback(root);
355 | yield break;
356 | }
357 |
358 | foreach (Transform child in root.transform)
359 | {
360 | yield return FindGameObject(name, callback, child.gameObject);
361 | if (!root)
362 | yield break;
363 | }
364 | }
365 | else
366 | {
367 | var scene = SceneManager.GetActiveScene();
368 | s_RootGameObjects.Clear();
369 | scene.GetRootGameObjects(s_RootGameObjects);
370 | foreach (var go in s_RootGameObjects)
371 | {
372 | yield return FindGameObject(name, callback, go);
373 | }
374 | }
375 |
376 | yield return null;
377 | }
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/Editor/ModelImporterLODGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Unity.Collections;
6 | using Unity.Jobs;
7 | using UnityEditor;
8 | using UnityEngine;
9 | using UnityObject = UnityEngine.Object;
10 |
11 | namespace Unity.AutoLOD
12 | {
13 | public class ModelImporterLODGenerator : AssetPostprocessor
14 | {
15 | public static bool saveAssets { set; get; }
16 | public static bool enabled { set; get; }
17 | public static Type meshSimplifierType { set; get; }
18 | public static int maxLOD { set; get; }
19 | public static int initialLODMaxPolyCount { set; get; }
20 |
21 | const HideFlags k_DefaultHideFlags = HideFlags.None;
22 |
23 | static List s_ModelAssetsProcessed = new List();
24 |
25 | public static bool IsEditable(string assetPath)
26 | {
27 | var attributes = File.GetAttributes(assetPath);
28 |
29 | return AssetDatabase.IsOpenForEdit(assetPath, StatusQueryOptions.ForceUpdate)
30 | && (attributes & FileAttributes.ReadOnly) == 0;
31 | }
32 |
33 | void OnPostprocessModel(GameObject go)
34 | {
35 | if (!go.GetComponentInChildren() && meshSimplifierType != null && IsEditable(assetPath))
36 | {
37 | if (go.GetComponentsInChildren().Any())
38 | {
39 | Debug.LogWarning("Automatic LOD generation on skinned meshes is not currently supported");
40 | return;
41 | }
42 |
43 | var originalMeshFilters = go.GetComponentsInChildren();
44 | uint polyCount = 0;
45 | foreach (var mf in originalMeshFilters)
46 | {
47 | var m = mf.sharedMesh;
48 | for (int i = 0; i < m.subMeshCount; i++)
49 | {
50 | var topology = m.GetTopology(i);
51 | var indexCount = m.GetIndexCount(i);
52 |
53 | switch (topology)
54 | {
55 | case MeshTopology.Quads:
56 | indexCount /= 4;
57 | break;
58 |
59 | case MeshTopology.Triangles:
60 | indexCount /= 3;
61 | break;
62 |
63 | case MeshTopology.Lines:
64 | case MeshTopology.LineStrip:
65 | indexCount /= 2;
66 | break;
67 | }
68 |
69 | polyCount += indexCount;
70 | }
71 | }
72 |
73 | var meshLODs = new List();
74 | var preprocessMeshes = new HashSet();
75 |
76 | var lodData = GetLODData(assetPath);
77 | var overrideDefaults = lodData.overrideDefaults;
78 | var importSettings = lodData.importSettings;
79 |
80 | // It's possible to override defaults to either generate on import or to not generate and use specified
81 | // LODs in the override, but in the case where we are not overriding and globally we are not generating
82 | // on import, then there should be no further processing.
83 | if (!overrideDefaults && !enabled)
84 | return;
85 |
86 | if (importSettings.generateOnImport)
87 | {
88 | if (importSettings.maxLODGenerated == 0 && polyCount <= importSettings.initialLODMaxPolyCount)
89 | return;
90 |
91 | var simplifierType = Type.GetType(importSettings.meshSimplifier) ?? meshSimplifierType;
92 |
93 | if (polyCount > importSettings.initialLODMaxPolyCount)
94 | {
95 | foreach (var mf in originalMeshFilters)
96 | {
97 | var inputMesh = mf.sharedMesh;
98 |
99 | var outputMesh = new Mesh();
100 | outputMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
101 | outputMesh.name = inputMesh.name;
102 | outputMesh.bounds = inputMesh.bounds;
103 | mf.sharedMesh = outputMesh;
104 |
105 | var meshLOD = MeshLOD.GetGenericInstance(meshSimplifierType);
106 | meshLOD.InputMesh = inputMesh;
107 | meshLOD.OutputMesh = outputMesh;
108 | meshLOD.Quality = (float)importSettings.initialLODMaxPolyCount / (float)polyCount;
109 | meshLODs.Add(meshLOD);
110 |
111 | preprocessMeshes.Add(outputMesh.GetInstanceID());
112 | }
113 | }
114 |
115 | // Clear out previous LOD data in case the number of LODs has been reduced
116 | for (int i = 0; i <= LODData.MaxLOD; i++)
117 | {
118 | lodData[i] = null;
119 | }
120 |
121 | lodData[0] = originalMeshFilters.Select(mf => mf.GetComponent()).ToArray();
122 |
123 | for (int i = 1; i <= importSettings.maxLODGenerated; i++)
124 | {
125 | var lodMeshes = new List();
126 |
127 | foreach (var mf in originalMeshFilters)
128 | {
129 | var inputMesh = mf.sharedMesh;
130 |
131 | var lodTransform = EditorUtility.CreateGameObjectWithHideFlags(mf.name,
132 | k_DefaultHideFlags, typeof(MeshFilter), typeof(MeshRenderer)).transform;
133 | lodTransform.parent = mf.transform;
134 | lodTransform.localPosition = Vector3.zero;
135 | lodTransform.localRotation = Quaternion.identity;
136 | lodTransform.localScale = new Vector3(1, 1, 1);
137 |
138 | var lodMF = lodTransform.GetComponent();
139 | var lodRenderer = lodTransform.GetComponent();
140 |
141 | AppendLODNameToRenderer(lodRenderer, i);
142 |
143 | var outputMesh = new Mesh();
144 | outputMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
145 | outputMesh.name = string.Format("{0} LOD{1}", inputMesh.name, i);
146 | outputMesh.bounds = inputMesh.bounds;
147 | lodMF.sharedMesh = outputMesh;
148 |
149 | lodMeshes.Add(lodRenderer);
150 |
151 | EditorUtility.CopySerialized(mf.GetComponent(), lodRenderer);
152 |
153 | var meshLOD = MeshLOD.GetGenericInstance(meshSimplifierType);
154 | meshLOD.InputMesh = inputMesh;
155 | meshLOD.OutputMesh = outputMesh;
156 | meshLOD.Quality = Mathf.Pow(0.5f, i);
157 | meshLODs.Add(meshLOD);
158 | }
159 |
160 | lodData[i] = lodMeshes.ToArray();
161 | }
162 |
163 | // Change the name of the original renderers last, so the name change doesn't end up in the clones for other LODs
164 | AppendLODNameToRenderers(go.transform, 0);
165 |
166 | if (meshLODs.Count > 0)
167 | {
168 | if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(lodData)))
169 | {
170 | AssetDatabase.CreateAsset(lodData, GetLODDataPath(assetPath));
171 | }
172 | else
173 | {
174 | var objects = AssetDatabase.LoadAllAssetsAtPath(GetLODDataPath(assetPath));
175 | foreach (var o in objects)
176 | {
177 | var mesh = o as Mesh;
178 | if (mesh)
179 | UnityObject.DestroyImmediate(mesh, true);
180 | }
181 | EditorUtility.SetDirty(lodData);
182 | }
183 | meshLODs.ForEach(ml => AssetDatabase.AddObjectToAsset(ml.OutputMesh, lodData));
184 | if (saveAssets)
185 | AssetDatabase.SaveAssets();
186 |
187 | if (preprocessMeshes.Count > 0)
188 | {
189 | // Process dependencies first
190 | var jobDependencies = new List();
191 | meshLODs.RemoveAll(ml =>
192 | {
193 | if (preprocessMeshes.Contains(ml.OutputMesh.GetInstanceID()))
194 | {
195 | jobDependencies.Add(ml.Generate());
196 | return true;
197 | }
198 |
199 | return false;
200 | });
201 |
202 | // Process remaining meshes
203 | foreach (var ml in meshLODs)
204 | {
205 | MonoBehaviourHelper.StartCoroutine(ml.GenerateAfterDependencies(jobDependencies));
206 | }
207 | }
208 | else
209 | {
210 | foreach (var ml in meshLODs)
211 | {
212 | ml.Generate();
213 | }
214 | }
215 | }
216 | }
217 | else
218 | {
219 | // Don't allow overriding LOD0
220 | lodData[0] = originalMeshFilters.Select(mf =>
221 | {
222 | var r = mf.GetComponent();
223 | AppendLODNameToRenderer(r, 0);
224 | return r;
225 | }).ToArray();
226 |
227 | for (int i = 1; i <= LODData.MaxLOD; i++)
228 | {
229 | var renderers = lodData[i];
230 | for (int j = 0; j < renderers.Length; j++)
231 | {
232 | var r = renderers[j];
233 |
234 | var lodTransform = EditorUtility.CreateGameObjectWithHideFlags(r.name,
235 | k_DefaultHideFlags, typeof(MeshFilter), typeof(MeshRenderer)).transform;
236 | lodTransform.parent = go.transform;
237 | lodTransform.localPosition = Vector3.zero;
238 |
239 | var lodMF = lodTransform.GetComponent();
240 | var lodRenderer = lodTransform.GetComponent();
241 |
242 | EditorUtility.CopySerialized(r.GetComponent(), lodMF);
243 | EditorUtility.CopySerialized(r, lodRenderer);
244 |
245 | AppendLODNameToRenderer(lodRenderer, i);
246 |
247 | renderers[j] = lodRenderer;
248 | }
249 | }
250 | }
251 |
252 | List lods = new List();
253 | var maxLODFound = -1;
254 | for (int i = 0; i <= LODData.MaxLOD; i++)
255 | {
256 | var renderers = lodData[i];
257 | if (renderers == null || renderers.Length == 0)
258 | break;
259 |
260 | maxLODFound++;
261 | }
262 |
263 | var importerRef = new SerializedObject(assetImporter);
264 | var importerLODLevels = importerRef.FindProperty("m_LODScreenPercentages");
265 | for (int i = 0; i <= maxLODFound; i++)
266 | {
267 | var lod = new LOD();
268 | lod.renderers = lodData[i];
269 | var screenPercentage = i == maxLODFound ? 0.01f : Mathf.Pow(0.5f, i + 1);
270 |
271 | // Use the model importer percentages if they exist
272 | if (i < importerLODLevels.arraySize && maxLODFound == importerLODLevels.arraySize)
273 | {
274 | var element = importerLODLevels.GetArrayElementAtIndex(i);
275 | screenPercentage = element.floatValue;
276 | }
277 |
278 | lod.screenRelativeTransitionHeight = screenPercentage;
279 | lods.Add(lod);
280 | }
281 |
282 | if (importerLODLevels.arraySize != 0 && maxLODFound != importerLODLevels.arraySize - 1)
283 | {
284 | Debug.LogWarning("The model has an existing lod group, but it's settings will not be used because " +
285 | "the specified lod count in the AutoLOD settings is different.");
286 | }
287 |
288 | var lodGroup = go.AddComponent();
289 | lodGroup.SetLODs(lods.ToArray());
290 | lodGroup.RecalculateBounds();
291 |
292 | // Keep model importer in sync
293 | importerLODLevels.ClearArray();
294 | for (int i = 0; i < lods.Count; i++)
295 | {
296 | var lod = lods[i];
297 | importerLODLevels.InsertArrayElementAtIndex(i);
298 | var element = importerLODLevels.GetArrayElementAtIndex(i);
299 | element.floatValue = lod.screenRelativeTransitionHeight;
300 | }
301 | importerRef.ApplyModifiedPropertiesWithoutUndo();
302 |
303 | s_ModelAssetsProcessed.Add(assetPath);
304 | }
305 | }
306 |
307 | static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
308 | {
309 | bool assetsImported = false;
310 |
311 | foreach (var asset in importedAssets)
312 | {
313 | if (s_ModelAssetsProcessed.Remove(asset))
314 | {
315 | var go = (GameObject)AssetDatabase.LoadMainAssetAtPath(asset);
316 | var lodData = AssetDatabase.LoadAssetAtPath(GetLODDataPath(asset));
317 |
318 | var lodGroup = go.GetComponentInChildren();
319 | var lods = lodGroup.GetLODs();
320 | for (int i = 0; i < lods.Length; i++)
321 | {
322 | var lod = lods[i];
323 | lodData[i] = lod.renderers;
324 | }
325 |
326 | EditorUtility.SetDirty(lodData);
327 | assetsImported = true;
328 | }
329 | }
330 |
331 | if (assetsImported && saveAssets)
332 | AssetDatabase.SaveAssets();
333 | }
334 |
335 | internal static string GetLODDataPath(string assetPath)
336 | {
337 | var pathPrefix = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(assetPath);
338 | return pathPrefix + "_lods.asset";
339 | }
340 |
341 | internal static LODData GetLODData(string assetPath)
342 | {
343 | var lodData = AssetDatabase.LoadAssetAtPath(GetLODDataPath(assetPath));
344 | if (!lodData)
345 | lodData = ScriptableObject.CreateInstance();
346 |
347 | var overrideDefaults = lodData.overrideDefaults;
348 |
349 | var importSettings = lodData.importSettings;
350 | if (importSettings == null)
351 | {
352 | importSettings = new LODImportSettings();
353 | lodData.importSettings = importSettings;
354 | }
355 |
356 | if (!overrideDefaults)
357 | {
358 | importSettings.generateOnImport = enabled;
359 | importSettings.meshSimplifier = meshSimplifierType.AssemblyQualifiedName;
360 | importSettings.maxLODGenerated = maxLOD;
361 | importSettings.initialLODMaxPolyCount = initialLODMaxPolyCount;
362 | }
363 |
364 | return lodData;
365 | }
366 |
367 | static void AppendLODNameToRenderers(Transform root, int lod)
368 | {
369 | var renderers = root.GetComponentsInChildren();
370 | foreach (var r in renderers)
371 | {
372 | AppendLODNameToRenderer(r, lod);
373 | }
374 | }
375 |
376 | static void AppendLODNameToRenderer(Renderer r, int lod)
377 | {
378 | if (r.name.IndexOf("_LOD", StringComparison.OrdinalIgnoreCase) == -1)
379 | r.name = string.Format("{0}_LOD{1}", r.name, lod);
380 | }
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/Runtime/Helpers/WorkingMesh.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using Unity.Collections;
5 | using UnityEngine;
6 | using UnityEngine.Rendering;
7 |
8 | namespace Unity.AutoLOD
9 | {
10 | public static class MeshExtensions
11 | {
12 | public static int GetTriangleCount(this Mesh mesh)
13 | {
14 | var triangleCount = 0;
15 | for (var i = 0; i < mesh.subMeshCount; i++)
16 | {
17 | triangleCount += (int)mesh.GetIndexCount(i);
18 | }
19 |
20 | return triangleCount;
21 | }
22 |
23 | public static WorkingMesh ToWorkingMesh(this Mesh mesh, Allocator allocator)
24 | {
25 | var bindposes = mesh.bindposes;
26 | var wm = new WorkingMesh(allocator, mesh.vertexCount, mesh.GetTriangleCount(), mesh.subMeshCount, bindposes.Length);
27 | mesh.ApplyToWorkingMesh(ref wm, bindposes);
28 |
29 | return wm;
30 | }
31 |
32 | // Taking bindposes optional parameter is ugly, but saves an additional array allocation if it was already
33 | // accessed to get the length
34 | public static void ApplyToWorkingMesh(this Mesh mesh, ref WorkingMesh wm, Matrix4x4[] bindposes = null)
35 | {
36 | wm.indexFormat = mesh.indexFormat;
37 | wm.vertices = mesh.vertices;
38 | wm.normals = mesh.normals;
39 | wm.tangents = mesh.tangents;
40 | wm.uv = mesh.uv;
41 | wm.uv2 = mesh.uv2;
42 | wm.uv3 = mesh.uv3;
43 | wm.uv4 = mesh.uv4;
44 | wm.colors = mesh.colors;
45 | wm.boneWeights = mesh.boneWeights;
46 | wm.bindposes = bindposes ?? mesh.bindposes;
47 | wm.subMeshCount = mesh.subMeshCount;
48 | for (int i = 0; i < mesh.subMeshCount; i++)
49 | {
50 | wm.SetTriangles(mesh.GetTriangles(i), i);
51 | }
52 | wm.name = mesh.name;
53 | wm.bounds = mesh.bounds;
54 | }
55 | }
56 |
57 | public struct WorkingMesh : IDisposable
58 | {
59 | enum Channel
60 | {
61 | Vertices,
62 | Normals,
63 | Tangents,
64 | UV,
65 | UV2,
66 | UV3,
67 | UV4,
68 | Colors,
69 | BoneWeights,
70 | Bindposes,
71 | Triangles,
72 | SubmeshOffset
73 | }
74 |
75 | const int k_MaxNameSize = 128;
76 |
77 | public Vector3[] vertices
78 | {
79 | get
80 | {
81 | return m_Vertices.Slice(0, vertexCount).ToArray();
82 | }
83 | set
84 | {
85 | vertexCount = value.Length;
86 | m_Vertices.Slice(0, vertexCount).CopyFrom(value);
87 | }
88 | }
89 | NativeArray m_Vertices;
90 |
91 | public int vertexCount
92 | {
93 | get { return m_Counts[(int)Channel.Vertices]; }
94 | private set { m_Counts[(int)Channel.Vertices] = value; }
95 | }
96 |
97 | public int[] triangles
98 | {
99 | get { return m_Triangles.Slice(0, trianglesCount).ToArray(); }
100 | set
101 | {
102 | subMeshCount = 1;
103 | trianglesCount = value.Length;
104 | SetTriangles(value, 0);
105 | }
106 | }
107 | NativeArray m_Triangles;
108 |
109 | int trianglesCount
110 | {
111 | get { return m_Counts[(int)Channel.Triangles]; }
112 | set { m_Counts[(int)Channel.Triangles] = value; }
113 | }
114 |
115 | public Vector3[] normals
116 | {
117 | get { return m_Normals.Slice(0, normalsCount).ToArray(); }
118 | set
119 | {
120 | if (value == null || value.Length == 0)
121 | {
122 | normalsCount = 0;
123 | }
124 | else
125 | {
126 | normalsCount = value.Length;
127 | m_Normals.Slice(0, normalsCount).CopyFrom(value);
128 | }
129 | }
130 | }
131 | NativeArray m_Normals;
132 |
133 | int normalsCount
134 | {
135 | get { return m_Counts[(int)Channel.Normals]; }
136 | set { m_Counts[(int)Channel.Normals] = value; }
137 | }
138 |
139 | public Vector4[] tangents
140 | {
141 | get { return m_Tangents.Slice(0, tangentsCount).ToArray(); }
142 | set
143 | {
144 | if (value == null || value.Length == 0)
145 | {
146 | tangentsCount = 0;
147 | }
148 | else
149 | {
150 | tangentsCount = value.Length;
151 | m_Tangents.Slice(0, tangentsCount).CopyFrom(value);
152 | }
153 | }
154 | }
155 | NativeArray m_Tangents;
156 |
157 | int tangentsCount
158 | {
159 | get { return m_Counts[(int)Channel.Tangents]; }
160 | set { m_Counts[(int)Channel.Tangents] = value; }
161 | }
162 |
163 | public Vector2[] uv
164 | {
165 | get { return m_UV.Slice(0, uvCount).ToArray(); }
166 | set
167 | {
168 | if (value == null || value.Length == 0)
169 | {
170 | uvCount = 0;
171 | }
172 | else
173 | {
174 | uvCount = value.Length;
175 | m_UV.Slice(0, uvCount).CopyFrom(value);
176 | }
177 | }
178 | }
179 | NativeArray m_UV;
180 |
181 | int uvCount
182 | {
183 | get { return m_Counts[(int)Channel.UV]; }
184 | set { m_Counts[(int)Channel.UV] = value; }
185 | }
186 |
187 | public Vector2[] uv2
188 | {
189 | get { return m_UV2.Slice(0, uv2Count).ToArray(); }
190 | set
191 | {
192 | if (value == null || value.Length == 0)
193 | {
194 | uv2Count = 0;
195 | }
196 | else
197 | {
198 | uv2Count = value.Length;
199 | m_UV2.Slice(0, uv2Count).CopyFrom(value);
200 | }
201 | }
202 | }
203 | NativeArray m_UV2;
204 |
205 | int uv2Count
206 | {
207 | get { return m_Counts[(int)Channel.UV2]; }
208 | set { m_Counts[(int)Channel.UV2] = value; }
209 | }
210 |
211 | public Vector2[] uv3
212 | {
213 | get { return m_UV3.Slice(0, uv3Count).ToArray(); }
214 | set
215 | {
216 | if (value == null || value.Length == 0)
217 | {
218 | uv3Count = 0;
219 | }
220 | else
221 | {
222 | uv3Count = value.Length;
223 | m_UV3.Slice(0, uv3Count).CopyFrom(value);
224 | }
225 | }
226 | }
227 | NativeArray m_UV3;
228 |
229 | int uv3Count
230 | {
231 | get { return m_Counts[(int)Channel.UV3]; }
232 | set { m_Counts[(int)Channel.UV3] = value; }
233 | }
234 |
235 | public Vector2[] uv4
236 | {
237 | get { return m_UV4.Slice(0, uv4Count).ToArray(); }
238 | set
239 | {
240 | if (value == null || value.Length == 0)
241 | {
242 | uv4Count = 0;
243 | }
244 | else
245 | {
246 | uv4Count = value.Length;
247 | m_UV4.Slice(0, uv4Count).CopyFrom(value);
248 | }
249 | }
250 | }
251 | NativeArray m_UV4;
252 |
253 | int uv4Count
254 | {
255 | get { return m_Counts[(int)Channel.UV4]; }
256 | set { m_Counts[(int)Channel.UV4] = value; }
257 | }
258 |
259 | public Color[] colors
260 | {
261 | get { return m_Colors.Slice(0, colorsCount).ToArray(); }
262 | set
263 | {
264 | if (value == null || value.Length == 0)
265 | {
266 | colorsCount = 0;
267 | }
268 | else
269 | {
270 | colorsCount = value.Length;
271 | m_Colors.Slice(0, colorsCount).CopyFrom(value);
272 | }
273 | }
274 | }
275 | NativeArray m_Colors;
276 |
277 | int colorsCount
278 | {
279 | get { return m_Counts[(int)Channel.Colors]; }
280 | set { m_Counts[(int)Channel.Colors] = value; }
281 | }
282 |
283 | public Color32[] colors32
284 | {
285 | get { return colors != null ? colors.Select(c => (Color32)c).ToArray() : null; }
286 | set { colors = value != null ? value.Select(c => (Color)c).ToArray() : null; }
287 | }
288 |
289 | public BoneWeight[] boneWeights
290 | {
291 | get { return m_BoneWeights.Slice(0, boneWeightsCount).ToArray(); }
292 | set
293 | {
294 | if (value == null || value.Length == 0)
295 | {
296 | boneWeightsCount = 0;
297 | }
298 | else
299 | {
300 | boneWeightsCount = value.Length;
301 | m_BoneWeights.Slice(0, boneWeightsCount).CopyFrom(value);
302 | }
303 | }
304 | }
305 | NativeArray m_BoneWeights;
306 |
307 | int boneWeightsCount
308 | {
309 | get { return m_Counts[(int)Channel.BoneWeights]; }
310 | set { m_Counts[(int)Channel.BoneWeights] = value; }
311 | }
312 |
313 | public Matrix4x4[] bindposes
314 | {
315 | get { return m_Bindposes.Slice(0, bindposesCount).ToArray(); }
316 | set
317 | {
318 | if (value == null || value.Length == 0)
319 | {
320 | bindposesCount = 0;
321 | }
322 | else
323 | {
324 | bindposesCount = value.Length;
325 | m_Bindposes.Slice(0, bindposesCount).CopyFrom(value);
326 | }
327 | }
328 | }
329 | NativeArray m_Bindposes;
330 |
331 | int bindposesCount
332 | {
333 | get { return m_Counts[(int)Channel.Bindposes]; }
334 | set { m_Counts[(int)Channel.Bindposes] = value; }
335 | }
336 |
337 | public int subMeshCount
338 | {
339 | get { return submeshOffsetCount; }
340 | set
341 | {
342 | if (submeshOffsetCount == value)
343 | return;
344 |
345 | var previousCount = submeshOffsetCount;
346 | submeshOffsetCount = value;
347 | for (var i = previousCount; i < submeshOffsetCount; i++)
348 | {
349 | // Initialize these offsets to be invalid, so we don't use stale values
350 | m_SubmeshOffset[i] = -1;
351 | }
352 | }
353 | }
354 | NativeArray m_SubmeshOffset;
355 |
356 | int submeshOffsetCount
357 | {
358 | get { return m_Counts[(int)Channel.SubmeshOffset]; }
359 | set { m_Counts[(int)Channel.SubmeshOffset] = value; }
360 | }
361 |
362 | public string name
363 | {
364 | get
365 | {
366 | var bytes = m_Name.Slice(0, m_NameLength).ToArray();
367 | return Encoding.UTF8.GetString(bytes);
368 | }
369 | set
370 | {
371 | if (value == null)
372 | value = string.Empty;
373 |
374 | var bytes = Encoding.UTF8.GetBytes(value);
375 | m_NameLength = Mathf.Min(bytes.Length, k_MaxNameSize);
376 | m_Name.Slice(0, m_NameLength).CopyFrom(bytes);
377 | }
378 | }
379 | NativeArray m_Name;
380 | int m_NameLength;
381 |
382 | // This data does not cross the job threshold, so if it needs to be read back, then it will need to be
383 | // in a NativeArray or some other type of NativeContainer
384 | public IndexFormat indexFormat { get; set; }
385 | public Bounds bounds { get; set; }
386 |
387 | NativeArray m_Counts;
388 |
389 | // These are stubbed out for API completeness, but obviously don't do anything
390 | public void RecalculateBounds() { }
391 | public void RecalculateNormals() { }
392 | public void RecalculateTangents() { }
393 |
394 | public void SetTriangles(int[] triangles, int submesh)
395 | {
396 | if (submesh >= subMeshCount)
397 | subMeshCount = submesh + 1;
398 |
399 | var preSliceLength = m_SubmeshOffset[submesh];
400 | if (preSliceLength < 0)
401 | {
402 | if (submesh > 0)
403 | {
404 | m_SubmeshOffset[submesh] = trianglesCount;
405 | preSliceLength = trianglesCount;
406 | }
407 | else
408 | {
409 | m_SubmeshOffset[submesh] = 0;
410 | preSliceLength = 0;
411 | }
412 | }
413 | var totalCount = preSliceLength; // count prior to submesh
414 | totalCount += triangles.Length; // new submesh triangle count
415 |
416 | var postSliceOffset = 0;
417 | var postSliceLength = 0;
418 | if (submesh < subMeshCount - 2) // count of all triangles after submesh
419 | {
420 | postSliceOffset = m_SubmeshOffset[submesh + 1];
421 | if (postSliceOffset >= 0)
422 | {
423 | postSliceLength = trianglesCount - postSliceOffset;
424 | totalCount += postSliceLength;
425 | }
426 | }
427 |
428 | trianglesCount = totalCount;
429 |
430 | // Shift other following triangles up/down
431 | if (postSliceOffset > 0)
432 | {
433 | var offset = preSliceLength + triangles.Length;
434 | m_SubmeshOffset[submesh + 1] = offset;
435 | var sourceSlice = new NativeSlice(m_Triangles, postSliceOffset, postSliceLength);
436 | var destSlice = new NativeSlice(m_Triangles, offset, postSliceLength);
437 | destSlice.CopyFrom(sourceSlice);
438 | }
439 |
440 | m_Triangles.Slice(preSliceLength, triangles.Length).CopyFrom(triangles);
441 | }
442 |
443 | public int[] GetTriangles(int submesh)
444 | {
445 | if (submesh < subMeshCount)
446 | {
447 | var start = 0;
448 | var stop = 0;
449 | GetTriangleRange(submesh, out start, out stop);
450 | var length = stop - start;
451 |
452 | var slice = new NativeSlice(m_Triangles, start, length);
453 | return slice.ToArray();
454 | }
455 |
456 | return new int[0];
457 | }
458 |
459 | void GetTriangleRange(int submesh, out int start, out int stop)
460 | {
461 | if (submesh < subMeshCount)
462 | {
463 | start = m_SubmeshOffset[submesh];
464 | stop = trianglesCount;
465 | if (submesh < subMeshCount - 1)
466 | stop = m_SubmeshOffset[submesh + 1];
467 |
468 | return;
469 | }
470 |
471 | start = -1;
472 | stop = -1;
473 | return;
474 | }
475 |
476 | public WorkingMesh(Allocator allocator, int maxVertices, int maxTriangles, int maxSubmeshes, int maxBindposes) : this()
477 | {
478 | m_Counts = new NativeArray(Enum.GetValues(typeof(Channel)).Length, allocator);
479 | m_Vertices = new NativeArray(maxVertices, allocator);
480 | m_Normals = new NativeArray(maxVertices, allocator);
481 | m_Tangents = new NativeArray(maxVertices, allocator);
482 | m_UV = new NativeArray(maxVertices, allocator);
483 | m_UV2 = new NativeArray(maxVertices, allocator);
484 | m_UV3 = new NativeArray(maxVertices, allocator);
485 | m_UV4 = new NativeArray(maxVertices, allocator);
486 | m_Colors = new NativeArray(maxVertices, allocator);
487 | m_BoneWeights = new NativeArray(maxVertices, allocator);
488 | m_Bindposes = new NativeArray(maxBindposes, allocator);
489 | m_Name = new NativeArray(k_MaxNameSize, allocator);
490 | m_SubmeshOffset = new NativeArray(maxSubmeshes, allocator);
491 | m_Triangles = new NativeArray(maxTriangles, allocator);
492 | }
493 |
494 | public void Dispose()
495 | {
496 | if (m_Counts.IsCreated)
497 | m_Counts.Dispose();
498 |
499 | if (m_Vertices.IsCreated)
500 | m_Vertices.Dispose();
501 |
502 | if (m_Normals.IsCreated)
503 | m_Normals.Dispose();
504 |
505 | if (m_Tangents.IsCreated)
506 | m_Tangents.Dispose();
507 |
508 | if (m_UV.IsCreated)
509 | m_UV.Dispose();
510 |
511 | if (m_UV2.IsCreated)
512 | m_UV2.Dispose();
513 |
514 | if (m_UV3.IsCreated)
515 | m_UV3.Dispose();
516 |
517 | if (m_UV4.IsCreated)
518 | m_UV4.Dispose();
519 |
520 | if (m_Colors.IsCreated)
521 | m_Colors.Dispose();
522 |
523 | if (m_BoneWeights.IsCreated)
524 | m_BoneWeights.Dispose();
525 |
526 | if (m_Bindposes.IsCreated)
527 | m_Bindposes.Dispose();
528 |
529 | if (m_Name.IsCreated)
530 | m_Name.Dispose();
531 |
532 | if (m_SubmeshOffset.IsCreated)
533 | m_SubmeshOffset.Dispose();
534 |
535 | if (m_Triangles.IsCreated)
536 | m_Triangles.Dispose();
537 | }
538 |
539 | public void ApplyToMesh(Mesh mesh)
540 | {
541 | mesh.indexFormat = indexFormat;
542 | mesh.vertices = vertices;
543 | mesh.normals = normals;
544 | mesh.tangents = tangents;
545 | mesh.uv = uv;
546 | mesh.uv2 = uv2;
547 | mesh.uv3 = uv3;
548 | mesh.uv4 = uv4;
549 | mesh.colors = colors;
550 | mesh.boneWeights = boneWeights;
551 | mesh.bindposes = bindposes;
552 | mesh.subMeshCount = subMeshCount;
553 | for (int i = 0; i < subMeshCount; i++)
554 | {
555 | mesh.SetTriangles(GetTriangles(i), i);
556 | }
557 | mesh.name = name;
558 | mesh.bounds = bounds;
559 | }
560 | }
561 | }
562 |
--------------------------------------------------------------------------------
/Editor/SceneLOD.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using Unity.AutoLOD.Utilities;
8 | using UnityEditor;
9 | using UnityEngine;
10 | using UnityEngine.SceneManagement;
11 | using Dbg = UnityEngine.Debug;
12 | using UnityObject = UnityEngine.Object;
13 |
14 |
15 | namespace Unity.AutoLOD
16 | {
17 | public class SceneLOD : ScriptableSingleton
18 | {
19 | class SceneLODAssetProcessor : UnityEditor.AssetModificationProcessor
20 | {
21 | public static string[] OnWillSaveAssets(string[] paths)
22 | {
23 | foreach (string path in paths)
24 | {
25 | if (path.Contains(".unity"))
26 | {
27 | AssetDatabase.StartAssetEditing();
28 |
29 | var scene = SceneManager.GetSceneByPath(path);
30 | if(!scene.IsValid()) continue;
31 | var rootGameObjects = scene.GetRootGameObjects();
32 | foreach (var go in rootGameObjects)
33 | {
34 | var lodVolume = go.GetComponent();
35 | if (lodVolume)
36 | PersistHLODs(lodVolume, path);
37 | }
38 |
39 | AssetDatabase.StopAssetEditing();
40 | }
41 | }
42 |
43 | return paths;
44 | }
45 |
46 | static void PersistHLODs(LODVolume lodVolume, string scenePath)
47 | {
48 | var hlodRoot = lodVolume.hlodRoot;
49 | if (hlodRoot)
50 | {
51 | var mf = hlodRoot.GetComponent();
52 | if (mf)
53 | {
54 | var sharedMesh = mf.sharedMesh;
55 | if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(sharedMesh)))
56 | SaveUniqueHLODAsset(sharedMesh, scenePath);
57 | }
58 | }
59 |
60 | foreach (Transform child in lodVolume.transform)
61 | {
62 | var childLODVolume = child.GetComponent();
63 | if (childLODVolume)
64 | PersistHLODs(childLODVolume, scenePath);
65 | }
66 | }
67 |
68 | static void SaveUniqueHLODAsset(UnityObject asset, string scenePath)
69 | {
70 | if (!string.IsNullOrEmpty(scenePath))
71 | {
72 | var directory = Path.GetDirectoryName(scenePath) + "/" + Path.GetFileNameWithoutExtension(scenePath) + "_HLOD/";
73 | if (!Directory.Exists(directory))
74 | Directory.CreateDirectory(directory);
75 |
76 | var path = directory + Path.GetRandomFileName();
77 | path = Path.ChangeExtension(path, "asset");
78 | AssetDatabase.CreateAsset(asset, path);
79 | }
80 | }
81 | }
82 |
83 | public static bool activated { get { return s_Activated; } }
84 |
85 | public int coroutineQueueRemaining { get { return m_CoroutineQueue.Count; }}
86 | public long coroutineCurrentExecutionTime { get { return m_ServiceCoroutineExecutionTime.ElapsedMilliseconds; }}
87 |
88 | static bool s_HLODEnabled = true;
89 | static bool s_Activated;
90 |
91 | string m_CreateRootVolumeForScene = "Default"; // Set to some value, so new scenes don't auto-create
92 | LODVolume m_RootVolume;
93 | GameObject[] m_SelectedObjects;
94 | Dictionary m_SelectedObjectLastPose = new Dictionary();
95 | Queue m_CoroutineQueue = new Queue();
96 | Coroutine m_ServiceCoroutineQueue;
97 | bool m_SceneDirty;
98 | Stopwatch m_ServiceCoroutineExecutionTime = new Stopwatch();
99 | Camera m_LastCamera;
100 | HashSet m_ExcludedRenderers = new HashSet();
101 | Vector3 m_LastCameraPosition;
102 | Quaternion m_LastCameraRotation;
103 |
104 | // Local method variable caching
105 | List m_FoundRenderers = new List();
106 | HashSet m_ExistingRenderers = new HashSet();
107 | HashSet m_AddedRenderers = new HashSet();
108 | HashSet m_RemovedRenderers = new HashSet();
109 |
110 | void OnEnable()
111 | {
112 | if (LayerMask.NameToLayer(LODVolume.HLODLayer) == -1)
113 | {
114 | var layers = TagManager.GetRequiredLayers();
115 | foreach (var layer in layers)
116 | {
117 | TagManager.AddLayer(layer);
118 | }
119 | }
120 |
121 | if (LayerMask.NameToLayer(LODVolume.HLODLayer) != -1)
122 | {
123 | Tools.lockedLayers |= LayerMask.GetMask(LODVolume.HLODLayer);
124 | s_Activated = true;
125 | }
126 |
127 | if (s_Activated)
128 | AddCallbacks();
129 |
130 | ResetServiceCoroutineQueue();
131 | }
132 |
133 | void OnDisable()
134 | {
135 | s_Activated = false;
136 | RemoveCallbacks();
137 | }
138 |
139 | void AddCallbacks()
140 | {
141 | EditorApplication.update += EditorUpdate;
142 | EditorApplication.hierarchyChanged += OnHierarchyChanged;
143 | Selection.selectionChanged += OnSelectionChanged;
144 | Camera.onPreCull += PreCull;
145 | #if UNITY_2019_1_OR_NEWER
146 | SceneView.duringSceneGui += OnSceneGUI;
147 | #else
148 | SceneView.onSceneGUIDelegate += OnSceneGUI;
149 | #endif
150 | }
151 |
152 | void RemoveCallbacks()
153 | {
154 | EditorApplication.update -= EditorUpdate;
155 | EditorApplication.hierarchyChanged -= OnHierarchyChanged;
156 | Selection.selectionChanged -= OnSelectionChanged;
157 | Camera.onPreCull -= PreCull;
158 | #if UNITY_2019_1_OR_NEWER
159 | SceneView.duringSceneGui -= OnSceneGUI;
160 | #else
161 | SceneView.onSceneGUIDelegate -= OnSceneGUI;
162 | #endif
163 | }
164 |
165 | void OnHierarchyChanged()
166 | {
167 | m_SceneDirty = true;
168 | m_ExcludedRenderers.Clear();
169 | }
170 |
171 | void OnSelectionChanged()
172 | {
173 | if (!m_RootVolume)
174 | return;
175 |
176 | if (m_SelectedObjects != null)
177 | m_CoroutineQueue.Enqueue(UpdateOctreeBounds(m_SelectedObjects));
178 |
179 | m_SelectedObjects = Selection.gameObjects;
180 | if (m_SelectedObjects != null)
181 | {
182 | foreach (var selected in m_SelectedObjects)
183 | {
184 | if (selected)
185 | {
186 | var selectedTransform = selected.transform;
187 | m_SelectedObjectLastPose[selected] = new Pose(selectedTransform.position, selectedTransform.rotation);
188 | }
189 | }
190 | }
191 | }
192 |
193 | void OnSceneGUI(SceneView sceneView)
194 | {
195 | var activeSceneName = SceneManager.GetActiveScene().name;
196 |
197 | var rect = sceneView.position;
198 | rect.x = 0f;
199 | rect.y = 0f;
200 |
201 | Handles.BeginGUI();
202 | GUILayout.BeginArea(rect);
203 | GUILayout.BeginHorizontal();
204 | if (m_RootVolume && GUILayout.Button(s_HLODEnabled ? "Disable HLOD" : "Enable HLOD"))
205 | {
206 | s_HLODEnabled = !s_HLODEnabled;
207 | m_LastCamera = null;
208 | }
209 | else if (!m_RootVolume && m_CreateRootVolumeForScene != activeSceneName && GUILayout.Button("Activate SceneLOD"))
210 | {
211 | m_CreateRootVolumeForScene = activeSceneName;
212 | m_SceneDirty = true;
213 | m_LastCamera = null;
214 | }
215 |
216 | GUILayout.FlexibleSpace();
217 | GUILayout.EndHorizontal();
218 | GUILayout.EndArea();
219 | Handles.EndGUI();
220 | }
221 |
222 | IEnumerator UpdateOctreeBounds(GameObject[] gameObjects)
223 | {
224 | foreach (var go in gameObjects)
225 | {
226 | if (!go)
227 | continue;
228 |
229 | Pose pose;
230 | if (m_SelectedObjectLastPose.TryGetValue(go, out pose))
231 | {
232 | var goTransform = go.transform;
233 | if (pose.position == goTransform.position && pose.rotation == goTransform.rotation)
234 | continue;
235 | }
236 |
237 | yield return UpdateChangedRenderer(go);
238 | }
239 | }
240 |
241 | IEnumerator UpdateChangedRenderer(GameObject go)
242 | {
243 | if (!go)
244 | yield break;
245 |
246 | while (!m_RootVolume)
247 | yield return UpdateOctree();
248 |
249 | var transform = go.transform;
250 | var renderer = go.GetComponent();
251 | if (renderer)
252 | {
253 | if (transform.hasChanged && m_RootVolume.renderers.Contains(renderer))
254 | {
255 | yield return m_RootVolume.UpdateRenderer(renderer);
256 | yield return SetRootLODVolume(); // In case the BVH has grown or shrunk
257 | transform.hasChanged = false;
258 | }
259 | }
260 |
261 | foreach (Transform child in transform)
262 | {
263 | yield return UpdateChangedRenderer(child.gameObject);
264 | if (!transform)
265 | yield break;
266 | }
267 | }
268 |
269 | IEnumerator UpdateOctree()
270 | {
271 | if (!m_RootVolume)
272 | {
273 | yield return SetRootLODVolume();
274 |
275 | if (!m_RootVolume)
276 | {
277 | if (m_CreateRootVolumeForScene == SceneManager.GetActiveScene().name)
278 | m_RootVolume = LODVolume.Create();
279 | else
280 | yield break;
281 | }
282 | }
283 |
284 | var renderers = m_FoundRenderers;
285 | renderers.Clear();
286 |
287 | yield return ObjectUtils.FindObjectsOfType(renderers);
288 |
289 | // Remove any renderers that should not be there (e.g. HLODs)
290 | renderers.RemoveAll(r => m_ExcludedRenderers.Contains(r));
291 | renderers.RemoveAll(r =>
292 | {
293 | if (r)
294 | {
295 | // Check against previous collection
296 | if (m_ExistingRenderers.Contains(r))
297 | return false;
298 |
299 | if (r.gameObject.layer == LayerMask.NameToLayer(LODVolume.HLODLayer))
300 | {
301 | m_ExcludedRenderers.Add(r);
302 | return true;
303 | }
304 |
305 | var mf = r.GetComponent();
306 | if (!mf || (mf.sharedMesh && mf.sharedMesh.GetTopology(0) != MeshTopology.Triangles))
307 | {
308 | m_ExcludedRenderers.Add(r);
309 | return true;
310 | }
311 |
312 | var lodGroup = r.GetComponentInParent();
313 | if (lodGroup)
314 | {
315 | var lods = lodGroup.GetLODs();
316 |
317 | // Skip LOD0, so that we keep the original renderers in the list
318 | for (int i = 1; i < lods.Length; i++)
319 | {
320 | if (lods[i].renderers.Contains(r))
321 | {
322 | m_ExcludedRenderers.Add(r);
323 | return true;
324 | }
325 | }
326 | }
327 | else
328 | {
329 | // HLODs should come after traditional LODs, so exclude any standalone renderers
330 | m_ExcludedRenderers.Add(r);
331 | return true;
332 | }
333 | }
334 |
335 | return false;
336 | });
337 |
338 | var existingRenderers = m_ExistingRenderers;
339 | existingRenderers.Clear();
340 | existingRenderers.UnionWith(m_RootVolume.renderers);
341 |
342 | var removed = m_RemovedRenderers;
343 | removed.Clear();
344 | removed.UnionWith(m_ExistingRenderers);
345 | removed.ExceptWith(renderers);
346 |
347 | var added = m_AddedRenderers;
348 | added.Clear();
349 | added.UnionWith(renderers);
350 | added.ExceptWith(existingRenderers);
351 |
352 | foreach (var r in removed)
353 | {
354 | if (existingRenderers.Contains(r))
355 | {
356 | yield return m_RootVolume.RemoveRenderer(r);
357 |
358 | // Check if the BVH shrunk
359 | yield return SetRootLODVolume();
360 | }
361 | }
362 |
363 | foreach (var r in added)
364 | {
365 | if (!existingRenderers.Contains(r))
366 | {
367 | yield return m_RootVolume.AddRenderer(r);
368 | r.transform.hasChanged = false;
369 |
370 | // Check if the BVH grew
371 | yield return SetRootLODVolume();
372 | }
373 | }
374 | }
375 |
376 | IEnumerator SetRootLODVolume()
377 | {
378 | if (m_RootVolume)
379 | {
380 | var rootVolumeTransform = m_RootVolume.transform;
381 | var transformRoot = rootVolumeTransform.root;
382 |
383 | // Handle the case where the BVH has grown
384 | if (rootVolumeTransform != transformRoot)
385 | m_RootVolume = transformRoot.GetComponent();
386 |
387 | yield break;
388 | }
389 |
390 | // Handle initialization or the case where the BVH has shrunk
391 | LODVolume lodVolume = null;
392 | var scene = SceneManager.GetActiveScene();
393 | var rootGameObjects = scene.GetRootGameObjects();
394 | foreach (var go in rootGameObjects)
395 | {
396 | if (!go)
397 | continue;
398 |
399 | lodVolume = go.GetComponent();
400 | if (lodVolume)
401 | break;
402 |
403 | yield return null;
404 | }
405 |
406 | if (lodVolume)
407 | m_RootVolume = lodVolume;
408 |
409 | m_ExcludedRenderers.Clear();
410 | }
411 |
412 | void EditorUpdate()
413 | {
414 | if ((m_CoroutineQueue.Count > 0 || m_SceneDirty || (m_RootVolume && m_RootVolume.dirty))
415 | && m_ServiceCoroutineQueue == null)
416 | {
417 | m_ServiceCoroutineQueue = MonoBehaviourHelper.StartCoroutine(ServiceCoroutineQueue());
418 | }
419 | }
420 | IEnumerator ServiceCoroutineQueue()
421 | {
422 | m_ServiceCoroutineExecutionTime.Start();
423 |
424 | if (m_SceneDirty)
425 | {
426 | m_CoroutineQueue.Enqueue(UpdateOctree());
427 | m_SceneDirty = false;
428 | }
429 |
430 | if (m_RootVolume && m_RootVolume.dirty)
431 | m_CoroutineQueue.Enqueue(m_RootVolume.UpdateHLODs());
432 |
433 | while (m_CoroutineQueue.Count > 0)
434 | {
435 | yield return MonoBehaviourHelper.StartCoroutine(m_CoroutineQueue.Dequeue());
436 | }
437 |
438 | ResetServiceCoroutineQueue();
439 | }
440 |
441 | void ResetServiceCoroutineQueue()
442 | {
443 | m_ServiceCoroutineQueue = null;
444 | m_ServiceCoroutineExecutionTime.Reset();
445 | }
446 |
447 | // PreCull is called before LODGroup updates
448 | void PreCull(Camera camera)
449 | {
450 | if (!m_RootVolume)
451 | return;
452 |
453 | var cameraType = camera.cameraType;
454 | var cameraTransform = camera.transform;
455 | var cameraPosition = cameraTransform.position;
456 | var cameraRotation = cameraTransform.rotation;
457 |
458 | if (((cameraType == CameraType.Game && camera == Camera.main) || cameraType == CameraType.SceneView)
459 | && (m_LastCamera != camera || m_LastCameraPosition != cameraPosition || m_LastCameraRotation != cameraRotation || m_SceneDirty))
460 | {
461 | var deltaForward = Vector3.Dot(cameraPosition - m_LastCameraPosition, cameraTransform.forward);
462 |
463 | UpdateLODGroup(m_RootVolume, camera, cameraPosition, m_LastCamera == camera && deltaForward < 0f);
464 |
465 | m_LastCamera = camera;
466 | m_LastCameraPosition = cameraPosition;
467 | m_LastCameraRotation = cameraRotation;
468 | }
469 | }
470 |
471 | bool UpdateLODGroup(LODVolume lodVolume, Camera camera, Vector3 cameraPosition, bool fastPath)
472 | {
473 | var lodGroupEnabled = s_HLODEnabled;
474 |
475 | var lodGroup = lodVolume.lodGroup;
476 | var lodGroupExists = lodGroup != null && lodGroup.lodGroup;
477 |
478 | // Start with leaf nodes first
479 | var lodVolumeTransform = lodVolume.transform;
480 | var childVolumes = lodVolume.childVolumes;
481 | foreach (var childVolume in childVolumes)
482 | {
483 | if (childVolume)
484 | {
485 | if (!fastPath || !lodGroupExists || !lodGroup.lodGroup.enabled)
486 | lodGroupEnabled &= UpdateLODGroup(childVolume, camera, cameraPosition, fastPath);
487 | }
488 | }
489 |
490 | if (lodGroupEnabled)
491 | {
492 | var allChildrenUsingCoarsestLOD = true;
493 | if (lodVolumeTransform.childCount == 0) // Leaf node
494 | {
495 | var cached = lodVolume.cached;
496 |
497 | // Disable all children LODGroups if an HLOD LODGroup could replace it
498 | foreach (var r in cached)
499 | {
500 | var childLODGroup = r as LODVolume.LODGroupHelper;
501 |
502 | if (childLODGroup != null && childLODGroup.GetCurrentLOD(camera, cameraPosition) != childLODGroup.GetMaxLOD())
503 | {
504 | allChildrenUsingCoarsestLOD = false;
505 | break;
506 | }
507 | }
508 |
509 | foreach (var r in cached)
510 | {
511 | var childLODGroup = r as LODVolume.LODGroupHelper;
512 |
513 | if (childLODGroup != null)
514 | childLODGroup.SetEnabled(!allChildrenUsingCoarsestLOD);
515 | else if (r != null)
516 | ((Renderer)r).enabled = !allChildrenUsingCoarsestLOD;
517 | }
518 | }
519 | else
520 | {
521 | foreach (var childVolume in childVolumes)
522 | {
523 | var childLODGroup = childVolume.lodGroup;
524 | if (childLODGroup != null && childLODGroup.lodGroup)
525 | {
526 | var maxLOD = childLODGroup.GetMaxLOD();
527 | if (maxLOD > 0 && childLODGroup.GetCurrentLOD(camera, cameraPosition) != maxLOD)
528 | {
529 | allChildrenUsingCoarsestLOD = false;
530 | break;
531 | }
532 | }
533 | }
534 |
535 | foreach (var childVolume in childVolumes)
536 | {
537 | var childLODGroup = childVolume.lodGroup;
538 | if (childLODGroup != null && childLODGroup.lodGroup)
539 | childLODGroup.SetEnabled(!allChildrenUsingCoarsestLOD);
540 | }
541 | }
542 |
543 | lodGroupEnabled &= allChildrenUsingCoarsestLOD;
544 | }
545 | else if (!s_HLODEnabled && lodVolumeTransform.childCount == 0) // Re-enable default renderers
546 | {
547 | foreach (var r in lodVolume.renderers)
548 | {
549 | if (!r)
550 | continue;
551 |
552 | var childLODGroup = r.GetComponentInParent();
553 | if (childLODGroup)
554 | childLODGroup.SetEnabled(true);
555 | else
556 | r.enabled = true;
557 | }
558 | }
559 |
560 | if (lodGroupExists)
561 | lodGroup.SetEnabled(lodGroupEnabled);
562 |
563 | return lodGroupEnabled;
564 | }
565 | }
566 | }
567 |
--------------------------------------------------------------------------------
/Runtime/LODVolume.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using Unity.AutoLOD;
7 | using Unity.AutoLOD.Utilities;
8 | using UnityEditor;
9 | using UnityEngine;
10 | using UnityEngine.Rendering;
11 |
12 | [RequiresLayer(HLODLayer)]
13 | public class LODVolume : MonoBehaviour
14 | {
15 | [Serializable]
16 | public class LODGroupHelper
17 | {
18 | public LODGroup lodGroup
19 | {
20 | get { return m_LODGroup; }
21 | set
22 | {
23 | m_LODGroup = value;
24 | m_LODs = null;
25 | }
26 | }
27 | public LOD[] lods
28 | {
29 | get
30 | {
31 | if (m_LODs == null && m_LODGroup)
32 | m_LODs = m_LODGroup.GetLODs();
33 |
34 | return m_LODs;
35 | }
36 | }
37 | public Vector3 referencePoint
38 | {
39 | get
40 | {
41 | if (!m_ReferencePoint.HasValue)
42 | m_ReferencePoint = m_LODGroup ? m_LODGroup.transform.TransformPoint(m_LODGroup.localReferencePoint) : Vector3.zero;
43 |
44 | return m_ReferencePoint.Value;
45 | }
46 | }
47 | public float worldSpaceSize
48 | {
49 | get
50 | {
51 | if (!m_WorldSpaceSize.HasValue && m_LODGroup)
52 | m_WorldSpaceSize = m_LODGroup.GetWorldSpaceSize();
53 |
54 | return m_WorldSpaceSize ?? 0f;
55 | }
56 |
57 | }
58 |
59 | public int maxLOD
60 | {
61 | get
62 | {
63 | if (!m_MaxLOD.HasValue)
64 | m_MaxLOD = lodGroup.GetMaxLOD();
65 |
66 | return m_MaxLOD.Value;
67 | }
68 | }
69 |
70 | [SerializeField]
71 | LODGroup m_LODGroup;
72 |
73 | //Transform m_Transform;
74 | LOD[] m_LODs;
75 | Vector3? m_ReferencePoint;
76 | float? m_WorldSpaceSize;
77 | int? m_MaxLOD;
78 | }
79 |
80 | public const string HLODLayer = "HLOD";
81 | public static Type meshSimplifierType { set; get; }
82 | public static Type batcherType { set; get; }
83 | public static bool drawBounds { set; get; }
84 |
85 | public LODGroupHelper lodGroup
86 | {
87 | get
88 | {
89 | if (m_LODGroupHelper == null)
90 | {
91 | m_LODGroupHelper = new LODGroupHelper();
92 | m_LODGroupHelper.lodGroup = GetComponent();
93 | }
94 |
95 | return m_LODGroupHelper;
96 | }
97 | }
98 |
99 | public bool dirty;
100 | public Bounds bounds;
101 | public GameObject hlodRoot;
102 | public List renderers = new List();
103 | public List childVolumes = new List();
104 |
105 | public List