Messages in this channel will be automatically deleted after 1 month
Messages in this channel will no longer be automatically deleted
Media is too big
VIEW IN TELEGRAM
Ну вот, пришло время написать первый пост по игре, над которой я работаю уже достаточно много времени.
Что за игра, в каком жанре, чем отличается и многие другие моменты будут описаны чуточку позже. А пока, делюсь результатами по первой фиче, которую вообще можно показать (а не только рассказать про код, технологии и фреймворки😄 ).
Я наконец-то реализовала возможность изменять ландшафт, что позволяет, например, вырыть пещерку (ура!💃 )
Что за игра, в каком жанре, чем отличается и многие другие моменты будут описаны чуточку позже. А пока, делюсь результатами по первой фиче, которую вообще можно показать (а не только рассказать про код, технологии и фреймворки
Я наконец-то реализовала возможность изменять ландшафт, что позволяет, например, вырыть пещерку (ура!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20
Channel name was changed to «Infinity World (Дневники разработчицы)»
Комментарии открыла, должно работать, но к старым постам это не относится :( Но можете и под этим оставить 💃
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰3🆒2👍1
Также присоединяйтесь к чату этой группы https://t.me/+P2tyt0x2ccI4M2Zi (в шапке канала ссылка на чат также есть). Там можно в том числе и мне задавать вопросы 🥔
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Infinity World (dev) Chat
You’ve been invited to join this group on Telegram.
🥰5
Меня в комментариях спрашивали, на чем базируется копание ландшафта, поэтому я решила немного рассказать о своем фреймворке прежде, чем начну вести сами дневники разработки по текущим задачам.
Я не использую уже готовые решения по той причине, что они или медленные или совсем не гибкие, а для реализации моих идей нужно и то и то) Я также не использую стандартный ландшафт от Unity, так как это совсем не про бесконечную бесшовную генерацию. По этой причине я решила написать свое решение.
С самого начала я определилась с некоторыми технологиями, которые буду использовать - Jobs + Burst. Эта связка дает слишком много, чтобы не быть использованной. Но, по ходу моих постов, вы скорее всего задатитесь вопрос: "А почему не GPGPU?". И ответ тут не так уж чтобы и простой. Одна из итераций у меня была на Compute Shaders, результаты по производительности впечатляющие, пока не начинаешь передавать данные (сгенерированные меши) с GPU на CPU. А это необходимо хотя бы в рамках построения физических коллайдеров. Также, вычислительные шейдеры - это не про гибкость, хотя часть моментов, например граф генерации шума, можно решить через кодогенерацию. Но я же вроде бы игру делаю, а не инструмент под Unity?👏
Другая часть технического стэка была выбрана по ходу исследований и множества итераций. Если Jobs + Burst я использую для генерации, то что для представления?
С самого начала я думала использовать подход ECS, уж очень он гибкий по сравнению с классическим ООП. Но какой фреймворк взять? Вначале я взяла Leo ECS Lite (не реклама), с которым я раннее работала и который мне очень понравился своей простотой. Но потом, когда я изучала BRG и интегрировала его к себе в проект, мне подсказали, что UECS из пакета DOTS, вполне себе уже используемый (все мы знаем про UECS, да? 🤪 ). И я, соблазнившись избавлением от GameObject, получением BRG из коробки для всего, что я захочу отрисовать, перешла на него. Хотя тот же BRG все равно использую в задачах, где даже entity можно не создавать.
Еще работая "на дядю" я мечтала, что не буду использовать uGUI (не буду рассказывать почему, подозреваю, что вы и так знаете), а возьму что-нибудь WPF-like. Поэтому в своей игре я сразу сделала ставку на NoesisGUI - WPF-like фреймворк с MVVM из коробки. Сейчас конечно уже появился UIToolkit, и даже с нормальными биндингами (те, которые runtime), но хотелось более мощное решение. Кстати, Baldur's Gate 3 тоже использует NoesisGUI☺️ .
Что касается сетевой части (да-да, она уже в каком-то виде даже есть, и изменение ландшафта синхронизируется между сервером и клиентами), выбора у меня было немного, раз я на UECS - Netcode for Entities. Авторитарный сервер (вместо p2p) конечно усложняет жизнь, но примеров достаточно, а также есть доброжелательное комьюнити. О том, как я решила задачу синхронизации ландшафта (как генерации так и изменения) я опишу чуть позже🫥
Итого, мой основной технический стэк выглядит вот так:
- Unity
- DOTS (Jobs, Burst, UECS, BRG)
- Netcode for Entities
- NoesisGUI
Я не использую уже готовые решения по той причине, что они или медленные или совсем не гибкие, а для реализации моих идей нужно и то и то) Я также не использую стандартный ландшафт от Unity, так как это совсем не про бесконечную бесшовную генерацию. По этой причине я решила написать свое решение.
С самого начала я определилась с некоторыми технологиями, которые буду использовать - Jobs + Burst. Эта связка дает слишком много, чтобы не быть использованной. Но, по ходу моих постов, вы скорее всего задатитесь вопрос: "А почему не GPGPU?". И ответ тут не так уж чтобы и простой. Одна из итераций у меня была на Compute Shaders, результаты по производительности впечатляющие, пока не начинаешь передавать данные (сгенерированные меши) с GPU на CPU. А это необходимо хотя бы в рамках построения физических коллайдеров. Также, вычислительные шейдеры - это не про гибкость, хотя часть моментов, например граф генерации шума, можно решить через кодогенерацию. Но я же вроде бы игру делаю, а не инструмент под Unity?
Другая часть технического стэка была выбрана по ходу исследований и множества итераций. Если Jobs + Burst я использую для генерации, то что для представления?
С самого начала я думала использовать подход ECS, уж очень он гибкий по сравнению с классическим ООП. Но какой фреймворк взять? Вначале я взяла Leo ECS Lite (
Еще работая "на дядю" я мечтала, что не буду использовать uGUI (не буду рассказывать почему, подозреваю, что вы и так знаете), а возьму что-нибудь WPF-like. Поэтому в своей игре я сразу сделала ставку на NoesisGUI - WPF-like фреймворк с MVVM из коробки. Сейчас конечно уже появился UIToolkit, и даже с нормальными биндингами (те, которые runtime), но хотелось более мощное решение. Кстати, Baldur's Gate 3 тоже использует NoesisGUI
Что касается сетевой части (да-да, она уже в каком-то виде даже есть, и изменение ландшафта синхронизируется между сервером и клиентами), выбора у меня было немного, раз я на UECS - Netcode for Entities. Авторитарный сервер (вместо p2p) конечно усложняет жизнь, но примеров достаточно, а также есть доброжелательное комьюнити. О том, как я решила задачу синхронизации ландшафта (как генерации так и изменения) я опишу чуть позже
Итого, мой основной технический стэк выглядит вот так:
- Unity
- DOTS (Jobs, Burst, UECS, BRG)
- Netcode for Entities
- NoesisGUI
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20❤11👍6👎1
Меня еще спросили, можно ли насквозь "прокопать" ландшафт. И мой ответ - да! Немного видео по фиче изменения ландшафта. И можно не только "копать", но и "возводить" 💃
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍6❤2👎1
Генерация. Воксели
"Весь мир должен генерироваться" - моя первая мысль, когда я только задумывалась об игре)
И прежде чем генерировать всякие красивости и бегающих за игроком лисичек, надо было сгенерировать ландшафт. Самое первое решение, которое у меня получилось, было очень и очень простым: генерируем простенький шум (Perlin например), где значение шума - оффсет для вершин. Дальше двигаем вершины, например в Compute Shaders, и получаем ландшафт. Такой метод используется в Valheim. Выглядит хорошо, если увеличить плотность сетки мешей, работает очень быстро, легко распараллелить. Но есть и один очень большой минус - ландшафт не может иметь сдвиги по горизонтали, а значит никаких пещер, нависающих скал и прочих деталей, которых мне так хотелось.
Поняв, что решение в лоб мне не подходит, я начала искать другие методы генерации. И вскоре пришла к идее вокселей. Самые популярные примеры воксельных миров - Minecraft и No Man's Sky (а сейчас еще и Enshrouded).
Что такое воксель? Его можно описать как куб пространства с каким-либо значением. Прямой сосед вокселю - пиксель, который живет в 2D мире, тогда как воксель - в 3D мире. В моей задаче каждый воксель хранит значение с плавающей запятой со знаком, то есть обычный float. Это значение означает дистанцию до поверхности, где отрицательное значение - мы под поверхностью, а положительное - над ней. Значение 0 же является самой поверхностью. Такое поле дистанций имеет свое название - Signed Distance Field (SDF). Хотя во многих источниках это значение называется плотностью (density), но мне лично помогло осознать алгоритм именно через дистанцию.
Также воксель еще хранит значение градиента SDF. Что такое градиент? Это вектор изменения значений (дистанций) в каждом вокселе. Чтобы его вычислить нам необходимо знать соседние значения SDF (это уже затык для параллелизации в будущем). Но в общем случае вычисление выглядит вот так:
На основе градиента вычисляются как позиции вершин, так и их нормали, но об этом расскажу в следующих постах. Также и про генерацию шума расскажу чуть позже)
"Весь мир должен генерироваться" - моя первая мысль, когда я только задумывалась об игре)
И прежде чем генерировать всякие красивости и бегающих за игроком лисичек, надо было сгенерировать ландшафт. Самое первое решение, которое у меня получилось, было очень и очень простым: генерируем простенький шум (Perlin например), где значение шума - оффсет для вершин. Дальше двигаем вершины, например в Compute Shaders, и получаем ландшафт. Такой метод используется в Valheim. Выглядит хорошо, если увеличить плотность сетки мешей, работает очень быстро, легко распараллелить. Но есть и один очень большой минус - ландшафт не может иметь сдвиги по горизонтали, а значит никаких пещер, нависающих скал и прочих деталей, которых мне так хотелось.
Поняв, что решение в лоб мне не подходит, я начала искать другие методы генерации. И вскоре пришла к идее вокселей. Самые популярные примеры воксельных миров - Minecraft и No Man's Sky (а сейчас еще и Enshrouded).
Что такое воксель? Его можно описать как куб пространства с каким-либо значением. Прямой сосед вокселю - пиксель, который живет в 2D мире, тогда как воксель - в 3D мире. В моей задаче каждый воксель хранит значение с плавающей запятой со знаком, то есть обычный float. Это значение означает дистанцию до поверхности, где отрицательное значение - мы под поверхностью, а положительное - над ней. Значение 0 же является самой поверхностью. Такое поле дистанций имеет свое название - Signed Distance Field (SDF). Хотя во многих источниках это значение называется плотностью (density), но мне лично помогло осознать алгоритм именно через дистанцию.
Также воксель еще хранит значение градиента SDF. Что такое градиент? Это вектор изменения значений (дистанций) в каждом вокселе. Чтобы его вычислить нам необходимо знать соседние значения SDF (это уже затык для параллелизации в будущем). Но в общем случае вычисление выглядит вот так:
public float3 ComputeGradient(float x, float y, float z, int seed)
{
var x1 = ComputeDensity(x + 1, y, z, seed);
var x2 = ComputeDensity(x - 1, y, z, seed);
var y1 = ComputeDensity(x, y + 1, z, seed);
var y2 = ComputeDensity(x, y - 1, z, seed);
var z1 = ComputeDensity(x, y, z + 1, seed);
var z2 = ComputeDensity(x, y, z - 1, seed);
return math.normalize(new float3(x1 - x2, y1 - y2, z1 - z2));
}
На основе градиента вычисляются как позиции вершин, так и их нормали, но об этом расскажу в следующих постах. Также и про генерацию шума расскажу чуть позже)
🔥12🤝4👍2👎1
Генерация. Шум
Продолжаю рассказывать про генерацию, и прежде чем перейти к генерации геометрии, наверное стоит рассказать про шум. Тем более тут есть что показать интересного)
Для воксельных миров подойдет любой непрерывный шум. Это может быть всеми известный шум Перлина, или какой-нибудь Simplex. Хотя подойдет даже просто обычная синусоида, главное выбрать несколько итераций с разной периодичностью и сложить вместе. Но, я человек-визуал, и мне сложно через числа что-то собрать адекватное🤪 , поэтому с самого начала я ориентировалась на визуальные инструменты и граф шума.
Под графом шума я подразумеваю именно набор нод и связей между собой. Чтобы упростить себе работу, я взяла уже готовое решение - FastNoise2. Это С++ библиотека, которая содержит множество API для генерации разнообразного шума, причем с поддержкой SIMD (хотя тут не все так просто). И самое главное - предоставляет граф из коробки!
Не все так просто с SIMD у меня по той причине, что я пишу на C# (привет P/Invoke) и использую Jobs + Burst. Также, я вычисляю шум для каждого вокселя отдельно, и не могу сократить кол-во вызовов P/Invoke, которые очень дорогие по сравнению с прямым вызовом метода. Ну и теряю векторизацию и все оптимизации Burst. Вот и получается, что SIMD вроде и есть, но очень условно.
Решение тут одно: переход на Burst математику и кодогенерацию по графу. Но это пока только в планах🥹
Вокруг библиотеки FastNoise2 у меня написан обычный C# враппер и редактор для Unity, чтобы можно было визуально связывать ноды и сразу видеть результат. Editor часть (на скриншоте) реализована с помощью Unity.GraphView, который до сих пор в экспериментальном статусе, и фреймворка NodeGraphProcessor. Очень жду Graph Tools Foundation, который должен быть с Unity 6 (предположительно) и который должен быть намного быстрее, чем GraphView.
Сам граф в Runtime выглядит просто как поинтер (IntPtr) на C++ часть, и я хотя бы не пробегаюсь по графу в C# части. Использование unmanaged типов позволяет мне их упаковать в Unity.Collections и использовать в Jobs.
Продолжаю рассказывать про генерацию, и прежде чем перейти к генерации геометрии, наверное стоит рассказать про шум. Тем более тут есть что показать интересного)
Для воксельных миров подойдет любой непрерывный шум. Это может быть всеми известный шум Перлина, или какой-нибудь Simplex. Хотя подойдет даже просто обычная синусоида, главное выбрать несколько итераций с разной периодичностью и сложить вместе. Но, я человек-визуал, и мне сложно через числа что-то собрать адекватное
Под графом шума я подразумеваю именно набор нод и связей между собой. Чтобы упростить себе работу, я взяла уже готовое решение - FastNoise2. Это С++ библиотека, которая содержит множество API для генерации разнообразного шума, причем с поддержкой SIMD (хотя тут не все так просто). И самое главное - предоставляет граф из коробки!
Не все так просто с SIMD у меня по той причине, что я пишу на C# (привет P/Invoke) и использую Jobs + Burst. Также, я вычисляю шум для каждого вокселя отдельно, и не могу сократить кол-во вызовов P/Invoke, которые очень дорогие по сравнению с прямым вызовом метода. Ну и теряю векторизацию и все оптимизации Burst. Вот и получается, что SIMD вроде и есть, но очень условно.
Решение тут одно: переход на Burst математику и кодогенерацию по графу. Но это пока только в планах
Вокруг библиотеки FastNoise2 у меня написан обычный C# враппер и редактор для Unity, чтобы можно было визуально связывать ноды и сразу видеть результат. Editor часть (на скриншоте) реализована с помощью Unity.GraphView, который до сих пор в экспериментальном статусе, и фреймворка NodeGraphProcessor. Очень жду Graph Tools Foundation, который должен быть с Unity 6 (предположительно) и который должен быть намного быстрее, чем GraphView.
Сам граф в Runtime выглядит просто как поинтер (IntPtr) на C++ часть, и я хотя бы не пробегаюсь по графу в C# части. Использование unmanaged типов позволяет мне их упаковать в Unity.Collections и использовать в Jobs.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍5👎1🤔1
Кстати, может стоит сюда писать и о всяких мелочах?) Например, различных нюансах Unity, с которыми я сталкиваюсь в ходе работы над проектом.
Вот вчера и сегодня оптимизировала генерацию текстурных данных для чанков. Была проблема, что текстурные массивы (Texture2DArray), которые у меня генерируются в runtime на лету, создавались слишком долго. Причина оказалась в том, что даже в случае, если массив используется только на GPU стороне, на CPU также создается его копия, и (!) после создания копируется на GPU. Это приводило к огромным задержкам (до 1 секунды🔥 ).
Я нашла "экспериментальное" API - флаги TextureCreationFlags, с помощью которых можно указать необходимое поведение. Но флаг TextureCreationFlags.DontInitializePixels не убирал синхронизацию с GPU, а вот флаги TextureCreationFlags.DontInitializePixels | TextureCreationFlags.DontUploadUponCreate уже привели к нужному поведению. Но правда на CPU все равно создается копия, она просто не используется и пока ее никак не убрать😔
Еще можно использовать RenderTexture с флагом TextureDimension.Tex2DArray, но RT не поддерживает сжатие, поэтому это для каких-то исключений.
На скриншоте голубое - то, что было, с синхронизацией с GPU, а оранжевое - без нее. Как видно, разница огромная!
Вот вчера и сегодня оптимизировала генерацию текстурных данных для чанков. Была проблема, что текстурные массивы (Texture2DArray), которые у меня генерируются в runtime на лету, создавались слишком долго. Причина оказалась в том, что даже в случае, если массив используется только на GPU стороне, на CPU также создается его копия, и (!) после создания копируется на GPU. Это приводило к огромным задержкам (до 1 секунды
Я нашла "экспериментальное" API - флаги TextureCreationFlags, с помощью которых можно указать необходимое поведение. Но флаг TextureCreationFlags.DontInitializePixels не убирал синхронизацию с GPU, а вот флаги TextureCreationFlags.DontInitializePixels | TextureCreationFlags.DontUploadUponCreate уже привели к нужному поведению. Но правда на CPU все равно создается копия, она просто не используется и пока ее никак не убрать
var textureArray = new Texture2DArray(width, height, 16,GraphicsFormat.RGBA_DXT1_SRGB, TextureCreationFlags.DontInitializePixels | TextureCreationFlags.DontUploadUponCreate | TextureCreationFlags.MipChain, mipCount)
{
filterMode = FilterMode.Bilinear,
wrapMode = TextureWrapMode.Repeat
};
Еще можно использовать RenderTexture с флагом TextureDimension.Tex2DArray, но RT не поддерживает сжатие, поэтому это для каких-то исключений.
На скриншоте голубое - то, что было, с синхронизацией с GPU, а оранжевое - без нее. Как видно, разница огромная!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18👍5❤2🎉2👎1
Вот еще один нюанс работы с Unity. Что-то их сегодня прям много 🥔
В Assembly Definition можно указывать требуемые версии пакетов, и даже define для этого, что удобно. Но не верьте Unity, которая пишет x >= version! Потому что это совсем не так! Define определится только, если x > version.
Вроде бы мелочь, а я потратила достаточно времени, пытаясь понять, где же ошиблась☺️
В Assembly Definition можно указывать требуемые версии пакетов, и даже define для этого, что удобно. Но не верьте Unity, которая пишет x >= version! Потому что это совсем не так! Define определится только, если x > version.
Вроде бы мелочь, а я потратила достаточно времени, пытаясь понять, где же ошиблась
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯8🤔1
Генерация. Octree.
После того как я сгенерировала воксели, я обнаружила, что количество вокселей, которые близки к поверхности, на самом деле очень мало. И большая часть вычислений на самом деле идет в пустоту. Ведь нам не нужны воксели, которые под ландшафтом, мы их все равно не увидим. Нам не нужны также воксели, которые в воздухе, мы их фактически тоже не видим.
Решение этой проблемы на самом деле достаточно простое: отсекаем все ненужное пространство и используем только те воксели, которые очень близки к поверхности.
С отсечением пространства хорошо справляются различные иерархические структуры данных. Я взяла для этого Octree, где корень - это чанк, а листья - воксели. А вот как определить, есть ли поверхность? Надо ли делить узел? Тут я пришла к набору эвристик:
- узел уже является вокселем? Тогда не разбиваем, это лист.
- в узле угловые воксели близки к поверхности (значение близко к 0)? Разбиваем узел.
- в узле угловые воксели и те, которые лежат на гранях, меняют свой знак относительно центрального? Значит тут есть поверхность, разбиваем.
- вычисляем ошибку через квадратичную функцию, она больше порога? Значит возможно тут есть поверхность, разбиваем.
С помощью octree получилось сильно оптимизировать вычисления. Отсекается от 70% до 90% пространства, и кол-во вокселей на чанк сильно уменьшается. Жаль, не смогла найти замеры с того времени, но прирост производительности можно примерно прикинуть.
На скриншоте пример результата octree для чанка. Видно, что самих вокселей стало намного меньше.
После того как я сгенерировала воксели, я обнаружила, что количество вокселей, которые близки к поверхности, на самом деле очень мало. И большая часть вычислений на самом деле идет в пустоту. Ведь нам не нужны воксели, которые под ландшафтом, мы их все равно не увидим. Нам не нужны также воксели, которые в воздухе, мы их фактически тоже не видим.
Решение этой проблемы на самом деле достаточно простое: отсекаем все ненужное пространство и используем только те воксели, которые очень близки к поверхности.
С отсечением пространства хорошо справляются различные иерархические структуры данных. Я взяла для этого Octree, где корень - это чанк, а листья - воксели. А вот как определить, есть ли поверхность? Надо ли делить узел? Тут я пришла к набору эвристик:
- узел уже является вокселем? Тогда не разбиваем, это лист.
- в узле угловые воксели близки к поверхности (значение близко к 0)? Разбиваем узел.
- в узле угловые воксели и те, которые лежат на гранях, меняют свой знак относительно центрального? Значит тут есть поверхность, разбиваем.
- вычисляем ошибку через квадратичную функцию, она больше порога? Значит возможно тут есть поверхность, разбиваем.
С помощью octree получилось сильно оптимизировать вычисления. Отсекается от 70% до 90% пространства, и кол-во вокселей на чанк сильно уменьшается. Жаль, не смогла найти замеры с того времени, но прирост производительности можно примерно прикинуть.
На скриншоте пример результата octree для чанка. Видно, что самих вокселей стало намного меньше.
🔥16👍5❤2👎1
Кстати, если визуализировать воксели без всяких оптимизаций, то есть по 4913 вокселей (17х17х17) на чанк, то это будет выглядеть вот так)
Красиво, но сколько вычислять😱
Красиво, но сколько вычислять
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7😍3🔥2😱1
Генерация. Меши
После генерации вокселей настало время генерации мешей. Для этого можно взять алгоритм Marching Cubes, но он имеет несколько минусов, с которыми я не смогла согласиться:
- неоднозначность сетки в некоторых случаях (в том числе и "дырки" из-за этого)
- нельзя создать острые края
- есть проблемы в сшивании разных LOD
Поэтому я взяла алгоритм Dual Contouring, который основан на Marching Cubes, но решает все перечисленные выше проблемы. Но для этого алгоритма нужно генерировать больше информации: я уже писала в посте про воксели про градиент, вот он и нужен для Dual Contouring.
Вершины, в отличие от Marchig Cubes, размещаются не на гранях кубов, а внутри куба (чаще всего просто средняя позиция между угловыми вокселями в зависимости от их значения).
Дальше, эти вершины "адаптируется" по градиенту с помощью QEF (вот тут есть объяснение, там много математики, поэтому боюсь не объясню на пальцах). Остается только собрать получившиеся вершины в треугольники и получаем сетку.
Основные проблемы были тут распараллелить алгоритм для Jobs. Пока это выглядит довольно криво, но зато работает и достаточно шустро). Да и от дырок я полностью не избавилась пока что)
Скриншот только такой нашла с тех времен, но можно увидеть саму сетку😶
После генерации вокселей настало время генерации мешей. Для этого можно взять алгоритм Marching Cubes, но он имеет несколько минусов, с которыми я не смогла согласиться:
- неоднозначность сетки в некоторых случаях (в том числе и "дырки" из-за этого)
- нельзя создать острые края
- есть проблемы в сшивании разных LOD
Поэтому я взяла алгоритм Dual Contouring, который основан на Marching Cubes, но решает все перечисленные выше проблемы. Но для этого алгоритма нужно генерировать больше информации: я уже писала в посте про воксели про градиент, вот он и нужен для Dual Contouring.
Вершины, в отличие от Marchig Cubes, размещаются не на гранях кубов, а внутри куба (чаще всего просто средняя позиция между угловыми вокселями в зависимости от их значения).
Дальше, эти вершины "адаптируется" по градиенту с помощью QEF (вот тут есть объяснение, там много математики, поэтому боюсь не объясню на пальцах). Остается только собрать получившиеся вершины в треугольники и получаем сетку.
Основные проблемы были тут распараллелить алгоритм для Jobs. Пока это выглядит довольно криво, но зато работает и достаточно шустро). Да и от дырок я полностью не избавилась пока что)
Скриншот только такой нашла с тех времен, но можно увидеть саму сетку
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9✍5👍3🔥2
В предыдущем посте я рассказала про меши, но может встать вопрос, а как работать с мешами в 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