Как клиент рисует
Эта страница - карта для контрибьютора, который хочет читать код рендерера. Она в нескольких строках объясняет идею каждой части и отправляет к заметкам, где лежат правила и числа, поэтому сами заметки не пересказывает.
Рисуют два крейта. mcrs_minecraft_render рисует ландшафт и небо, а mcrs_minecraft_entity_render - мобов. Рендерер относится к слою 6 в карте крейтов, поэтому он работает с данными нижних слоев и не содержит сетевого, клиентского или серверного кода.
Ландшафт лежит в нескольких больших буферах GPU
Заголовок раздела «Ландшафт лежит в нескольких больших буферах GPU»Отдельного буфера на каждый чанк у рендерера нет. Он записывает меши в несколько больших буферов GPU, их называют аренами. Арен три, и в них лежат квады, модели и грани. Аллокатор упаковывает блоки к нулевому смещению, а арена растет шагами и только до самого старшего занятого байта. На 96 колонках арены заполнены примерно на 50% от 4,3 ГБ, то есть 768 МБ квадов, 2,5 ГБ моделей и 1 ГБ граней, а весь ландшафт рисуется 7 вызовами отрисовки. Числа взяты из раздела Render distance в PERF.md и сняты на M4 Max.
Весь кадр собирает одна система рендера. Она загружает данные, разрешает временные метки, запускает отсечение и выполняет единственный проход рендера, в котором рисуются небо, непрозрачный ландшафт, облака и ландшафт со смешиванием. По DECISIONS.md такая схема сократила кадр с пяти энкодеров и шести буферов команд до одного энкодера и двух буферов команд.
Что рисовать, выбирает GPU
Заголовок раздела «Что рисовать, выбирает GPU»Отсечение (culling) отбрасывает геометрию, которую камера не видит. В mcrs этим занимается вычислительный проход на GPU, а CPU в отсечении не участвует. Проход проверяет каждую группу квадов относительно вида и сам записывает аргументы отрисовки. На 96 колонках отсечение занимает на GPU от 0,46 до 0,49 мс в сценариях PERF.md, которые сняты на M4 Max с macOS в 2560x1440, а частоту GPU удерживает MCRS_GPU_HOT=16384.
Окклюзия проверяется по глубине прошлого кадра
Заголовок раздела «Окклюзия проверяется по глубине прошлого кадра»Группа может попадать в вид и все равно прятаться за холмом. В DECISIONS.md измерено, как часто это случается: на 96 колонках 7,96 млн из 15,3 млн нарисованных треугольников были за ландшафтом прошлого кадра. Тест окклюзии убирает такие треугольники. Он работает по пирамиде глубины, построенной из прошлого кадра, и идет в два прохода.
Первое отсечение скрывает то, что скрывает старая пирамида, и оставляет кандидатами группы, которые скрыл только этот тест. Затем рендерер перестраивает пирамиду из глубины текущего кадра, и второе отсечение возвращает кандидатов, которые все-таки видны, во второй набор вызовов отрисовки. Благодаря второму проходу ничего не появляется с опозданием на кадр.
Проход окклюзии стоит времени, даже когда рисовать нечего, поэтому он запускается, только когда в памяти есть группы. Его можно выключить через MCRS_OCCLUSION=0 и увидеть, что он дает. На 96 колонках он снижает кадр GPU с 6,0 мс до 3,7 мс.
Позы мобов считает и рисует GPU
Заголовок раздела «Позы мобов считает и рисует GPU»Дизайн в docs/entity-render.md рассчитан на 100 000 мобов в одном мире без просадки кадра. Это цель заметки, измеренного результата за ней пока нет. В заметке есть раздел про стресс-сцены и пороги, а числа для каждого размера лежат в docs/entity-render-budget.md.
ECS хранит состояние каждого моба, а GPU держит проекцию этого состояния в постоянном слоте. CPU пишет в слот, только когда что-то меняется, то есть приходит пакет, проходит тик или меняется ввод скрипта. Остальное делает GPU, причем только для видимых мобов: он отсекает, считает позы, расширяет грани и рисует. Позы задает Molang, язык выражений, на котором написаны определения клиентских сущностей, а mcrs_molang_wgsl переводит каналы Molang в шейдерный код WGSL.
Заметка описывает три пути выполнения, и рендерер выбирает один из них при старте по возможностям графического адаптера.
| Путь | Что ему нужно |
|---|---|
| Full | Compute-шейдеры и vertex storage |
| Compatibility | Режим совместимости WebGPU |
| Minimal | Без compute, для WebGL2 |
Сама веб-сборка до устойчивого кадра не доходит.4
Далекие мобы - плоские картинки
Заголовок раздела «Далекие мобы - плоские картинки»Далекому мобу не нужен полный риг, то есть набор костей, который задает позу модели. Для каждой пары из модели и текстуры рендерер запекает небольшой набор видов. В наборе 16 поворотов по yaw и 9 углов возвышения по 64 на 64 текселя, и он занимает около 4,7 МБ. Далекий моб после этого рисуется как импостор, то есть одним квадом, который показывает ближайший вид. Набор запекается лениво, когда модель и текстура впервые появляются на активном мобе. У полных ригов есть лимит, а у импосторов его нет. Заметка фиксирует цену каждого шага и решения, которые не помогли.
Где лежат правила и числа
Заголовок раздела «Где лежат правила и числа»| Тема | Файл |
|---|---|
| Почему рендерер работает так, с числами | DECISIONS.md |
| Время кадра, память и методика | PERF.md |
| Дизайн рендеринга сущностей | docs/entity-render.md |
| Модель нагрузки и бюджет сущностей | docs/entity-render-budget.md |
| Что клиент умеет сегодня | Клиент сегодня |
Запланированы еще две работы: ограничение памяти на 96 колонках и рендеринг внутри приложения-хоста, которому принадлежат окно и камера.56
Footnotes
Заголовок раздела «Footnotes»-
В работе#16 Первая задача перезапуска рендерера: раскладки, объявленные один раз для Rust и WGSL. ↩
-
В работе#37 Последняя задача перезапуска рендерера: тонированные оверлеи сводятся в один спрайт. ↩
-
В работе#330 Отслеживает сравнение отложенного рендерера с базовой линией до перестройки. ↩
-
В работе#344 Отслеживает веб-клиент, который доходит до устойчивого кадра. ↩
-
В работе#337 Отслеживает ограничение памяти клиента на 96 колонках. ↩
-
В работе#506 Отслеживает рендеринг, встроенный в приложение-хост, которому принадлежат окно и камера. ↩
Источники и проверка
Проверено: 26.4-snapshot-7, 51d45c26a, 2026-11-09
Переведено с версии 51d45c26a