Рендеринг Minecraft, Фреймбуферы, Шейдеры
Сегодня у нас пост в котором, я немного расскажу о рендеринге в игре целиком, а также разберу один интересный эффект из одного мода.
Постараюсь обойтись без сложных терминов, да и в целом пост обзорный, т.е. это не гайд, как сделать крутой эффект, но зато у Вас вероятно отпадёт большинство вопросов, вроде "Почему какие-то моды не работают с шейдерпаками".
Как работает рендеринг в Minecraft?
Я начну разбор сразу с Sodium+Iris, чтобы было понятно как работает рендеринг с шейдерпаками. Для начала напомню, Шейдеры это не Шейдерпаки, очень часто люди это путают! Говорить "Я играю в майнкрафт с шейдерами", это всё равно что говорить "Я играю в майнкрафт с текстурами", вместо "Я играю в майнкрафт с текстурпаками".
Короче говоря, шейдер - это программа, которая что-то рисует или обрабатывает на видеокарте, а шейдерпак - это набор этих самых шейдеров, которые использует тот же Iris или Optifine.
Рассмотрим процесс отрисовки этого кадра из шейдерпака Complementary Reimagined:
Начинается всё с рисования теней. Игра создаёт вот такую текстуру:
Тут у вас вероятно возникла куча вопросов:
1) Что это такое? - Это карта теней, представьте себе камеру, которая стоит на месте солнца и смотрит на вас. Он нужен, чтобы позднее определить, в какую сторону направлены тени и где их видно (то есть участки, которые не видно на этой текстуре - тени, они будут темнее в финальном кадре)
То есть своего рода снимок со спутника)
2) Почему он круглый? - Обычно для оптимизации шейдерпаки делают снимок не всего мира, а лишь некоторого участка вокруг игрока, то в некотором радиусе от него, чтобы не рисовать лишнего, что игрок всё равно не увидит.
3) Что это за синие участки, и вообще почему всё тёмное/чёрно-белое? - Если выкрутить яркость и присмотреться внимательно, то можно заметить на этом рисунке листву, которая светлее того же дёрна. То есть объекты ближе к камере более белые, а те, что дальше - более чёрные. Это позволяет понять приблизительное расстояние до объектов, тем самым на финальном кадре определить на какой именно поверхности будет тень. Синие фрагменты есть не во всех шейдерпаках, конкретно сейчас это вода, обычно её игнорируют, но иногда могут вот так обрабатывать для разных эффектов, вроде теней под водой или бликов.
Ну и ещё эта текстура обычно рендерится в меньшем размере, чем финальный кадр (меньше, чем окно игры), потому что делать полную карту теней, это будет съедать куда больше ресурсов, а итоговые тени потом просто размывают (во многих пакетах её можно настраивать, можете сами в этом убедиться :D)
Далее обычно идёт скайбокс:
Обычно их сразу делят на дневной и ночной, и целиком рендерят шейдерами. В отличии от ресурспаков, которые часто его добавляют просто как куб с текстурами на внутренних гранях. Сделано это потому что те же звёзды на небе проще нарисовать шейдером, чем добавлять 8к/16к текстуру, к тому же шейдер проще анимировать.
После чего уже идёт сам рендеринг мира. Происходит он одновременно в 4 текстуры.
Albedo (Цвет)
Вообще зависит от реализации шейдера, тут, в Complementary, можно сразу заметить тени и в целом освещение (некоторые грани блоков темнее/светлее других), но нету тумана и пост-обработки. Но суть тут конкретно в том, чтобы передать цвет каждого объекта.
Normal (Нормали/Наклон)
Тут уже можно отчётливо заметить разные грани, грубо говоря, они помогают разработчику шейдерпака определить в какую сторону направлен каждый пиксель на картинке, условно: сиреневый - север, зелёный - верх, красноватый - восток и т.п. Это нужно, чтобы понять в какую сторону отражается свет от объекта (все ведь помнят, чему равен угол падения и угол отражения из физики?)
Specular (Материал/Отражение)
Это уже текстура отвечающая за свойства материала, например, чтобы от железного блока отражался мир. Обычно тут есть чёткий стандарт, который используют большинство PBR ресурспаков:
- Красный - более гладкие блоки, чем больше красного тем больше гладкости.
- Зеленый - металличность, чем больше зеленого - больше процент отражения.
- Синий - шершавость, обычно отвечает за логику отражения света во время дождя (т.е. чтобы прям лужи было видно или капающий дождь на поверхности лужи)
- Канал прозрачности - зависит от тестурпака, обычно отвечает за параметр emissive, это когда у эндермена в темноте глаза светятся, также можно сделать, например, с рудами.
Depth (Глубина)
Тут почти ничего не видно, разве что пару блоков снизу по центру, уже упоминал ранее - это глубина, где тёмные объекты - ближе к камере, а светлые дальше от неё. Нужно, чтобы можно было определить глобальные координаты для каждого пикселя на экране (это делается через матрицу modelView)
Ну а после этого за дело берётся последний шейдер-композитор, который объединяет эти 4 картинки в одну, например, берёт координаты пикселя, сравнивает их с картой тени (1 пункт, который мы обсуждали), если не находит там его, то рисует тень (если прям упрощённо), в зависимости от Depth-текстуры рисует тень (если пиксель дальше определённого расстояния, то делает его более серым, иначе не трогает) и другие эффекты, в зависимости от того, что захочет разработчик шейдерпака.
Ну и в результате получается то, что было на 1 картинке - финальный вариант.
Я тут не стал вдаваться в подробности той же отрисовки чанков или сущностей, как это всё дело оптимизирует Sodium, и как в это вмешивается Iris, да и думаю Вы это вряд ли переживёте)
Как работают эффекты применяемые на мир целиком?
Я думаю, теперь понять, как работает подобный эффект станет проще.
По сути у вас есть точка центра, от которой должны расходится волны. Для того чтобы они были в 3D, а не 2D (поверх экрана) используется эта самая Depth-текстура, а сами лучи строятся примерно по такому алгоритму:
- Ты "выстреливаешь" луч из камеры через пиксель на картинке.
- Ты спрашиваешь математическую формулу: "Какое расстояние от текущей точки луча до моей сферы"
- Допустим, формула говорит: "5 метров".
- Ты смело пропускаешь этот пиксель.
- Повторяешь вопрос для каждого пикселя. Если расстояние стало почти нулевым - значит, луч "ударился" о поверхность объекта! Пора красить пиксель в цвет энергии.
В данный момент проблема лишь одна - ты хочешь сделать такой эффект, а разработчик шейдерпака уже прописал все свои эффекты. Что делать? - Вариантов не много:
- Переписать сам шейдерпак и добавить туда эффект.
- После отрисовки финального кадра воспользоваться его сохранённой копией, её можно найти в фреймбуфере (это объект, который как раз и хранит те 4 текстуры, что мы обсуждали ранее), то есть мы берём, рисуем новый прямоугольник размером с экран прямо поверх мира, взяв за основу финальный кадр и Depth-текстуру из буфера и с их помощью рисуешь то, что тебе надо. Это и есть - пост-шейдеры, их может быть много и каждый может использовать результат предыдущего.
Единственный минус - тебе доступна не вся информация и вообще, по факту разработчики шейдерпаков плевать хотели, что ты там за фичу хотел написать и могут вообще не использовать некоторые из тех 4 текстур (хотя текстуру глубины обычно используют все шейдеры...). Но вот, к примеру я год назад, хотел целиком перенести отрисовку npc на движок Kool, но не смог разобраться с тенями, потому что Kool и Шейдерпаки не смогли определиться, где именно в мире будет находиться нпс. Например, взять ту же карту теней, как помните она округлая, есть шейдерпак, делающий подомное не только для теней, но и для мира в целом:
И вот представьте, Kool рендерит npc на одних координатах, а шейдерпак такой говорит: "Да плевал я на совместимость, лучше изогну тени для оптимизации", а Холлоу потом неделю сидит в RenderDoc и ругается, не понимая, куда делась тень от npc и почему она на соседней горе, а не возле npc.
В общем методика рабочая для всяких эффектов и партиклов, но есть ряд ограничений, если ты хочешь сделать что-то своё уникальное, как я год назад)
А насчёт серверов, по факту майн многопоточный, те же чанки подготавливаются для отрисовки в многопоточном режиме, да и рендеринг и серверная часть работают в отдельных потоках. Узким горлышком тут обычно называют сам подход к обновлению чанков на сервере. Представь себе 100 игроков, и все они одновременно прогружают свою зону чанков в мире. Это всё происходит в одном потоке. Но разделить это на несколько потоков не безопасно, т.к. это может поломать другие моды (условно захочет какой-нибудь мод собрать всех мобов рядом с игроками, а из-за того, что ты их раскидал по разным потокам возникнут конфликты или гонки данных), так что тут разве что или самому ядро и моды переписывать, или ждать, пока это сделают Mojang.