Давно не писала 🤦 Завалена багами, которые зачем-то наделала. Может кто-нибудь знает, зачем мы, программисты, делаем баги? Уже раздражает даже 🤪
Вот и сейчас, разбираюсь, почему у меня швы не бесшовные, хотя в коде выглядят именно такими😔
А так, что за последний месяц было сделано (пока без точных цифр):
- переписала генератор вокселей под Burst, стало существенно шустрее, спаек почти нет;
- перевела почти всю генерацию на новую систему;
- пофиксила множество багов и недоработок, остались швы только;
- воксельный мир стал проще, так как многие моменты были пересмотрены;
- воксели теперь генерируются на GPU, что уменьшило время генерации на 90% сразу)
Зачем все это? Затем, что я столкнулась с жесткими спайками - до 60мс на кадр! Основная проблема - неправильно выбранная архитектура генератора, из-за чего распределение задач генерации по чанкам не давала нужный эффект. Ну и вычислительная сложность высокая, поэтому частичный переход на GPU - стоит того.
Вот и сейчас, разбираюсь, почему у меня швы не бесшовные, хотя в коде выглядят именно такими
А так, что за последний месяц было сделано (пока без точных цифр):
- переписала генератор вокселей под Burst, стало существенно шустрее, спаек почти нет;
- перевела почти всю генерацию на новую систему;
- пофиксила множество багов и недоработок, остались швы только;
- воксельный мир стал проще, так как многие моменты были пересмотрены;
- воксели теперь генерируются на GPU, что уменьшило время генерации на 90% сразу)
Зачем все это? Затем, что я столкнулась с жесткими спайками - до 60мс на кадр! Основная проблема - неправильно выбранная архитектура генератора, из-за чего распределение задач генерации по чанкам не давала нужный эффект. Ну и вычислительная сложность высокая, поэтому частичный переход на GPU - стоит того.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥8🥰1
Unity никогда не перестает удивлять, и иногда даже бесить)
Вот есть пакет Entities из DOTS, есть еще один пакет - Entities.Graphics, который предоставляет все необходимое для отрисовки через ECS без GameObject и компонентов MeshRenderer/MeshFilter.
Сама отрисовка происходит через самый обыкновенный BRG, с моей же стороны надо только предоставить информацию что рендерить и как это рендерить. Звучит просто, правда же?🔍
Если посмотреть документацию, то можно увидеть, что Unity предлагает использовать RenderMeshArray, если хочется в рантайме создавать сущности, без запечки. И раньше я так и делала, но так как этот компонент содержит два managed массива - один для мешей, другой для материалов, а чанки у меня постоянно создаются/удаляются/обновляются, то приходится пересоздавать эти массивы. Что очень дорого. Я уже не говорю про трекинг всех этих данных так, чтобы хотя бы индексы не менялись в этих массивах у тех чанков, которые не изменялись. А еще под Burst не запихнешь, и в Job System не используешь из-за managed кода. Короче, одни проблемы.😤
Почему я взялась искать другой путь? Потому что все эти managed массивы, пересчет хэша у RenderMeshArray, так как это SharedComponent, куча работы вокруг - это все дорого: 3-5-10 мс на обновление 30 чанков! И это за кадр! Интересно, откуда у меня спайки?🤔
Вначале я ввела spatial hashing для чанков, чтобы обновлять RenderMeshArray per chunk group, а не каждый раз для всех. Это сильно помогло, в худшем случае конечно все равно происходил перерасчет массивов и их хэша для всех групп, но в среднем - нагрузка снизилась в 10 раз.
Дальше мне подсказали, что если читать документацию не по диагонали, то можно заметить несколько строчек про компонент MaterialMeshInfo, который может принимать BatchMeshID и BatchMaterialID из BRG.🗣 То есть можно полностью избавиться от managed массивов и всего остального вокруг этого. Ура!
API тут простое:
- получаем систему EntitiesGraphicsSystem
- регистрируем наш меш через метод RegisterMesh и получаем BatchMeshID
- регистрируем материал через метод RegisterMaterial и получаем BatchMaterialID
- указываем эти id в компоненте MaterialMeshInfo и радуемся жизни
Что может пойти не так? Спросите вы. Тут 3, ладно, 4 строчки кода, если учитывать SetComponent, невозможно же запутаться? Но Unity имела на этот счет свое мнение🤭
Я использовала пакеты Entities, Entities.Graphics версии 1.0.16. И как минимум в этих версиях, данный функционал не работает! Я просто получала очень странную отрисовку мешей, чанки почему-то располагались в рандомных местах, все мигало, частично двигалось за камерой, а через несколько секунд Unity вылетала. По логам было понятно, что проблема в BRG - меши и материалы куда-то утекали, Id становились недействительными и все становилось грустно.
Решение? Хорошо, что есть новые версии пакетов) Решение оказалось простым - переход на версии 1.2.1, заодно и переход на Unity 2023.2.20. И все заработало как ожидалось, даже без изменений кода🥔
Что же касается циферок: медиана стала 0.03 мс против 3 мс ( в самом лучшем случае)! Минус еще одна проблема, маленькая победа)
Вот есть пакет Entities из DOTS, есть еще один пакет - Entities.Graphics, который предоставляет все необходимое для отрисовки через ECS без GameObject и компонентов MeshRenderer/MeshFilter.
Сама отрисовка происходит через самый обыкновенный BRG, с моей же стороны надо только предоставить информацию что рендерить и как это рендерить. Звучит просто, правда же?
Если посмотреть документацию, то можно увидеть, что Unity предлагает использовать RenderMeshArray, если хочется в рантайме создавать сущности, без запечки. И раньше я так и делала, но так как этот компонент содержит два managed массива - один для мешей, другой для материалов, а чанки у меня постоянно создаются/удаляются/обновляются, то приходится пересоздавать эти массивы. Что очень дорого. Я уже не говорю про трекинг всех этих данных так, чтобы хотя бы индексы не менялись в этих массивах у тех чанков, которые не изменялись. А еще под Burst не запихнешь, и в Job System не используешь из-за managed кода. Короче, одни проблемы.
Почему я взялась искать другой путь? Потому что все эти managed массивы, пересчет хэша у RenderMeshArray, так как это SharedComponent, куча работы вокруг - это все дорого: 3-5-10 мс на обновление 30 чанков! И это за кадр! Интересно, откуда у меня спайки?
Вначале я ввела spatial hashing для чанков, чтобы обновлять RenderMeshArray per chunk group, а не каждый раз для всех. Это сильно помогло, в худшем случае конечно все равно происходил перерасчет массивов и их хэша для всех групп, но в среднем - нагрузка снизилась в 10 раз.
Дальше мне подсказали, что если читать документацию не по диагонали, то можно заметить несколько строчек про компонент MaterialMeshInfo, который может принимать BatchMeshID и BatchMaterialID из BRG.
API тут простое:
- получаем систему EntitiesGraphicsSystem
- регистрируем наш меш через метод RegisterMesh и получаем BatchMeshID
- регистрируем материал через метод RegisterMaterial и получаем BatchMaterialID
- указываем эти id в компоненте MaterialMeshInfo и радуемся жизни
Что может пойти не так? Спросите вы. Тут 3, ладно, 4 строчки кода, если учитывать SetComponent, невозможно же запутаться? Но Unity имела на этот счет свое мнение
Я использовала пакеты Entities, Entities.Graphics версии 1.0.16. И как минимум в этих версиях, данный функционал не работает! Я просто получала очень странную отрисовку мешей, чанки почему-то располагались в рандомных местах, все мигало, частично двигалось за камерой, а через несколько секунд Unity вылетала. По логам было понятно, что проблема в BRG - меши и материалы куда-то утекали, Id становились недействительными и все становилось грустно.
Решение? Хорошо, что есть новые версии пакетов) Решение оказалось простым - переход на версии 1.2.1, заодно и переход на Unity 2023.2.20. И все заработало как ожидалось, даже без изменений кода
Что же касается циферок: медиана стала 0.03 мс против 3 мс ( в самом лучшем случае)! Минус еще одна проблема, маленькая победа)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20👏9😎5❤3😁1
Еще одна проблема решена: планирование задач в Job System занимало очень много времени.
Я планировала примерно по 150-200 задач на каждом шаге генерации за кадр. Это все занимало значительно время: от 1 мс до 3 мс на кадр за шаг. В совокупности - до 15-20 мс за кадр!🗣 Что конечно же вызывало спайки.
Как такое решать? Это же просто планирование задач!🤷
Решение: так как планирование задач нельзя запихнуть под Burst (не поддерживается), то остается только позапихивать все задачи в одну, то есть вместо планирование 32 задач одинакового типа, но с разными данными, планировать одну задачу, а данные объединить в массив данных. 32 - это количество чанков в шаге.
Но в данном решении есть несколько нюансов, из-за которых я сразу не сделала таким образом. Самый главный нюанс - консистентность хранилища состояния мира и сгенерированных данных для чанков. Каждый шаг - выполняется параллельно и не связан с другими шагами, пишет/читает разные данные, но хранилище то одно. А что делать, если реаллок? А что делать, если мы в каком-то шаге пишем в джобах параллельно, а в другом, параллельном шаге, читаем это хранилище в одном потоке?
Связывать шаги не хотелось, так как во-первых, тогда появляется проблема загрузки потоков Job System - я просто не загружу их адекватно при последовательном выполнении, а во-вторых, проблема не исчезает, если в первом шаге мы запланировали (и фактически запустили) джобы с параллельной записью, а во втором шаге только-только приступили к чтению хранилища в Main Thread.
Что же делать?🤔 Я пришла к идее разделения процессов записи и чтения. Учитывая, что каждый шаг читает/пишет данные по непересекающимся с другими шагами чанками, и это гарантируется на уровне моего планировщика чанков, то можно выделить локальный контекст под каждый шаг, в который мы спокойно можем писать наши данные так, как нам удобно: в джобах, параллельно, последовательно, в меин треде. А вот читать - из глобального контекста.
Остается только потом мигрировать данные из локальных контекстов всех шагов в глобальный контекст в главном потоке. Это происходит после выполнения всех шагов, когда максимально безопасно менять глобальный контекст.
Что дало такое усложнение? Я решила одну из проблем, по которой происходили спайки. Миграция занимает тысячные/сотые доли мс, и выполняется хоть и на Main Thread, но под Burst. А позволив перенести всю работу с хранилищем состояния мира генерации на Job System, я почти полностью освободила главный поток, заодно сверху как обычно Burst работает.
На скриншоте видно, на сколько уменьшилось время выполнения одного из шагов генерации.
Я планировала примерно по 150-200 задач на каждом шаге генерации за кадр. Это все занимало значительно время: от 1 мс до 3 мс на кадр за шаг. В совокупности - до 15-20 мс за кадр!
Как такое решать? Это же просто планирование задач!
Решение: так как планирование задач нельзя запихнуть под Burst (не поддерживается), то остается только позапихивать все задачи в одну, то есть вместо планирование 32 задач одинакового типа, но с разными данными, планировать одну задачу, а данные объединить в массив данных. 32 - это количество чанков в шаге.
Но в данном решении есть несколько нюансов, из-за которых я сразу не сделала таким образом. Самый главный нюанс - консистентность хранилища состояния мира и сгенерированных данных для чанков. Каждый шаг - выполняется параллельно и не связан с другими шагами, пишет/читает разные данные, но хранилище то одно. А что делать, если реаллок? А что делать, если мы в каком-то шаге пишем в джобах параллельно, а в другом, параллельном шаге, читаем это хранилище в одном потоке?
Связывать шаги не хотелось, так как во-первых, тогда появляется проблема загрузки потоков Job System - я просто не загружу их адекватно при последовательном выполнении, а во-вторых, проблема не исчезает, если в первом шаге мы запланировали (и фактически запустили) джобы с параллельной записью, а во втором шаге только-только приступили к чтению хранилища в Main Thread.
Что же делать?
Остается только потом мигрировать данные из локальных контекстов всех шагов в глобальный контекст в главном потоке. Это происходит после выполнения всех шагов, когда максимально безопасно менять глобальный контекст.
Что дало такое усложнение? Я решила одну из проблем, по которой происходили спайки. Миграция занимает тысячные/сотые доли мс, и выполняется хоть и на Main Thread, но под Burst. А позволив перенести всю работу с хранилищем состояния мира генерации на Job System, я почти полностью освободила главный поток, заодно сверху как обычно Burst работает.
На скриншоте видно, на сколько уменьшилось время выполнения одного из шагов генерации.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍2😱1
This media is not supported in your browser
VIEW IN TELEGRAM
Переношу (и заодно тестирую) шум на GPU. В качестве базы все также использую библиотеку FastNoise, но уже ее версию на HLSL. К этой библиотеке пришлось дописывать некоторые очень нужные функции (в основном касается примитивных шумов + фильтрации) + перенесла то, что у меня было на C#.
Так как шум (а потом и SDF) считается на GPU (ComputeShaders), то можно использовать уже посложнее алгоритмы☺️ Например, добавила поддержку генерации систем пещер 😧
#generation
Так как шум (а потом и SDF) считается на GPU (ComputeShaders), то можно использовать уже посложнее алгоритмы
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🔥5👍3
Внезапно обнаружила, что в библиотеке FastNoise, о которой рассказывала раньше, фрактальный шум (Fbm, Ridged, Ping-Pong) не совсем фрактальный на самом деле! 😧
Автор библиотеки допустил ошибку, и весь фрактальный шум генерируется на основе линейных параметров, не учитывая предыдущие шаги. Это дает совсем неинтересные результаты: фактически значение в каждой точке просто уточняется N итераций (октав). А как мы знаем, фракталы - это немного другое.
Дописать и решить эту проблему не сложно, в общем случае все сводится к добавлению множителя на каждом шаге, который рассчитывается вот так:
На скриншоте слева - обычный Fractal Ridged из Fast Noise, справа - Multi Fractal Ridged моей версии.🫥
#generation
Автор библиотеки допустил ошибку, и весь фрактальный шум генерируется на основе линейных параметров, не учитывая предыдущие шаги. Это дает совсем неинтересные результаты: фактически значение в каждой точке просто уточняется N итераций (октав). А как мы знаем, фракталы - это немного другое.
Дописать и решить эту проблему не сложно, в общем случае все сводится к добавлению множителя на каждом шаге, который рассчитывается вот так:
pow(lacunarity, -index * spectralExponent);
На скриншоте слева - обычный Fractal Ridged из Fast Noise, справа - Multi Fractal Ridged моей версии.
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18❤5👍4
Наконец-то пофиксила все баги, которые наделала 🫥 , а также вернула почти весь функционал, чтобы можно было сравнить старую версию генератора с новой. 😧
Я не меняла алгоритмы генерации, только снизила нагрузку как общую, так и per frame.
Список изменений:
🔹 планирование генерации чанков стало Burst Compatible и полностью unmanaged;
🔹 планирование теперь предполагает подход Wave Front (волнами) по 32 чанка на волну;
🔹 были убраны отдельные Thread Worker для генерации, вместо них вся генерация была переписана на Job System, а также все максимально стало Burst Compatible;
🔹 расчет SDF для вокселей был полностью перенесен на GPGPU - это позволило считать SDF сразу для всех вокселей, что очень сильно упростило дальнейшие алгоритмы;
🔹 пост-процессинг сгенерированных данных был сильно упрощен, а также изменен в сторону like-ECS;
🔹 были переписаны структуры данных: где-то в угоду производительности (но больше использование памяти), где-то наоборот, но в целом они мало поменяли картину;
🔹 был упрощен подход с хранением типов вокселей: теперь есть общие наборы типов вокселей для всех биомов, биомы же только предоставляют веса для типов вокселей из сетов;
🔹 множество мелких исправлений и улучшений, что-то даже уже не помню🤭
Что касается цифр, было:
❌ были большие пики, абсолютные цифры также удручающие - свыше 600 мс на кадр❗️
❌ генерация всего мира занимала примерно 1 секунду, но ценой времени кадра (свыше 100 мс).
Стало:
✅ пики ушли, на графике есть локальные пики, которые не относятся к генерации и никак не влияют на время кадра;
✅ за кадр медиана 0,03мс на генерацию в Main Thread и примерно ~10 мс суммарно по всем Job System Workers;
✅ генерация всего мира занимает примерно 1 секунду, но время кадра почти не меняется (полезно будет при реализации телепортов/быстрого перемещения).
#generation
Я не меняла алгоритмы генерации, только снизила нагрузку как общую, так и per frame.
Список изменений:
🔹 планирование генерации чанков стало Burst Compatible и полностью unmanaged;
🔹 планирование теперь предполагает подход Wave Front (волнами) по 32 чанка на волну;
🔹 были убраны отдельные Thread Worker для генерации, вместо них вся генерация была переписана на Job System, а также все максимально стало Burst Compatible;
🔹 расчет SDF для вокселей был полностью перенесен на GPGPU - это позволило считать SDF сразу для всех вокселей, что очень сильно упростило дальнейшие алгоритмы;
🔹 пост-процессинг сгенерированных данных был сильно упрощен, а также изменен в сторону like-ECS;
🔹 были переписаны структуры данных: где-то в угоду производительности (но больше использование памяти), где-то наоборот, но в целом они мало поменяли картину;
🔹 был упрощен подход с хранением типов вокселей: теперь есть общие наборы типов вокселей для всех биомов, биомы же только предоставляют веса для типов вокселей из сетов;
🔹 множество мелких исправлений и улучшений, что-то даже уже не помню
Что касается цифр, было:
❌ были большие пики, абсолютные цифры также удручающие - свыше 600 мс на кадр❗️
❌ генерация всего мира занимала примерно 1 секунду, но ценой времени кадра (свыше 100 мс).
Стало:
✅ пики ушли, на графике есть локальные пики, которые не относятся к генерации и никак не влияют на время кадра;
✅ за кадр медиана 0,03мс на генерацию в Main Thread и примерно ~10 мс суммарно по всем Job System Workers;
✅ генерация всего мира занимает примерно 1 секунду, но время кадра почти не меняется (полезно будет при реализации телепортов/быстрого перемещения).
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19❤7👏5👍2
ГД как часть разработки
Без грамотного описания игры разработка скорее всего не достигнет успеха. Потому что это будет не процесс разработки, а процесс нащупывания в темноте. ГД (геймдизайн) тут выступает в качестве факела, который освещает путь и разгоняет темноту☺️
Я конечно же тоже работаю над ГДД (геймдизайн документом). Это большая и сложная задача, к которой приходится подходить постепенно, шаг за шагом. И также как и с кодом, итеративно.
За последнюю неделю я описала еще несколько механик, ответила на часть вопросов, которые витали в воздухе. Переработала часть критических моментов, и даже вывела некоторые формулы для расчета параметров и характеристик.
Так что, не считая понедельника, всю неделю я не написала ни единой строчки кода😔 , зато написала сотни и даже тысячи строк текста 🤭
Также, я пришла к одной идее, которая возможно многим знакома, но для меня, как разработчицы в первую очередь, а потом уже геймдизайнера, это было небольшим открытием.
Идея
Идея заключается в том, чтобы все параметры, характеристики, термины, объекты заносить в таблицу. В тексте описания механик, вместо того, чтобы постоянно описывать какой-нибудь параметр и за что он отвечает, просто ссылаться на элемент таблицы.
Это правда очень сильно облегчает жизнь: получается прямая ссылка, название которой еще можно менять в одно действие. В таблице, для каждого элемента можно также завести описание, чтобы при клике на ссылку мы еще видели и описание.
В Notion, который я использую, это делается максимально просто:
🔹создается таблица;
🔹таблица заполняется нужными элементами;
🔹сослаться на элемент из таблицы в любом блоке текста можно через @ и в выпадающем списке выбрать конкретный элемент.
#game_design
Без грамотного описания игры разработка скорее всего не достигнет успеха. Потому что это будет не процесс разработки, а процесс нащупывания в темноте. ГД (геймдизайн) тут выступает в качестве факела, который освещает путь и разгоняет темноту
Я конечно же тоже работаю над ГДД (геймдизайн документом). Это большая и сложная задача, к которой приходится подходить постепенно, шаг за шагом. И также как и с кодом, итеративно.
За последнюю неделю я описала еще несколько механик, ответила на часть вопросов, которые витали в воздухе. Переработала часть критических моментов, и даже вывела некоторые формулы для расчета параметров и характеристик.
Так что, не считая понедельника, всю неделю я не написала ни единой строчки кода
Также, я пришла к одной идее, которая возможно многим знакома, но для меня, как разработчицы в первую очередь, а потом уже геймдизайнера, это было небольшим открытием.
Идея
Идея заключается в том, чтобы все параметры, характеристики, термины, объекты заносить в таблицу. В тексте описания механик, вместо того, чтобы постоянно описывать какой-нибудь параметр и за что он отвечает, просто ссылаться на элемент таблицы.
Это правда очень сильно облегчает жизнь: получается прямая ссылка, название которой еще можно менять в одно действие. В таблице, для каждого элемента можно также завести описание, чтобы при клике на ссылку мы еще видели и описание.
В Notion, который я использую, это делается максимально просто:
🔹создается таблица;
🔹таблица заполняется нужными элементами;
🔹сослаться на элемент из таблицы в любом блоке текста можно через @ и в выпадающем списке выбрать конкретный элемент.
#game_design
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥7❤3😁1
Media is too big
VIEW IN TELEGRAM
Знаю знаю, что ландшафт цвета детской неожиданности совсем не впечатляет 🤭 (обещаю, что скоро это изменится), но все равно поделюсь поделюсь видео с первыми результатами переноса поддержки множества биомов на GPU.
Когда я считала воксели на Job System, то у меня уже была успешно реализована функция смешивания разных биомов. Сейчас же, в рамках задачи возвращения оставшихся функций на новый пайплайн генерации, переношу смешивание на HLSL.
Это оказалось не так просто, так как столкнулась с очень долгой компиляцией вычислительных шейдеров😔 Все потому, что весь код инлайнится, а алгоритмы шумов - большие и дорогие 😭 Но ничего, я уже успешно снизила время с 30 минут до 30 секунд, жить можно) А там еще что-нибудь придумаю 🥔
#generation
Когда я считала воксели на Job System, то у меня уже была успешно реализована функция смешивания разных биомов. Сейчас же, в рамках задачи возвращения оставшихся функций на новый пайплайн генерации, переношу смешивание на HLSL.
Это оказалось не так просто, так как столкнулась с очень долгой компиляцией вычислительных шейдеров
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16👏7🤯3👍2❤1
Media is too big
VIEW IN TELEGRAM
Графический дебаг иногда бывает красивым, а иногда даже жутковатым 🤭
Сломала шейдинг, приходится искать проблему самым простым способом: выводить вертекс каналы в качестве Albedo. Это например веса нескольких материалов, жаль, что неправильные, интересно почему?🤔
#generation
Сломала шейдинг, приходится искать проблему самым простым способом: выводить вертекс каналы в качестве Albedo. Это например веса нескольких материалов, жаль, что неправильные, интересно почему?
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤3🔥3🙏2
This media is not supported in your browser
VIEW IN TELEGRAM
Суббота прошла не зря (кря-кря): я вернула шейдинг, который ранее поломала, а также перенесла смешивание биомов на GPU сторону. 🥔
Интересный эффект "висюлек" получился на границе двух биомов + пещер.😧
Теперь надо подумать, как генерировать карту биомов, чтобы переходы были плавными, а не такими резкими🔍
#generation
Интересный эффект "висюлек" получился на границе двух биомов + пещер.
Теперь надо подумать, как генерировать карту биомов, чтобы переходы были плавными, а не такими резкими
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤6👍2
Media is too big
VIEW IN TELEGRAM
Путешествие в гору
Долго же я возилась со смешиванием биомов😔 : попробовала множество вариантов, механик, функций. Четыре дня непрерывных поисков.
Пока пришла к весовым функциям для каждого биома + карта высоты, которая является входным параметром для весовых функций. В дальнейшем добавится карта температуры и влажности. Но сейчас всего лишь три тестовых биома, и усложнять не очень хочется.
Каждый биом сам является функцией, которая определяет как SDF, так и воксели. Но некоторым биомам, оказалось, нужна и та самая базовая высота, по которой считается вес, иначе выглядит фу🫣
На самом деле тут еще экспериментировать и экспериментировать. Но как стартовая точка вполне подходит. Можно добавить побольше типов вокселей и деталей, чтобы разнообразить вид.
Note: текстуры сейчас используются максимально простые, чтобы видеть как смешивается, в дальнейшем их заменю на более подходящие.
#generation
Долго же я возилась со смешиванием биомов
Пока пришла к весовым функциям для каждого биома + карта высоты, которая является входным параметром для весовых функций. В дальнейшем добавится карта температуры и влажности. Но сейчас всего лишь три тестовых биома, и усложнять не очень хочется.
Каждый биом сам является функцией, которая определяет как SDF, так и воксели. Но некоторым биомам, оказалось, нужна и та самая базовая высота, по которой считается вес, иначе выглядит фу
На самом деле тут еще экспериментировать и экспериментировать. Но как стартовая точка вполне подходит. Можно добавить побольше типов вокселей и деталей, чтобы разнообразить вид.
Note: текстуры сейчас используются максимально простые, чтобы видеть как смешивается, в дальнейшем их заменю на более подходящие.
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤7
Видео в прошлом посте конечно длинное 🤪 Но я хотела показать масштабы мира и на сколько все далеко генерируется, причем в режиме realtime ☺️
Размер видимого мира - 4 кубических километра, то есть 4096x4096x4096 units, где каждый 1x1x1 unit - воксель. А это почти 69 млрд вокселей-кубиков! Ну, почти, есть же уровни детализации🔍
Для тестирования я добавила 3 биома с одноцветными текстурами (спасибо Paint за это🤭 ).
Почему одноцветные? Потому что другие надо готовить, плюс я нашла ошибку в шейдере в части смешивания текстур по высоте🤷
Почему всего три? Потому что два - показалось мало, а четыре - слишком много🤭 Биомы добавлять не сложно, но вот сделать их интересными - задача не из простых.
Что дальше?
Надо починить шейдер для ландшафта и вернуть инстансинг объектов на карте.
Ах, совсем забыла, пока возилась с биомами, я починила еще один краш: иногда лучше не трогать Pack в StructLayout под Burst🤦 Даже если очень, очень надо.
А еще вернула под новый генератор мира поиск стартовой точки, чтобы не оказываться в воздухе☺️
#generation
Размер видимого мира - 4 кубических километра, то есть 4096x4096x4096 units, где каждый 1x1x1 unit - воксель. А это почти 69 млрд вокселей-кубиков! Ну, почти, есть же уровни детализации
Для тестирования я добавила 3 биома с одноцветными текстурами (спасибо Paint за это
Почему одноцветные? Потому что другие надо готовить, плюс я нашла ошибку в шейдере в части смешивания текстур по высоте
Почему всего три? Потому что два - показалось мало, а четыре - слишком много
Что дальше?
Надо починить шейдер для ландшафта и вернуть инстансинг объектов на карте.
Ах, совсем забыла, пока возилась с биомами, я починила еще один краш: иногда лучше не трогать Pack в StructLayout под Burst
А еще вернула под новый генератор мира поиск стартовой точки, чтобы не оказываться в воздухе
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18🥰5
Типы вокселей
Я уже не раз рассказывала, что мир у мня воксельный, и вся генерация основана на вокселях. Что это означает? А то, что инстансинг окружения, выбор материала, текстур и прочее, все это основано на вокселе. То есть сам воксель определяет что как именно у нас выглядит.
Снег в горах - это не отдельный биом, и не просто градиент, это именно другие воксели. Каждый биом ведь предоставляет не только SDF, но и информацию о типах вокселей в конкретной точке пространства и их веса.
Для проверки своей идеи я добавила парочку масок для снега. Чтобы это была не просто "шапка" на верхушке горы, а что-то более реалистичное. Одна маска базируется на высоте поверхности (я ее знаю из SDF), вторая же - на нормали, которая вычисляется из SDF с помощью метода численного дифференцирования.
Жаль только, что с повышением уровня детализации (LOD), эффект становится слишком уж приближенным. У меня есть идеи как для высоких уровней детализации дополнять детали, с этим как раз поможет GPU. Надеюсь когда-нибудь дойду до этого☺️
#generation #shading
Я уже не раз рассказывала, что мир у мня воксельный, и вся генерация основана на вокселях. Что это означает? А то, что инстансинг окружения, выбор материала, текстур и прочее, все это основано на вокселе. То есть сам воксель определяет что как именно у нас выглядит.
Снег в горах - это не отдельный биом, и не просто градиент, это именно другие воксели. Каждый биом ведь предоставляет не только SDF, но и информацию о типах вокселей в конкретной точке пространства и их веса.
Для проверки своей идеи я добавила парочку масок для снега. Чтобы это была не просто "шапка" на верхушке горы, а что-то более реалистичное. Одна маска базируется на высоте поверхности (я ее знаю из SDF), вторая же - на нормали, которая вычисляется из SDF с помощью метода численного дифференцирования.
Жаль только, что с повышением уровня детализации (LOD), эффект становится слишком уж приближенным. У меня есть идеи как для высоких уровней детализации дополнять детали, с этим как раз поможет GPU. Надеюсь когда-нибудь дойду до этого
#generation #shading
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤8🥰2👍1🤯1
StructLayout.Pack и Burst
Когда мы работаем со структурами, нам часто бывает важно как именно расположены члены этой структуры в памяти. Это может быть по причинам работы с P/Invoke, или может мы захотели оптимизировать использование памяти.
Для того, чтобы управлять тем, как члены структуры расположены в памяти, мы можем обратиться к атрибуту StructLayout, который имеет несколько параметров.
Note: атрибут StructLayout может применяться также и к классам!
1️⃣ Первый параметр - LayoutKind - тип макета, который бывает:
1. Sequential: все поля располагаются в памяти последовательно в порядке объявления.
2. Explicit: поля располагаются в памяти по заданным смещениям. Эти смещения указываются через атрибут FieldOffset.
3. Auto: автоматическое расположение полей в зависимости от компилятора. Совсем не обязательно, что поля будут располагаться в порядке объявления, так что с этим поосторожнее.
Note: по умолчанию всегда стоит Sequential, то есть получаем поведение из пункта 1.
2️⃣ Второй параметр, который тоже частенько используется - Size. Он отвечает за абсолютный размер структуры. Используется, когда мы хотим выделять конкретное количество байт на структуру. Причем, мы можем выделить больше, чем поля в сумме занимают на самом деле. Таким образом можно, например, выделить кусочек памяти на header структуры и там хранить какую-то информацию.
Note: размер структуры без каких-либо полей равен одному байту. Так что не обязательно указывать Size = 1 у "пустой" структуры.
3️⃣ Третий параметр - CharSet - задает кодировку для строк. Но в рамках Burst это мало интересует. Но может понадобиться, если работа ведется с неуправляемыми библиотеками.
4️⃣ И четвертый параметр - Pack - задает выравнивание полей. Тоже может использоваться частенько, но надо быть очень осторожным с ним. Полезен, если мы хотим, чтобы структура не занимала лишнее место в памяти, если используем поля с разным размером.
Давайте придумаем пример:
Какой размер у такой структуры? Ответ - 7 байт. Почему? Потому что мы указали выравнивание 1 байт, тогда: поле ByteValue занимает 1 байт, поле IntValue занимает 4 байта и поле ShortValue - 2 байта. Если указать Pack = 4, то размер структуры будет уже 12 байт, по 4 байта на каждое поле.
Вроде бы все логично, но не для Burst: до версии 1.8.16 включительно было замечено такое поведение, что Burst пытается вставить свое выравнивание. Возможно это связано с векторизацией🤷 , но это приводит к тому, что в коде без Burst, структура занимает N памяти, например, те же 7 байт, а в коде с Burst, это структура будет занимать уже 16 байт.
К чему приводит такое поведение? А то, что если выделить память по размеру структуры без Burst, а потом попытаться читать/писать под Burst, то приходим к крашу, так как получаем сдвиг элементов и выходим за границы выделенной памяти.
Как с этим справляться? Я пока не придумала лучше того, чтобы совсем не использовать Pack. Но иногда так хочется, хочется сэкономить пару байт (да-да, я часто использую поля с размером 1 байт). А потом вспоминаю, как я искала неделю причину краша.🤦
Когда-то Burst поддерживал только частично параметр Pack, сейчас в документации написано, что полностью поддерживает. Но все равно будьте осторожны!
P.S.: возможно кто-то сталкивался с подобным, поделитесь в комментариях!
#programming
Когда мы работаем со структурами, нам часто бывает важно как именно расположены члены этой структуры в памяти. Это может быть по причинам работы с P/Invoke, или может мы захотели оптимизировать использование памяти.
Для того, чтобы управлять тем, как члены структуры расположены в памяти, мы можем обратиться к атрибуту StructLayout, который имеет несколько параметров.
Note: атрибут StructLayout может применяться также и к классам!
1️⃣ Первый параметр - LayoutKind - тип макета, который бывает:
1. Sequential: все поля располагаются в памяти последовательно в порядке объявления.
2. Explicit: поля располагаются в памяти по заданным смещениям. Эти смещения указываются через атрибут FieldOffset.
3. Auto: автоматическое расположение полей в зависимости от компилятора. Совсем не обязательно, что поля будут располагаться в порядке объявления, так что с этим поосторожнее.
Note: по умолчанию всегда стоит Sequential, то есть получаем поведение из пункта 1.
2️⃣ Второй параметр, который тоже частенько используется - Size. Он отвечает за абсолютный размер структуры. Используется, когда мы хотим выделять конкретное количество байт на структуру. Причем, мы можем выделить больше, чем поля в сумме занимают на самом деле. Таким образом можно, например, выделить кусочек памяти на header структуры и там хранить какую-то информацию.
Note: размер структуры без каких-либо полей равен одному байту. Так что не обязательно указывать Size = 1 у "пустой" структуры.
3️⃣ Третий параметр - CharSet - задает кодировку для строк. Но в рамках Burst это мало интересует. Но может понадобиться, если работа ведется с неуправляемыми библиотеками.
4️⃣ И четвертый параметр - Pack - задает выравнивание полей. Тоже может использоваться частенько, но надо быть очень осторожным с ним. Полезен, если мы хотим, чтобы структура не занимала лишнее место в памяти, если используем поля с разным размером.
Давайте придумаем пример:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ExampleStruct
{
public byte ByteValue;
public int IntValue;
public short ShortValue;
}
Какой размер у такой структуры? Ответ - 7 байт. Почему? Потому что мы указали выравнивание 1 байт, тогда: поле ByteValue занимает 1 байт, поле IntValue занимает 4 байта и поле ShortValue - 2 байта. Если указать Pack = 4, то размер структуры будет уже 12 байт, по 4 байта на каждое поле.
Вроде бы все логично, но не для Burst: до версии 1.8.16 включительно было замечено такое поведение, что Burst пытается вставить свое выравнивание. Возможно это связано с векторизацией
К чему приводит такое поведение? А то, что если выделить память по размеру структуры без Burst, а потом попытаться читать/писать под Burst, то приходим к крашу, так как получаем сдвиг элементов и выходим за границы выделенной памяти.
Как с этим справляться? Я пока не придумала лучше того, чтобы совсем не использовать Pack. Но иногда так хочется, хочется сэкономить пару байт (да-да, я часто использую поля с размером 1 байт). А потом вспоминаю, как я искала неделю причину краша.
Когда-то Burst поддерживал только частично параметр Pack, сейчас в документации написано, что полностью поддерживает. Но все равно будьте осторожны!
P.S.: возможно кто-то сталкивался с подобным, поделитесь в комментариях!
#programming
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍7🔥4🥰1
This media is not supported in your browser
VIEW IN TELEGRAM
Редакторы тоже важны
Возвращала инстансинг объектов, и столкнулась с тем, что совсем не помню, что и как указывать в LOD. Я просто не могла настроить свои же лоды!😔
Причина в том, что там надо указывать процентное отношение объекта к размеру камеры, а не какие-то понятные единицы. Это отношение еще надо посчитать, ох, лучше я редактор напишу☺️
Взяла за основу редактор от UnityEngine.LODGroup - он понятен и привычен. Переписала его под свои нужды: в моей реализации LOD многое упрощено, да и работаю я напрямую с Mesh и Material, так как моя реализация под BRG.
Что ж, часа 4, и вот, PropertyDrawer, который почти повторяет функционал LODGroup. Рисуется в MonoBehaviour, ScriptableObject, везде, где указано сериализованное поле моего типа. Удобненько.🥔
#programming
Возвращала инстансинг объектов, и столкнулась с тем, что совсем не помню, что и как указывать в LOD. Я просто не могла настроить свои же лоды!
Причина в том, что там надо указывать процентное отношение объекта к размеру камеры, а не какие-то понятные единицы. Это отношение еще надо посчитать, ох, лучше я редактор напишу
Взяла за основу редактор от UnityEngine.LODGroup - он понятен и привычен. Переписала его под свои нужды: в моей реализации LOD многое упрощено, да и работаю я напрямую с Mesh и Material, так как моя реализация под BRG.
Что ж, часа 4, и вот, PropertyDrawer, который почти повторяет функционал LODGroup. Рисуется в MonoBehaviour, ScriptableObject, везде, где указано сериализованное поле моего типа. Удобненько.
#programming
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14❤10👍2
Возвращение заблудших фич
Наконец-то я вернула весь функционал, который у меня был до начала переделки генератора и его оптимизации.💃
Почему так долго? Потому что это десятки тысяч строк кода и много математики (ну и не всегда я работаю полный день, признаю🤷 ). Плюс я не раз натыкалась на свои же старые баги (все еще задаюсь вопросом, зачем я их наделала? 🤦 ), а также добавила новые и снова наткнулась на них 🤭
По результатам оптимизации я писала пост вот туть.
Просто пост радости, теперь можно продолжить работать над ГД, добавлять новые возможности и идти к новым горизонтам, даже если из этого проекта ничего не получится.
#generation
Наконец-то я вернула весь функционал, который у меня был до начала переделки генератора и его оптимизации.
Почему так долго? Потому что это десятки тысяч строк кода и много математики (ну и не всегда я работаю полный день, признаю
По результатам оптимизации я писала пост вот туть.
Просто пост радости, теперь можно продолжить работать над ГД, добавлять новые возможности и идти к новым горизонтам, даже если из этого проекта ничего не получится.
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤27