В предыдущем посте я рассказала про меши, но может встать вопрос, а как работать с мешами в Jobs? Ведь UnityEngine.Mesh - это managed тип, а мы хотим в Jobs, так еще и под Burst!
Для таких задач Unity добавила т.н. Extended Mesh API: они расширили API для Mesh и добавили поддержку Unity.Collections.
Вот небольшой пример как работать с мешами в Jobs:
Vertex - это кастомная структура вершины.
Для таких задач Unity добавила т.н. Extended Mesh API: они расширили API для Mesh и добавили поддержку Unity.Collections.
Вот небольшой пример как работать с мешами в Jobs:
// meshes - массив или список UnityEngine.Mesh
var meshDataArray = Mesh.AllocateWritableMeshData(meshes);
for (var i = 0; i < meshes.Length; i++)
{
var meshData = meshDataArray[i];
meshData.SetVertexBufferParams(vertexCount, Vertex.Layout);
meshData.SetIndexBufferParams(indexCount, IndexFormat.UInt32);
var vertexArray = meshData.GetVertexData<Vertex>();
var indexArray = meshData.GetIndexData<int>();
// какая-то логика
}
Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, meshes, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontResetBoneBounds);
Vertex - это кастомная структура вершины.
[StructLayout(LayoutKind.Explicit)]
[DebuggerDisplay("Position = {Position}, Normal = {Normal}")]
public readonly struct Vertex : IEquatable<Vertex>
{
public static readonly VertexAttributeDescriptor[] Layout =
{
new(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3)
};
[FieldOffset(0)]
public readonly float3 Position;
[FieldOffset(12)]
public readonly float3 Normal;
public Vertex(float3 position, float3 normal)
{
Position = position;
Normal = normal;
}
}
🔥6👏3😍3👍2
А прямо сейчас работаю над детерминированным инстансингом объектов. Пока есть проблема с этой детерминированностью, но думаю, скоро покажу результаты)👏
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥2☃1👀1
Сегодня видимо будет рекорд по постам в день) Но решая проблему с детерминированностью инстансинга я наткнулась на одной интересное свойство SDF и градиента SDF. Может кому-нибудь окажется полезным или хотя бы интересным.
Так вот, у меня проблема, что при изменении ландшафта (например когда копаем), ранее поставленные объекты пропадали. А пропадали они потому, что угол наклона поверхности мог измениться так, что мы там и не хотим больше инстансить. Например, странно видеть дерево на отвесной скале, правда же?
Но в исходном варианте то мы уже заинстансили, и при копании нам объект нужно просто опустить. А он взял и пропал :( Решение - использовать исходные нормали для проверки, но как их получить, если генерируем по текущему состоянию?
Оказывается, градиент в SDF показывает нам динамику не только в текущей точке пространства. И зная значение изменения (что мы знаем из набора операций над ландшафтом), можно вычислить исходную нормаль в совершенно другой точке пространства. Магия😧
Первое видео - текущие нормали, второе - исходные, третье - дельта.
*В комментариях еще видео)
Так вот, у меня проблема, что при изменении ландшафта (например когда копаем), ранее поставленные объекты пропадали. А пропадали они потому, что угол наклона поверхности мог измениться так, что мы там и не хотим больше инстансить. Например, странно видеть дерево на отвесной скале, правда же?
Но в исходном варианте то мы уже заинстансили, и при копании нам объект нужно просто опустить. А он взял и пропал :( Решение - использовать исходные нормали для проверки, но как их получить, если генерируем по текущему состоянию?
Оказывается, градиент в SDF показывает нам динамику не только в текущей точке пространства. И зная значение изменения (что мы знаем из набора операций над ландшафтом), можно вычислить исходную нормаль в совершенно другой точке пространства. Магия
Первое видео - текущие нормали, второе - исходные, третье - дельта.
*В комментариях еще видео)
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯8❤4🔥1
Генерация. LOD и чанки
В прошлый раз я остановилась на мешах. Если генерировать видимый мир (4х4х4 км) сразу весь, то мы получим 82 426 462 208 вокселей! Звучит очень тяжело, а еще сохранение накатывать на этот мир, а еще с сервером синхронизировать, совсем беда.
Поэтому первым делом надо разбить все пространство на чанки - кубики с размером 16х16х16 вокселей. Каждый чанк обрабатывается отдельно и параллельно. Данные также стараюсь сохранять относительно чанков и локальных координат, так легко понять что к чему относится. Но и в таком виде, мы не уменьшаем количество вокселей, а количество чанков равно 16 777 216.
Чтобы решить эту проблему я решила обратиться к LOD (Level of Details). И даже не ради уменьшения нагрузки на GPU при отрисовке (хотя этот эффект есть), а ради уменьшения кол-ва данных, которые надо обработать.
Все чанки я сгруппировала по LOD: вокруг игрока - уровень 0, чуть дальше - 1, потом - 2 и т.д. Каждый чанк все также имеет 16х16х16 вокселей, но их размер увеличивается! Поэтому чанк с LOD 1 в 8 (2^3) раз больше, чем с LOD 0! А значит мне надо меньшее количество чанков и вокселей, чтобы покрыть большее расстояние.
Чтобы "сшить" эти чанки, мне пришлось увеличить число вокселей до 17х17х17 в каждом чанке, чтобы они перекрывали друг друга. Также как и с вокселями, тут применяется Octree, чтобы отсечь те чанки, которые "пустые". Особенно помогает при перестроении мира при движении игрока.
На скриншоте показаны чанки с разным LOD. Игрок находится в центре.
В прошлый раз я остановилась на мешах. Если генерировать видимый мир (4х4х4 км) сразу весь, то мы получим 82 426 462 208 вокселей! Звучит очень тяжело, а еще сохранение накатывать на этот мир, а еще с сервером синхронизировать, совсем беда.
Поэтому первым делом надо разбить все пространство на чанки - кубики с размером 16х16х16 вокселей. Каждый чанк обрабатывается отдельно и параллельно. Данные также стараюсь сохранять относительно чанков и локальных координат, так легко понять что к чему относится. Но и в таком виде, мы не уменьшаем количество вокселей, а количество чанков равно 16 777 216.
Чтобы решить эту проблему я решила обратиться к LOD (Level of Details). И даже не ради уменьшения нагрузки на GPU при отрисовке (хотя этот эффект есть), а ради уменьшения кол-ва данных, которые надо обработать.
Все чанки я сгруппировала по LOD: вокруг игрока - уровень 0, чуть дальше - 1, потом - 2 и т.д. Каждый чанк все также имеет 16х16х16 вокселей, но их размер увеличивается! Поэтому чанк с LOD 1 в 8 (2^3) раз больше, чем с LOD 0! А значит мне надо меньшее количество чанков и вокселей, чтобы покрыть большее расстояние.
Чтобы "сшить" эти чанки, мне пришлось увеличить число вокселей до 17х17х17 в каждом чанке, чтобы они перекрывали друг друга. Также как и с вокселями, тут применяется Octree, чтобы отсечь те чанки, которые "пустые". Особенно помогает при перестроении мира при движении игрока.
На скриншоте показаны чанки с разным LOD. Игрок находится в центре.
🔥7🤯3❤🔥1🤗1
This media is not supported in your browser
VIEW IN TELEGRAM
И вот еще видео, на котором видно как меняются LOD чанков в рантайме, когда игрок перемещается.
Можно также заметить, как LOD "уничтожают" информацию, делая чанки более "размытыми" и менее детализированными. На это у меня есть идея, как увеличить детализированность, но пробовать воплотить в жизнь буду чуть позже)
Можно также заметить, как LOD "уничтожают" информацию, делая чанки более "размытыми" и менее детализированными. На это у меня есть идея, как увеличить детализированность, но пробовать воплотить в жизнь буду чуть позже)
🔥14
Сегодня расскажу немного об архитектурных моментах в генерации. Я хоть и использую ECS, но только для геймплейной части. Я не сторонница использовать один инструмент для решения всех задач, всегда стараюсь найти более оптимальное решение (хоть и не всегда получается).
Поэтому в генерации у меня ООП подход со своими плюсами и минусами. Почему именно он? Потому что мне нужна иерархия для модулей генерации. Вся логика генерации разбита на множество небольших кусочков, которые связаны между собой контрактами. Контракты тут - generic параметры - так что появляется иерархия через композицию.
Как это выглядит? Например модуль для вокселей:
Где
Исходное API конечно выглядит немного пугающе, но это плата за возможность компоновать модули как необходимо, причем не только из кода, но и из редактора.
В каждом модуле надо реализовать абстрактный метод OnExecute, в нем содержится вся логика генерации.
Причем на выходе надо указать не только сгенерируемые данные, но и результат генерации - успешно или нет. Если не успешно, то все сабмодули у этого модуля выполнены не будут. Таким образом можно отсекать куски генерации per chunk по различным условиям.
Все модули собираются в т.н. шаблон генерации, который выполняется на N потоках (в дополнение к Jobs) per chunk. На скриншотах редактор этого шаблона, который сделан на UI Toolkit.
Поэтому в генерации у меня ООП подход со своими плюсами и минусами. Почему именно он? Потому что мне нужна иерархия для модулей генерации. Вся логика генерации разбита на множество небольших кусочков, которые связаны между собой контрактами. Контракты тут - generic параметры - так что появляется иерархия через композицию.
Как это выглядит? Например модуль для вокселей:
public sealed class VoxelsBuildModule : BitmaskCachedChunkGenerationModule<VoxelData, IVoxelSubModule, IVoxelDataProcessor>
{
// some code here
}
Где
VoxelData - выходные данные, IVoxelSubModule - ограничение для сабмодулей, которые могут быть у этого модуля (они получат на вход VoxelData), ну а IVoxelDataProcessor - это абстракция для процессора данных после генерации.Исходное API конечно выглядит немного пугающе, но это плата за возможность компоновать модули как необходимо, причем не только из кода, но и из редактора.
В каждом модуле надо реализовать абстрактный метод OnExecute, в нем содержится вся логика генерации.
protected override ResultData<VoxelData> OnExecute(in Chunk chunk, in BorderMask mask, ref CachedValue<VoxelData> cachedValue, ref PerThreadData perThreadData)
{
}
Причем на выходе надо указать не только сгенерируемые данные, но и результат генерации - успешно или нет. Если не успешно, то все сабмодули у этого модуля выполнены не будут. Таким образом можно отсекать куски генерации per chunk по различным условиям.
Все модули собираются в т.н. шаблон генерации, который выполняется на N потоках (в дополнение к Jobs) per chunk. На скриншотах редактор этого шаблона, который сделан на UI Toolkit.
❤10🤩3🤝3👍1
Еще немножечко про архитектуру ☺️ Я уже в общих чертах описала модули генерации и шаблон генерации, которые используются уже в самом генераторе. А если быть конкретнее - в мире генерации, который объединяет множество аспектов генерации. Это и состояние чанков, и кэширование, и сама генерация и т.д.
Все эти аспекты связаны между собой, так что я не беспокоюсь о том, что для чанка у меня не будет кэша например. Если чанк раннее генерировался, то его кэш я получу и смогу решить, что делать дальше. Единственный аспект, который вынесен наружу - это аспект патчей.
Патчем я называю любой запрос на генерацию/перегенерацию мира. Он состоит из списка чанков, которые необходимо сгенерировать, и списка чанков, которые необходимо выгрузить (очистить кэш и удалить состояние). Патч может быть как добавляющим новые чанки к существующим, так и требующим очистку кэша и полную перегенерацию указанных чанков.
Например при движении игрока мы явно хотим добавлять новые чанки, не затрагивая существующие. А вот при копании мы уже хотим полностью перегенерировать чанк, так как у него появляются структурные изменения.
Я старалась сделать понятное API для работы с патчами, потому что я сама забываю о том, как работать с тем, что написала🫥
Создание патча выглядит просто:
Также и применение:
Где на выход отдается
Все эти аспекты связаны между собой, так что я не беспокоюсь о том, что для чанка у меня не будет кэша например. Если чанк раннее генерировался, то его кэш я получу и смогу решить, что делать дальше. Единственный аспект, который вынесен наружу - это аспект патчей.
Патчем я называю любой запрос на генерацию/перегенерацию мира. Он состоит из списка чанков, которые необходимо сгенерировать, и списка чанков, которые необходимо выгрузить (очистить кэш и удалить состояние). Патч может быть как добавляющим новые чанки к существующим, так и требующим очистку кэша и полную перегенерацию указанных чанков.
Например при движении игрока мы явно хотим добавлять новые чанки, не затрагивая существующие. А вот при копании мы уже хотим полностью перегенерировать чанк, так как у него появляются структурные изменения.
Я старалась сделать понятное API для работы с патчами, потому что я сама забываю о том, как работать с тем, что написала
Создание патча выглядит просто:
public static unsafe WorldPatch Create(byte sourceId, NativeArray<Chunk> chunksToBuild, NativeArray<BorderMask> borderBitmasks, NativeArray<Chunk> chunksToRelease, Allocator allocator)
{
}
Также и применение:
public static PatchHandle ApplyPatch(this GenerationWorld world, ref WorldPatch patch, PatchOptions options = PatchOptions.Default)
{
}
Где на выход отдается
PatchHandle, через который можно проверить, применился ли патч, сколько осталось чанков (полезно для экрана загрузки) и т.п.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2🥰2
Кстати, пока писала предыдущий пост, заметила, что патч создается через
Все дело в том, что все больше и больше добавляются новые типы аллокаторов (тот же Rewind Allocator например), которые не представлены в enum. И используя enum мы просто теряем их поддержку. Я вот тоже использую кастомный аллокатор на базе smmalloc.
Единственный, не очень приятный, момент:
Создание:
Dispose:
И Dispose в Jobs использовать не получится! По этой причине Unity использует
Allocator! Allocator - это enum с перечислением нескольких аллокаторов, но не сами аллокаторы. Есть рекомендация использовать AllocatorManager.AllocatorHandle, и создавать все коллекции и выделять память через него.Все дело в том, что все больше и больше добавляются новые типы аллокаторов (тот же Rewind Allocator например), которые не представлены в enum. И используя enum мы просто теряем их поддержку. Я вот тоже использую кастомный аллокатор на базе smmalloc.
Единственный, не очень приятный, момент:
NativeArray<T> по умолчанию не поддерживает AllocatorManager.AllocatorHandle! Для его создания необходимо использовать API CollectionHelper.Создание:
var array = CollectionHelper.CreateNativeArray<int>(1000, /*какой-то аллокатор*/);
Dispose:
CollectionHelper.DisposeNativeArray(array, /*какой-то аллокатор*/);
И Dispose в Jobs использовать не получится! По этой причине Unity использует
NativeList<T> или UnsafeList<T>, который легко сконвертировать во временный NativeArray<T>.👍3
Сегодня также закончила первую версию генератора деталей 💃 Под деталями имею в виду всякие мелкие объекты без коллайдеров: камушки, палочки, веточки и все такое)
Так как эти объекты зависят от поверхности ландшафта, то генерирую на основе данных о треугольниках. И тут важно перевести позиции вершин из world space в voxel space, тогда результаты всегда будут одинаковы для конкретного вокселя, даже если поверхность изменилась. Единственный момент был - нормали, но как я уже писала, нормали можно восстановить.
Рисуются детали с помощью BRG, который отлично подходит для данной задачи. BatchRendererGroup (BRG) - это API для ручного батчинга и создания команд отрисовки. Попробую кратенько рассказать.
Регистрируем меш и материал, создаем буфер, в котором будут храниться данные для шейдера (например матрицы трансформации) и получаем
Дальше необходимо для каждого
В конце, для каждого
В ходе исследований BRG у меня появился инструмент, который я использую в этом проекте. Он простенький и его нельзя назвать универсальным, но если кому-то будет интересно, то может стать неплохой стартовой точкой.
На одном скриншоте показан редактор деталей для каждого воксель материала, а на втором - Frame Debugger.
Так как эти объекты зависят от поверхности ландшафта, то генерирую на основе данных о треугольниках. И тут важно перевести позиции вершин из world space в voxel space, тогда результаты всегда будут одинаковы для конкретного вокселя, даже если поверхность изменилась. Единственный момент был - нормали, но как я уже писала, нормали можно восстановить.
Рисуются детали с помощью BRG, который отлично подходит для данной задачи. BatchRendererGroup (BRG) - это API для ручного батчинга и создания команд отрисовки. Попробую кратенько рассказать.
Регистрируем меш и материал, создаем буфер, в котором будут храниться данные для шейдера (например матрицы трансформации) и получаем
BatchID.Дальше необходимо для каждого
BatchID и соответствующего ему буфера данных провести culling - собрать индексы только тех инстансов, которые видны через камеру.В конце, для каждого
BatchID, если для него есть видимые инстансы, необходимо создать команды отрисовки и отправить их на выполнение.В ходе исследований BRG у меня появился инструмент, который я использую в этом проекте. Он простенький и его нельзя назвать универсальным, но если кому-то будет интересно, то может стать неплохой стартовой точкой.
На одном скриншоте показан редактор деталей для каждого воксель материала, а на втором - Frame Debugger.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5❤4
После деталей приступила к траве! Генерация травы очень похожа на генерацию деталей, но есть и отличия, так что придется хорошенько поработать 💃
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5⚡1
Media is too big
VIEW IN TELEGRAM
Первая версия травы! 🙌 Выглядит кривовато, буду еще дорабатывать)
За счет уже готового процесса добавления новых модулей генерации, это заняло всего один день. Правда много т.н. бойлерплейта, хмм, есть о чем подумать😶
За счет уже готового процесса добавления новых модулей генерации, это заняло всего один день. Правда много т.н. бойлерплейта, хмм, есть о чем подумать
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤1
Чуть ранее я немного писала про архитектуру генератора и упомянула, но не рассказала, т.н. процессоры данных. Что это такое? Попробую объяснить.
Генератор и модули генерации отвечают только за генерацию данных в каком-либо формате. Генератор ландшафта не генерирует Mesh, он генерирует только набор вершин и описание треугольников. Генератор деталей или травы не генерирует сами детали или траву, он генерирует только матрицы трансформаций. То есть генератор - это речь только про самый минимум данных.
А как тогда получать ландшафт? Камушки? Травку и остальные объекты окружения? Да еще и с материалами!
Вот тут и вступают в игру эти процессоры данных, которые умеют переводить данные из формата от генератора в формат, который нужен для представления. Например, для ландшафта процессинг занимает несколько шагов:
- конвертирование данных о треугольниках в
- генерация материала для каждого чанка и всех данных которые необходимы для этого материала (текстурные массивы, различные буферы и т.п.);
- отправка данных отображения (меш, материал, настройки рендеринга и т.п.) в различные системы из мира ECS, которые уже знают как из этого собрать сущность.
Процессор создать достаточно просто: наследуемся от базового класса, указываем какие данные мы хотим обрабатывать и описываем логику обработки.
Потом указываем в шаблоне генерации (на скриншоте) нужный процессор.
Генератор и модули генерации отвечают только за генерацию данных в каком-либо формате. Генератор ландшафта не генерирует Mesh, он генерирует только набор вершин и описание треугольников. Генератор деталей или травы не генерирует сами детали или траву, он генерирует только матрицы трансформаций. То есть генератор - это речь только про самый минимум данных.
А как тогда получать ландшафт? Камушки? Травку и остальные объекты окружения? Да еще и с материалами!
Вот тут и вступают в игру эти процессоры данных, которые умеют переводить данные из формата от генератора в формат, который нужен для представления. Например, для ландшафта процессинг занимает несколько шагов:
- конвертирование данных о треугольниках в
UnityEngine.Mesh через Extended Mesh API;- генерация материала для каждого чанка и всех данных которые необходимы для этого материала (текстурные массивы, различные буферы и т.п.);
- отправка данных отображения (меш, материал, настройки рендеринга и т.п.) в различные системы из мира ECS, которые уже знают как из этого собрать сущность.
Процессор создать достаточно просто: наследуемся от базового класса, указываем какие данные мы хотим обрабатывать и описываем логику обработки.
public class SurfaceDataProcessor : BaseDataProcessor<SurfaceData>, ISurfaceDataProcessor
{
protected sealed override void OnDataAdded(in Chunk chunk, ref SurfaceData data){}
protected sealed override void OnDataRemoved(in Chunk chunk){}
}
Потом указываем в шаблоне генерации (на скриншоте) нужный процессор.
👍8👎1
Разработка игр это часто не просто использование одного инструмента. В моем случае приходится залезать и в С++, чтобы дописывать то, чего не хватает 🧂
Все думаю, как бы перевести это на Unity.Mathematics (и на C# соответственно), и под Burst. А еще кодоген! Чтобы количество вызовов уменьшить и убрать абстракции. Пока это только мечта...😢
Все думаю, как бы перевести это на Unity.Mathematics (и на C# соответственно), и под Burst. А еще кодоген! Чтобы количество вызовов уменьшить и убрать абстракции. Пока это только мечта...
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9😢3🌚1
Все еще вожусь с травой 👏
Поняла, что не хватает LOD для деталей и травы. Надо будет запланировать добавить поддержку в LOD для BRG.
Также очень не хватает смешивания травы и ландшафта, слишком резкая граница как по мне. Но это визуал, над которым я еще совсем не работала)
Поняла, что не хватает LOD для деталей и травы. Надо будет запланировать добавить поддержку в LOD для BRG.
Также очень не хватает смешивания травы и ландшафта, слишком резкая граница как по мне. Но это визуал, над которым я еще совсем не работала)
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10
Media is too big
VIEW IN TELEGRAM
Трава кстати уже шевелится 💃 Я взяла ассеты из сэмплов HDRP, чтобы сейчас немного укориться ☺️
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤3👎1
В комментариях я обещала рассказать про основные структуры данных, о которых я уже упоминала 💃
Воксель - просто информация о пространстве: density и gradient + информация по визуализации (индексы материалов).
Позицию в вокселе я не храню, так как доступ к ним и так через позицию, и хранятся они в отношении позиция вокселя -> воксель. Но это не HashMap! А обычный массив с вычислением индекса из позиции:
Чанк - тут еще меньше информации: позиция и lod!
Вся идея состоит в том, чтобы из любой точки кода можно было сказать: "я хочу сгенерировать пространство для вот этой позиции с таким-то уровнем детальности". Ну и еще отсюда вытекает множество плюсов в виде хранения данных в формате AOS (кэш-able), легковесность, расширяемость (сам чанк не знает, что в нем хранится, он выступает только в качестве ключа).
Octree - фактически, дерево у меня представлено в виде HashMap, где ключ - просто число без знака, а значение - нода с какой-либо информацией.
Ключ - просто набор бит (причем используются первые 30 бит). Зная, что каждая нода разбивается на 8 нод, на каждый уровень можно выделить по 3 бита (2^3 = 8). Максимальная глубина соответственно равна 10, чего мне хватает с запасом. Чтобы получить значение ребенка:
Немного побитовых операций, а уже не надо в ноде хранить ссылки на другие ноды☺️ Я после этого начала использовать побитовые операции везде где надо и не надо 🤭
Воксель - просто информация о пространстве: density и gradient + информация по визуализации (индексы материалов).
public readonly struct Voxel : IEquatable<Voxel>
{
public readonly float3 Gradient;
public readonly half Density;
...
public readonly FixedVoxelType4 VoxelTypes;
...
}
Позицию в вокселе я не храню, так как доступ к ним и так через позицию, и хранятся они в отношении позиция вокселя -> воксель. Но это не HashMap! А обычный массив с вычислением индекса из позиции:
math.dot(coordinate, new int3(1, dimension.x, dimension.x * dimension.y)); (когда математику вдруг можно применить и в таких случаях).Чанк - тут еще меньше информации: позиция и lod!
public readonly struct Chunk : IEquatable<Chunk>
{
public readonly int3 Position;
public readonly ChunkLod Lod;
}
Вся идея состоит в том, чтобы из любой точки кода можно было сказать: "я хочу сгенерировать пространство для вот этой позиции с таким-то уровнем детальности". Ну и еще отсюда вытекает множество плюсов в виде хранения данных в формате AOS (кэш-able), легковесность, расширяемость (сам чанк не знает, что в нем хранится, он выступает только в качестве ключа).
Octree - фактически, дерево у меня представлено в виде HashMap, где ключ - просто число без знака, а значение - нода с какой-либо информацией.
public readonly struct LocationCode : IEquatable<LocationCode>
{
public readonly uint Value;
}
Ключ - просто набор бит (причем используются первые 30 бит). Зная, что каждая нода разбивается на 8 нод, на каждый уровень можно выделить по 3 бита (2^3 = 8). Максимальная глубина соответственно равна 10, чего мне хватает с запасом. Чтобы получить значение ребенка:
(source.Value << 3) | childIndex, где childIndex - значение от 0 до 7. Чтобы получить значение родителя: code >> 3. Немного побитовых операций, а уже не надо в ноде хранить ссылки на другие ноды
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16❤9👏3🤯2
Когда немного переборщила с травой 🤭
Пофиксила заполнение буфера, теперь рисуется вся трава, причем довольно быстро (30+ фпс есть). Но если глянуть в Frame Debugger, то можно увидеть сколько на самом деле рисуется инстансов - 26473! А вершины и индексы определяются десятками миллионов!
Как рисовать такие объемы? Например с помощью GPU Instancing. Но еще лучше - сгруппировать все меши и материалы и использовать Batch Renderer Group! Я использую свой инструмент - BRG Container.
Следующий шаг - добавить поддержку LOD. А то и выглядит так себе при движении, и треугольников нууу слишком уж много😔
Пофиксила заполнение буфера, теперь рисуется вся трава, причем довольно быстро (30+ фпс есть). Но если глянуть в Frame Debugger, то можно увидеть сколько на самом деле рисуется инстансов - 26473! А вершины и индексы определяются десятками миллионов!
Как рисовать такие объемы? Например с помощью GPU Instancing. Но еще лучше - сгруппировать все меши и материалы и использовать Batch Renderer Group! Я использую свой инструмент - BRG Container.
Следующий шаг - добавить поддержку LOD. А то и выглядит так себе при движении, и треугольников нууу слишком уж много
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16😱4❤2✍1