520 lines
20 KiB
C#
520 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Unity.HLODSystem.SpaceManager;
|
|
using Unity.HLODSystem.Utils;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace Unity.HLODSystem.Streaming
|
|
{
|
|
public class YooStreaming : IStreamingBuilder
|
|
{
|
|
static class Styles
|
|
{
|
|
public static TextureFormat[] SupportTextureFormats = new[]
|
|
{
|
|
TextureFormat.RGBA32,
|
|
TextureFormat.RGB24,
|
|
TextureFormat.BC7,
|
|
TextureFormat.DXT5,
|
|
TextureFormat.DXT1,
|
|
TextureFormat.ASTC_4x4,
|
|
TextureFormat.ASTC_5x5,
|
|
TextureFormat.ASTC_6x6,
|
|
TextureFormat.ASTC_8x8,
|
|
TextureFormat.ASTC_10x10,
|
|
TextureFormat.ASTC_12x12,
|
|
TextureFormat.ETC_RGB4,
|
|
TextureFormat.ETC2_RGB,
|
|
TextureFormat.ETC2_RGBA8,
|
|
TextureFormat.PVRTC_RGB4,
|
|
TextureFormat.PVRTC_RGB2,
|
|
TextureFormat.PVRTC_RGBA4,
|
|
TextureFormat.PVRTC_RGBA2,
|
|
};
|
|
|
|
public static string[] SupportTextureFormatStrings;
|
|
public static Dictionary<TextureFormat, int> SupportTextureFormatIndex;
|
|
|
|
static Styles()
|
|
{
|
|
SupportTextureFormatStrings = new string[SupportTextureFormats.Length];
|
|
SupportTextureFormatIndex = new Dictionary<TextureFormat, int>();
|
|
for (int i = 0; i < SupportTextureFormats.Length; ++i)
|
|
{
|
|
SupportTextureFormatStrings[i] = SupportTextureFormats[i].ToString();
|
|
SupportTextureFormatIndex[SupportTextureFormats[i]] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
[InitializeOnLoadMethod]
|
|
static void RegisterType()
|
|
{
|
|
StreamingBuilderTypes.RegisterType(typeof(YooStreaming));
|
|
}
|
|
|
|
private IGeneratedResourceManager m_manager;
|
|
private SerializableDynamicObject m_streamingOptions;
|
|
private int m_controllerID;
|
|
|
|
HashSet<string> m_shaderGuids = new HashSet<string>();
|
|
|
|
public YooStreaming(IGeneratedResourceManager manager, int controllerID, SerializableDynamicObject streamingOptions)
|
|
{
|
|
m_manager = manager;
|
|
m_streamingOptions = streamingOptions;
|
|
m_controllerID = controllerID;
|
|
}
|
|
|
|
public YooStreaming(TerrainHLOD terrainHlod, SerializableDynamicObject streamingOptions)
|
|
{
|
|
m_manager = terrainHlod;
|
|
m_streamingOptions = streamingOptions;
|
|
}
|
|
|
|
public void Build(SpaceNode rootNode, DisposableList<HLODBuildInfo> infos, GameObject root,
|
|
float cullDistance, float lodDistance, bool writeNoPrefab, bool extractMaterial, Action<float> onProgress)
|
|
{
|
|
dynamic options = m_streamingOptions;
|
|
string path = options.OutputDirectory;
|
|
|
|
HLODTreeNodeContainer container = new HLODTreeNodeContainer();
|
|
HLODTreeNode convertedRootNode = ConvertNode(container, rootNode);
|
|
|
|
//create settings if there is no settings.
|
|
|
|
// var settings = AddressableAssetSettingsDefaultObject.GetSettings(true);
|
|
// var group = GetGroup(settings, options.AddressablesGroupName);
|
|
m_shaderGuids.Clear();
|
|
|
|
if (onProgress != null)
|
|
onProgress(0.0f);
|
|
|
|
HLODData.TextureCompressionData compressionData;
|
|
compressionData.PCTextureFormat = options.PCCompression;
|
|
compressionData.WebGLTextureFormat = options.WebGLCompression;
|
|
compressionData.AndroidTextureFormat = options.AndroidCompression;
|
|
compressionData.iOSTextureFormat = options.iOSCompression;
|
|
compressionData.tvOSTextureFormat = options.tvOSCompression;
|
|
|
|
|
|
string filenamePrefix = $"{path}{root.name}";
|
|
|
|
if (Directory.Exists(path) == false)
|
|
{
|
|
Directory.CreateDirectory(path);
|
|
}
|
|
|
|
Dictionary<int, HLODData> hlodDatas = new Dictionary<int, HLODData>();
|
|
|
|
for (int i = 0; i < infos.Count; ++i)
|
|
{
|
|
if (hlodDatas.ContainsKey(infos[i].ParentIndex) == false)
|
|
{
|
|
HLODData newData = new HLODData();
|
|
newData.CompressionData = compressionData;
|
|
hlodDatas.Add(infos[i].ParentIndex, newData);
|
|
}
|
|
|
|
HLODData data = hlodDatas[infos[i].ParentIndex];
|
|
data.AddFromWokringObjects(infos[i].Name, infos[i].WorkingObjects);
|
|
data.AddFromWorkingColliders(infos[i].Name, infos[i].Colliders);
|
|
|
|
if (writeNoPrefab)
|
|
{
|
|
if (hlodDatas.ContainsKey(i) == false)
|
|
{
|
|
HLODData newData = new HLODData();
|
|
newData.CompressionData = compressionData;
|
|
hlodDatas.Add(i, newData);
|
|
}
|
|
|
|
HLODData prefabData = hlodDatas[i];
|
|
var spaceNode = infos[i].Target;
|
|
|
|
for (int oi = 0; oi < spaceNode.Objects.Count; ++oi)
|
|
{
|
|
if (PrefabUtility.IsAnyPrefabInstanceRoot(spaceNode.Objects[oi]) == false)
|
|
{
|
|
prefabData.AddFromGameObject(spaceNode.Objects[oi]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (onProgress != null)
|
|
onProgress((float)i / (float)infos.Count);
|
|
}
|
|
|
|
|
|
if (extractMaterial)
|
|
{
|
|
ExtractMaterial(hlodDatas, filenamePrefix);
|
|
}
|
|
|
|
Dictionary<int, RootData> rootDatas = new Dictionary<int, RootData>();
|
|
foreach (var item in hlodDatas)
|
|
{
|
|
string filename = $"{filenamePrefix}_group{item.Key}.hlod";
|
|
using (Stream stream = new FileStream(filename, FileMode.Create))
|
|
{
|
|
HLODDataSerializer.Write(stream, item.Value);
|
|
stream.Close();
|
|
}
|
|
|
|
AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
|
|
RootData rootData = AssetDatabase.LoadAssetAtPath<RootData>(filename);
|
|
m_manager.AddGeneratedResource(rootData);
|
|
rootDatas.Add(item.Key, rootData);
|
|
}
|
|
|
|
var addressableController = root.AddComponent<YooAssetHLODController>();
|
|
addressableController.ControllerID = m_controllerID;
|
|
m_manager.AddGeneratedResource(addressableController);
|
|
|
|
for (int i = 0; i < infos.Count; ++i)
|
|
{
|
|
var spaceNode = infos[i].Target;
|
|
var hlodTreeNode = convertedTable[infos[i].Target];
|
|
|
|
for (int oi = 0; oi < spaceNode.Objects.Count; ++oi)
|
|
{
|
|
int highId = -1;
|
|
GameObject obj = spaceNode.Objects[oi];
|
|
|
|
if (PrefabUtility.IsPartOfAnyPrefab(obj) == false)
|
|
{
|
|
GameObject rootGameObject = null;
|
|
|
|
if (rootDatas.ContainsKey(i))
|
|
rootGameObject = rootDatas[i].GetRootObject(obj.name);
|
|
|
|
if (rootGameObject != null)
|
|
{
|
|
GameObject go = PrefabUtility.InstantiatePrefab(rootGameObject) as GameObject;
|
|
go.transform.SetParent(obj.transform.parent);
|
|
go.transform.localPosition = obj.transform.localPosition;
|
|
go.transform.localRotation = obj.transform.localRotation;
|
|
go.transform.localScale = obj.transform.localScale;
|
|
|
|
if (m_manager.IsGeneratedResource(obj))
|
|
m_manager.AddGeneratedResource(go);
|
|
else
|
|
m_manager.AddConvertedPrefabResource(go);
|
|
|
|
spaceNode.Objects.Add(go);
|
|
|
|
Object.DestroyImmediate(obj);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var address = GetAddress(spaceNode.Objects[oi]);
|
|
if (string.IsNullOrEmpty(address) && PrefabUtility.IsAnyPrefabInstanceRoot(spaceNode.Objects[oi]))
|
|
{
|
|
address = GetAddress(spaceNode.Objects[oi]);
|
|
}
|
|
|
|
if (address != null)
|
|
{
|
|
highId = addressableController.AddHighObject(address, spaceNode.Objects[oi]);
|
|
}
|
|
else
|
|
{
|
|
highId = addressableController.AddHighObject(spaceNode.Objects[oi]);
|
|
}
|
|
|
|
hlodTreeNode.HighObjectIds.Add(highId);
|
|
}
|
|
|
|
{
|
|
if (rootDatas[infos[i].ParentIndex].GetRootObject(infos[i].Name) != null)
|
|
{
|
|
string filename = $"{filenamePrefix}_group{infos[i].ParentIndex}.hlod";
|
|
int lowId = addressableController.AddLowObject(filename + "[" + infos[i].Name + "]");
|
|
hlodTreeNode.LowObjectIds.Add(lowId);
|
|
}
|
|
}
|
|
}
|
|
|
|
addressableController.Container = container;
|
|
addressableController.Root = convertedRootNode;
|
|
addressableController.CullDistance = cullDistance;
|
|
addressableController.LODDistance = lodDistance;
|
|
|
|
addressableController.UpdateMaxManualLevel();
|
|
}
|
|
|
|
private void ExtractMaterial(Dictionary<int, HLODData> hlodDatas, string filenamePrefix)
|
|
{
|
|
Dictionary<string, HLODData.SerializableMaterial> hlodAllMaterials = new Dictionary<string, HLODData.SerializableMaterial>();
|
|
//collect all materials
|
|
foreach (var hlodData in hlodDatas.Values)
|
|
{
|
|
var hlodMaterials = hlodData.GetMaterials();
|
|
for (int mi = 0; mi < hlodMaterials.Count; ++mi)
|
|
{
|
|
if (hlodAllMaterials.ContainsKey(hlodMaterials[mi].ID) == false)
|
|
{
|
|
hlodAllMaterials.Add(hlodMaterials[mi].ID, hlodMaterials[mi]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Dictionary<string, HLODData.SerializableMaterial> extractedMaterials = new Dictionary<string, HLODData.SerializableMaterial>();
|
|
//save files to disk
|
|
foreach (var hlodMaterial in hlodAllMaterials)
|
|
{
|
|
hlodMaterial.Value.GetTextureCount();
|
|
Material mat = hlodMaterial.Value.To();
|
|
|
|
for (int ti = 0; ti < hlodMaterial.Value.GetTextureCount(); ++ti)
|
|
{
|
|
var serializeTexture = hlodMaterial.Value.GetTexture(ti);
|
|
Texture2D texture = serializeTexture.To();
|
|
byte[] bytes = texture.EncodeToPNG();
|
|
string textureFilename = $"{filenamePrefix}_{mat.name}_{serializeTexture.TextureName}.png";
|
|
File.WriteAllBytes(textureFilename, bytes);
|
|
|
|
AssetDatabase.ImportAsset(textureFilename);
|
|
|
|
var assetImporter = AssetImporter.GetAtPath(textureFilename);
|
|
var textureImporter = assetImporter as TextureImporter;
|
|
|
|
if (textureImporter)
|
|
{
|
|
textureImporter.wrapMode = serializeTexture.WrapMode;
|
|
textureImporter.sRGBTexture = GraphicsFormatUtility.IsSRGBFormat(serializeTexture.GraphicsFormat);
|
|
textureImporter.SaveAndReimport();
|
|
}
|
|
|
|
var storedTexture = AssetDatabase.LoadAssetAtPath<Texture>(textureFilename);
|
|
m_manager.AddGeneratedResource(storedTexture);
|
|
mat.SetTexture(serializeTexture.Name, storedTexture);
|
|
}
|
|
|
|
string matFilename = $"{filenamePrefix}_{mat.name}.mat";
|
|
AssetDatabase.CreateAsset(mat, matFilename);
|
|
AssetDatabase.ImportAsset(matFilename);
|
|
|
|
var storedMaterial = AssetDatabase.LoadAssetAtPath<Material>(matFilename);
|
|
m_manager.AddGeneratedResource(storedMaterial);
|
|
|
|
|
|
using (WorkingMaterial newWM = new WorkingMaterial(Collections.Allocator.Temp, storedMaterial))
|
|
{
|
|
var newSM = new HLODData.SerializableMaterial();
|
|
newSM.From(newWM);
|
|
|
|
extractedMaterials.Add(hlodMaterial.Key, newSM);
|
|
}
|
|
}
|
|
|
|
//apply to HLODData
|
|
foreach (var hlodData in hlodDatas.Values)
|
|
{
|
|
var materials = hlodData.GetMaterials();
|
|
for (int i = 0; i < materials.Count; ++i)
|
|
{
|
|
if (extractedMaterials.ContainsKey(materials[i].ID) == false)
|
|
continue;
|
|
|
|
materials[i] = extractedMaterials[materials[i].ID];
|
|
}
|
|
|
|
var objects = hlodData.GetObjects();
|
|
for (int oi = 0; oi < objects.Count; ++oi)
|
|
{
|
|
var matIds = objects[oi].GetMaterialIds();
|
|
|
|
for (int mi = 0; mi < matIds.Count; ++mi)
|
|
{
|
|
if (extractedMaterials.ContainsKey(matIds[mi]) == false)
|
|
continue;
|
|
|
|
matIds[mi] = extractedMaterials[matIds[mi]].ID;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Dictionary<SpaceNode, HLODTreeNode> convertedTable = new Dictionary<SpaceNode, HLODTreeNode>();
|
|
|
|
private HLODTreeNode ConvertNode(HLODTreeNodeContainer container, SpaceNode rootNode)
|
|
{
|
|
HLODTreeNode root = new HLODTreeNode();
|
|
root.SetContainer(container);
|
|
|
|
Queue<HLODTreeNode> hlodTreeNodes = new Queue<HLODTreeNode>();
|
|
Queue<SpaceNode> spaceNodes = new Queue<SpaceNode>();
|
|
Queue<int> levels = new Queue<int>();
|
|
|
|
hlodTreeNodes.Enqueue(root);
|
|
spaceNodes.Enqueue(rootNode);
|
|
levels.Enqueue(0);
|
|
|
|
while (hlodTreeNodes.Count > 0)
|
|
{
|
|
var hlodTreeNode = hlodTreeNodes.Dequeue();
|
|
var spaceNode = spaceNodes.Dequeue();
|
|
int level = levels.Dequeue();
|
|
|
|
convertedTable[spaceNode] = hlodTreeNode;
|
|
|
|
hlodTreeNode.Level = level;
|
|
hlodTreeNode.Bounds = spaceNode.Bounds;
|
|
if (spaceNode.HasChild() == true)
|
|
{
|
|
List<HLODTreeNode> childTreeNodes = new List<HLODTreeNode>(spaceNode.GetChildCount());
|
|
for (int i = 0; i < spaceNode.GetChildCount(); ++i)
|
|
{
|
|
var treeNode = new HLODTreeNode();
|
|
treeNode.SetContainer(container);
|
|
childTreeNodes.Add(treeNode);
|
|
|
|
hlodTreeNodes.Enqueue(treeNode);
|
|
spaceNodes.Enqueue(spaceNode.GetChild(i));
|
|
levels.Enqueue(level + 1);
|
|
}
|
|
|
|
hlodTreeNode.SetChildTreeNode(childTreeNodes);
|
|
}
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
static bool showFormat = true;
|
|
|
|
public static void OnGUI(SerializableDynamicObject streamingOptions)
|
|
{
|
|
dynamic options = streamingOptions;
|
|
|
|
#region Setup default values
|
|
|
|
if (options.OutputDirectory == null)
|
|
{
|
|
string path = Application.dataPath;
|
|
path = "Assets" + path.Substring(Application.dataPath.Length);
|
|
path = path.Replace('\\', '/');
|
|
if (path.EndsWith("/") == false)
|
|
path += "/";
|
|
options.OutputDirectory = path;
|
|
}
|
|
|
|
if (options.AddressablesGroupName == null)
|
|
{
|
|
options.AddressablesGroupName = "HLOD";
|
|
}
|
|
|
|
if (options.PCCompression == null)
|
|
{
|
|
options.PCCompression = TextureFormat.BC7;
|
|
}
|
|
|
|
if (options.WebGLCompression == null)
|
|
{
|
|
options.WebGLCompression = TextureFormat.DXT5;
|
|
}
|
|
|
|
if (options.AndroidCompression == null)
|
|
{
|
|
options.AndroidCompression = TextureFormat.ETC2_RGBA8;
|
|
}
|
|
|
|
if (options.iOSCompression == null)
|
|
{
|
|
options.iOSCompression = TextureFormat.PVRTC_RGBA4;
|
|
}
|
|
|
|
if (options.tvOSCompression == null)
|
|
{
|
|
options.tvOSCompression = TextureFormat.ASTC_4x4;
|
|
}
|
|
|
|
#endregion
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel("OutputDirectory");
|
|
if (GUILayout.Button(options.OutputDirectory))
|
|
{
|
|
string selectPath = EditorUtility.OpenFolderPanel("Select output folder", "Assets", "");
|
|
|
|
if (selectPath.StartsWith(Application.dataPath))
|
|
{
|
|
selectPath = "Assets" + selectPath.Substring(Application.dataPath.Length);
|
|
selectPath = selectPath.Replace('\\', '/');
|
|
if (selectPath.EndsWith("/") == false)
|
|
selectPath += "/";
|
|
options.OutputDirectory = selectPath;
|
|
}
|
|
else
|
|
{
|
|
EditorUtility.DisplayDialog("Error", $"Select directory under {Application.dataPath}", "OK");
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
options.AddressablesGroupName = EditorGUILayout.TextField("Addressables Group", options.AddressablesGroupName);
|
|
|
|
|
|
// It stores return value from foldout and uses it as a condition.
|
|
if (showFormat = EditorGUILayout.Foldout(showFormat, "Compress Format"))
|
|
{
|
|
EditorGUI.indentLevel += 1;
|
|
options.PCCompression = PopupFormat("PC & Console", (TextureFormat)options.PCCompression);
|
|
options.WebGLCompression = PopupFormat("WebGL", (TextureFormat)options.WebGLCompression);
|
|
options.AndroidCompression = PopupFormat("Android", (TextureFormat)options.AndroidCompression);
|
|
options.iOSCompression = PopupFormat("iOS", (TextureFormat)options.iOSCompression);
|
|
options.tvOSCompression = PopupFormat("tvOS", (TextureFormat)options.tvOSCompression);
|
|
EditorGUI.indentLevel -= 1;
|
|
}
|
|
}
|
|
|
|
private static TextureFormat PopupFormat(string label, TextureFormat format)
|
|
{
|
|
int selectIndex = 0;
|
|
//no matter the format exists or not.
|
|
Styles.SupportTextureFormatIndex.TryGetValue(format, out selectIndex);
|
|
selectIndex = EditorGUILayout.Popup(label, selectIndex, Styles.SupportTextureFormatStrings);
|
|
if (selectIndex < 0)
|
|
selectIndex = 0;
|
|
return Styles.SupportTextureFormats[selectIndex];
|
|
}
|
|
|
|
private string GetAddress(Object obj)
|
|
{
|
|
if (obj != null)
|
|
{
|
|
string assetPath = AssetDatabase.GetAssetPath(obj);
|
|
var split = assetPath.Split('/');
|
|
var name = split.Last();
|
|
assetPath = name.Split('.').First();
|
|
EditorGUIUtility.systemCopyBuffer = assetPath;
|
|
return assetPath;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private string GetAssetPath(Object obj)
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(obj);
|
|
|
|
if (string.IsNullOrEmpty(path) && PrefabUtility.GetPrefabInstanceStatus(obj) == PrefabInstanceStatus.Connected)
|
|
{
|
|
Object prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(obj);
|
|
path = AssetDatabase.GetAssetPath(prefab);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
}
|
|
} |