Всем привет! Пока я делал систему которая будет комбинировать по 100 объектов в 1 меш каждый кадр я наткнулся на такую проблему которая завела меня в тупик... С каждым скомбинированным мешем через mesh.combineMeshes создается новый объект, а значит новый VBO и т.е. это всё пихается в видео память (как написано в Statistics), но вроде ещё пихается в оперативную тоже. Дело в том что в окончательном варианте когда всё будет уже сделано, комбинирование мешей будет делаться каждые 2 - 5 секунды. И после большого времени игры видео и оперативная память полностью забьются. Делая Destroy объект из памяти не удаляется. И вообщем я совсем не знаю что делать. У меня 8 GB RAM и 4 GB video, и всё равно быстренько всё забивается! Что делать? Вообщем если у кого нибудь есть хотя бы какие нибудь идеи - пишите. Заранее спасибо.
Ranger, допустим здание состоит из кубов, в сумме получается 2000 вершин, при комбинировании создается один меш, тоже 2000к вершин, а те кубы отключаются (setEnable)
нее... ну посмотри по статистике.. я не помню как это назвается отображение DC и вершин.UnityStats.drawCalls ,UnityStats.triangles ,UnityStats.vertices
Я так понял, что запиливание в 1 меш было вызвано задачей оптимизации. Я не ковырял, но, мне кажется Юнитине будет осекать вершины внутри мешей.
Ranger, удалять со сцены эти кубы нельзя. Это для того что бы я потом мог удалить скомбинированный меш изменить что то в этих кубах и опять скомбинировать. setenable отключает рендеринг ,все скрипты и компоненты которые у объекта.
Сорри, я нормально с combineMeshes не разбирался, но все же четкое ощущение, что тут просто обычная утечка памяти. Т.е. мало удалить объект, нужно чтобы на массив его вершин, трисов и т.п. никто не ссылалася, иначе этот массив так и будет сидеть в памяти. Новое создание - новый массив, который вечно сидит в памяти. Тут нужно смотреть в первую очередь на архитектуру приложения, т.к. утечки на шарпе - это достаточно явная промашка программиста.
seaman, Ваши слова мне почти ничего не сказали понятного... Просто VBO этот , это как бы уникальный объект которого ещё небыло на сцене. После комбинирования он и создается. Даже если его удалить он ещё остается в памяти. Тут вот я и не знаю что делать. Как удалить объект отовсюду ? Со сцены, из памяти..
seaman, Тут скорее задвоение (затроение, и т.д.) robertono не удаляет базовые меши после создания большого меша
robertono, посмоьтри и попробуй следующее: 1. возможна ошибка в скрипте : в новый меш входит старый меш + все(!) меши объектов, а не только новорожденные
2. оценивай количество памяти соразмерно генерации объектов. количество занятой памяти должно рости линейно от количества объектов. если растет по экспоненте, значит ты дублируешь данные в меш. (см п.1)
3. Посмотри Destroy чего ты делаешь. Возможно, что Destroy убивает объект, а не удаляет меш. Сделай руками вызов дестроя меша из скрипта. проверь экземпляр меша он д.б. равен null
Сообщение отредактировал Ranger - Вторник, 12 Ноября 2013, 05:31
1. возможна ошибка в скрипте : в новый меш входит старый меш + все(!) меши объектов, а не только новорожденные
Комбинирую я меш так: сначала беру первые 100 кубов из массива, комбинирую, потом следующие 100 + тот который уже скомбинирован и так делается меш.
ЦитатаRanger ()
Возможно, что Destroy убивает объект, а не удаляет меш.
Destroy(GameObject) Удаляю тот объект, который в компонентах содержит Mesh Filter (здесь сам скомбинированный меш) и Mesh Renderer. Из памяти он не пропадает.
Удаляю тот объект, который в компонентах содержит Mesh Filter
Утечка - если на mesh этого Mesh Filter где-то есть ссылка он не удалится. Удалится объект, который удаляешь, компоненты, которые на нем висят, а mesh - нет. 1. Проверяешь нет ли на них ссылок. Если нашел - обнуляешь, или лучше перестраиваешь приложение так, чтобы этих ссылок просто не было. 2. Если найти не сумел. Пробуешь перед удалением объекта обнулить все ресурсоемкие его части MeshFilter.mesh = null. Это костыль и лучше делать как в 1.
1. Проверяешь нет ли на них ссылок. Если нашел - обнуляешь, или лучше перестраиваешь приложение так, чтобы этих ссылок просто не было.
Нету, всё было обнулено.
Не то это всё. Прочитал на ансверсах юнити3д.ком о таких вещах, как : не нужно делать new Mesh(); . на unity3d.ru прочитал что нужно делать resources.unloadunusedassets(); и что это связанно с VBO. Однако это не повлияло ни на что. Память всё равно заполняется. Сейчас повторю ещё тесты с Resources. Говорят это как раз то что очищает память после Destory и делать это нужно желательно
ЦитатаRanger ()
я не помню как это назвается отображение DC и вершин.UnityStats.drawCalls ,UnityStats.triangles ,UnityStats.vertices
что это ? В юнити такого нету. А кстати, можно ли как то в OnGUI вывести из статистики что то конкретное? Например сколько сейчас памяти видео заполнено.
Добавлено (14.11.2013, 23:31) --------------------------------------------- Повторил тесты с unloadunusedassets : потрясающая вещь, очищает кучу памяти, одно здание с примерно 2300 кубов комбинировалось по 100 кубов за 1 кадр а потом в один целый меш и без очистки набирается 42 МБ , а после очистки 8 МБ. Конечно прирост большой На одну проблему меньше, вопросы выше ещё в силе, тему пока не заканчиваю.
int i = 1; while (i < meshFilters.Length) { combine[i].mesh = meshFilters[i].sharedMesh; combine[i].transform = meshFilters[i].transform.localToWorldMatrix; i++; } NewMesh = new Mesh(); NewMesh.CombineMeshes(combine); Destroy(Mymesh.sharedMesh); //!!!!!!!!!!! =|=============> здесь грохается старый меш return NewMesh;
}
Из полезного тебе здесь только
Код
Destroy(Mymesh.sharedMesh); //!!!!!!!!!!! =|=============> здесь грохается старый меш
что это ? В юнити такого нету. А кстати, можно ли как то в OnGUI вывести из статистики что то конкретное? Например сколько сейчас памяти видео заполнено.
У них какая то хрень с документацией.. Через скрипт референс не ищется. Еле нашел через гугл (возможно в новой версии не поддерживается): Правда страница на японском http://docs-jp.unity3d.com/Documentation/ScriptReference/UnityStats.html
Сообщение отредактировал Ranger - Пятница, 15 Ноября 2013, 05:39
Ranger, возможно не ищется потому что в референсе только UnityEngine. Ещё есть UnityEditor, где и есть этот UnityStats. UnityEditor.UnityStats.vboTotal Можно прописать в using.
Накидал код по быстрому взяв за основу пример. Сам меши не ковырял, но ,думаю, ты допилишь до рабочего.
Незнаю зачем Вы сделали для меня этот пример... Хотя я вроде не говорил что всё уже готово.. Мой код-велосипед из 318 строчек, выполняющий покадровое комбинирование. Я всё таки выложу это.
Код
using UnityEngine; using System; using System.Collections; using System.Collections.Generic;
public class CombineQueue : MonoBehaviour { public GameObject destroyObj; public int maxFrame = 100; //Указывает максимальное колличество комбинирования в 1 кадр int curFrame; //Указывает сколько будет обработано объектов в текущий кадр. Current Frame
List<BuildingClass> InstanceList = new List<BuildingClass> (); List<GameObject> Buildings = new List<GameObject> (); List<CombineInstance> Instance = new List<CombineInstance> (); List<Transform> Objects = new List<Transform> ();
public void AddToQueue (CombineInstance[] insList, Transform[] cube, GameObject caller) { //Debug.Log ("New instances! Count : " + insList.Length + " . Caller: " + caller.name); for (int i = 0; i < insList.Length - 1; i++) { //Внимание! Мы не проверяем последний item, так как он дубликат первого item!!! BuildingClass newInstance = new BuildingClass (); newInstance.instance = insList [i]; newInstance.cube = cube [i]; newInstance.gameobject = caller; InstanceList.Add (newInstance); } /* foreach (CombineInstance ins in insList) { BuildingClass newInstance = new BuildingClass (); newInstance.instance = ins; newInstance.gameobject = caller; InstanceList.Add (newInstance); } */ //Debug.Log ("Instances added! Length: " + InstanceList.Count); }
IEnumerator CoroutineCombine (GameObject go, CombineInstance[] instances, int verts) { BuildingMeshes bMeshes = go.GetComponent<BuildingMeshes> (); if (bMeshes.Meshes.Count == 0) { //Если у здания нету ещё не одной скомбинированной группы GameObject obj = new GameObject (); obj.AddComponent ("MeshFilter"); obj.AddComponent ("MeshRenderer"); obj.renderer.material = emptyMat; obj.transform.parent = go.gameObject.transform; obj.name = go.name + "_Combined_1"; obj.GetComponent<MeshFilter> ().mesh = new Mesh (); obj.GetComponent<MeshFilter> () .mesh.CombineMeshes (instances); obj.AddComponent ("MeshCollider"); obj.gameObject.active = true;
CombinedMeshes newInstance = new CombinedMeshes (); newInstance.mesh = obj; newInstance.vertices = obj.GetComponent<MeshFilter> ().mesh.vertexCount; bMeshes.Meshes.Add (newInstance); } else if (bMeshes.Meshes.Count > 0) { //Если у здания уже есть хотя бы одна скомбинированная группа int vCount = bMeshes.Meshes [bMeshes.Meshes.Count - 1].vertices; if (vCount < 60000) { if (60000 - vCount >= verts) { GameObject newOne = bMeshes.Meshes [bMeshes.Meshes.Count - 1].mesh; //bMeshes.Meshes[bMeshes.Meshes.Count -1].mesh.GetComponent<MeshFilter> () .mesh.CombineMeshes (AddOne(instances,newOne)); //bMeshes.Meshes[bMeshes.Meshes.Count -1]
void Update () { if (Input.GetKeyDown (KeyCode.H)) { Resources.UnloadUnusedAssets(); }
//Считаем сколько объектов будет обработано в следующий кадр if (InstanceList.Count - 1 >= maxFrame) { curFrame = maxFrame - 1; curStatus = "maxFrame "; } else if (InstanceList.Count - 1 < maxFrame) { curFrame = InstanceList.Count - 1; curStatus = "elseFrame "; //Debug.Log(InstanceList.Count + " " + curFrame); }
if (InstanceList.Count != 0) {
//Считаем сколько разных групп for (int i = 0; i <= curFrame; i++) { GameObject go = InstanceList [i].gameobject; if (!Exists (go)) Buildings.Add (go); } //Пробуем работать с каждой группой foreach (GameObject go in Buildings) { Instance.Clear (); //Очищаем список для создания нового списка комбинирования Objects.Clear (); //Очищаем список для создания нового списка объектов (это для распределения по мешам в 60к вершин) for (int i = 0; i <= curFrame; i++) { //Копаем 100 первых BuildingClass объектов GameObject instanceObj = null; if (i < InstanceList.Count) instanceObj = InstanceList [i].cube.gameObject; else if (i >= InstanceList.Count) instanceObj = InstanceList [InstanceList.Count - 1].cube.gameObject;
void Combine (GameObject go, CombineInstance[] instances, int verts) { BuildingMeshes bMeshes = go.GetComponent<BuildingMeshes> (); if (bMeshes.Meshes.Count == 0) { //Если у здания нету ещё не одной скомбинированной группы GameObject obj = new GameObject (); obj.AddComponent ("MeshFilter"); obj.AddComponent ("MeshRenderer"); obj.renderer.material = emptyMat; obj.transform.parent = go.gameObject.transform; obj.name = go.name + "_Combined_1"; obj.GetComponent<MeshFilter> ().mesh = new Mesh (); obj.GetComponent<MeshFilter> () .mesh.CombineMeshes (instances); obj.AddComponent ("MeshCollider"); obj.gameObject.active = true;
CombinedMeshes newInstance = new CombinedMeshes (); newInstance.mesh = obj; newInstance.vertices = obj.GetComponent<MeshFilter> ().mesh.vertexCount; bMeshes.Meshes.Add (newInstance); } else if (bMeshes.Meshes.Count > 0) { //Если у здания уже есть хотя бы одна скомбинированная группа int vCount = bMeshes.Meshes [bMeshes.Meshes.Count - 1].vertices; if (vCount < 60000) { if (60000 - vCount >= verts) { GameObject newOne = bMeshes.Meshes [bMeshes.Meshes.Count - 1].mesh; //bMeshes.Meshes[bMeshes.Meshes.Count -1].mesh.GetComponent<MeshFilter> () .mesh.CombineMeshes (AddOne(instances,newOne)); //bMeshes.Meshes[bMeshes.Meshes.Count -1]
public class BuildingClass { public CombineInstance instance; public Transform cube; public GameObject gameobject; }
Добавляются туда меши с помощью функции AddToQueue, до этого обрабатывается в моём MeshMerger:
Код
using UnityEngine; using System.Collections; using System.Threading;
public class MeshMerger : MonoBehaviour { //Этот скрипт получает объект который нужно скомбинировать //Сортирует, делит по массивам, отправляет на переработку в CombineQueue
//Функция Combine с массивом объектов пока отключена! public void Combine(GameObject[] GameObjects,GameObject father){ for(int i = 0;i < GameObjects.Length;i++){ meshFilters[i] = GameObjects[i].GetComponent<MeshFilter> (); }
combine = new CombineInstance[meshFilters.Length]; int a = 0; while (a < meshFilters.Length) { combine [a].mesh = meshFilters [a].mesh; combine [a].transform = meshFilters [a].transform.localToWorldMatrix; //meshFilters [i].gameObject.active = false; a++; } //GetComponent<CombineQueue>().AddToQueue(combine,GameObjects,father); //Debug.Log("Request from CombineMesh. Sent request to CombineQueue as multiple objects"); } public void Combine (GameObject GameObj) { meshFilters = GameObj.GetComponentsInChildren<MeshFilter> (); cubes = GameObj.GetComponentsInChildren<Transform> (); cubes[0] = cubes[cubes.Length - 1]; cubes[cubes.Length - 1] = null; combine = new CombineInstance[meshFilters.Length]; int i = 0;
while (i < meshFilters.Length) { combine [i].mesh = meshFilters [i].mesh; combine [i].transform = meshFilters [i].transform.localToWorldMatrix; //meshFilters [i].gameObject.active = false; i++; } GetComponent<CombineQueue>().AddToQueue(combine,cubes,GameObj); //Debug.Log("Request from CombineMesh. Sent request to CombineQueue as one object"); } }
Если нужно скомбинировать меш, то просто нужно вызвать что то типо такого: GameObject.Find("Another Scripts").GetComponent<MeshMerger> ().Combine(gameObject);
Добавлено (15.11.2013, 17:26) --------------------------------------------- В скрипте CombineQueue очень много закомментированных строчек если что)