Математика в Gamedev по-простому
495 subscribers
61 photos
1 video
3 files
26 links
Как на самом деле работают стрельба, толпа NPC, графика, физика тканей.

Канал про то, что ИИ не заменит: понимание.

Разборы на пальцах, рабочий код, интерактивы. dev-math.ru

Сотрудничество: @it_bizdev
Download Telegram
Дизеринг и гамма-коррекция: почему градиенты «бандятся» и откуда грязь в тёмных сценах
https://dev-math.ru/articles/dithering/

Люблю писать такие статьи. Когда я начинал заниматься что графикой, что трекингом, меня очень часто больше всего увлекало на сколько это мир физических ограничений. Нюансы вывода изображения на экран. Нюансы человеческого зрения и тому подобное. Параллельно изучая такие технические моменты ещё и узнаешь, а как что в этом мире работает. И это то, чем меня увлекла когда-то клиентская разработка в отличии от бекенда. Помимо мнгновенного результата действий, то что ты не живешь в чистой абстракции, а сталкиваешься с нюансами практического мира.

Итак. Мы разбирали по кускам тему всю неделю. Теперь полная статья с разбором, интерактивами и так далее. Бандинг, дизеринг, как работает гамма-коррекция, что такое линейное цветовое пространство и зачем оно надо (да и попутно объясняется зачем была нужна гамма).

На сайте произошел большой редизайн. Светлая тема мне нравится наверное чуть больше. Но в основном так как я решил делать игру во вселенной своих комиксов и рилсов. Позже анонс будет размещён на сайте по игре (думаю я управлюсь за 4 месяца ну или хотя бы постараюсь). А так репосты и 🔥 как всегда приветствуются. Это мотивирует писать статьи и дальше. Ведь кому-то это интересно.

#devmath #графика #квантование #дизеринг #sRGB #linearworkflow
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Обернул шейдер в if, чтобы считать реже. А быстрее не стало

Вы нашли в шейдере тяжёлый кусок — дорогой цикл, лишний проход, ветку с кучей математики — и обернули его в if: мол, для половины пикселей эту дорогую ветку пропустим и сэкономим. Пропустили. А кадр не разгрузился ни на сколько.

Видеокарта прячет задержку памяти тем, что держит не один поток, а тысячи. Один встал на выборке из памяти — планировщик тут же пускает считать другой, у которого данные уже приехали; пока первый ждёт свою порцию, вычислители заняты чужой работой. Вот зачем на GPU тысячи потоков. Мало потоков— прятать нечем, и тогда видеокарта реально встаёт на сотни тактов ожидания.

Но тут и зарыта плата. Одно ядро GPU — это не маленький процессор, а простая и небыстрая линия: ни хитрого предсказания ветвлений, ни глубоких кэшей, один поток на нём медленнее, чем на CPU. Сила — в количестве. И эти линии не независимы: они идут строем, группами (у NVIDIA — по 32), и вся группа исполняет одну и ту же инструкцию разом, каждая над своими данными. Это SIMT — одна команда, много потоков. Представьте не тысячу гонцов, у каждого свой маршрут, а колонну, марширующую в ногу: шаг у всех общий.

Собственно, вот и разгадка вашего if. У этого есть имя — дивергенция ветвлений (branch divergence, ещё говорят «расхождение варпа»). Когда потоки одной группы на развилке расходятся, железу приходится прогнать обе ветки по очереди, отключая на каждой те линии, которых там нет. Группа идёт так же долго, как все пути её потоков вместе взятые. Ваш «пропустить дорогой код» экономит, только если его пропускает вся колонна разом. Один поток свернул в тяжёлую ветку — и остальные тридцать один стоят и ждут его.

По сути, ветвления тут не под запретом — платите вы не за if, а за расхождение внутри группы. Противоположность расхождения — когерентность: это когда соседние потоки заняты одним и тем же, все сворачивают в одну сторону и шагают в ногу. Такая когерентная ветка почти бесплатна — вторая сторона просто не исполняется.

Пример. Материал с enum-режимом и switch (_Mode) в шейдере (классический ubershader, куда свалили все варианты освещения). Если режим один на весь материал — uniform-параметр, — весь варп читает одно значение и идёт одной веткой, остальные просто не исполняются: расхождения нет, дёшево. Но стоит выбирать режим попиксельно — из маски материалов, material-ID в G-буфере, слоёв терреина — и соседние пиксели одного варпа уходят в разные ветки: на стыках материалов варп прогоняет все режимы по очереди.

И та же боль в raymarching и SDF: часть лучей упирается в поверхность за пару шагов, часть марширует до предела — и вся группа топчется в цикле, пока не закончит самый глубокий луч. Ранний выход по одному лучу ничего не даёт: время группы держит самый медленный поток.

В четверг — почему переключение шейдера или материала сбивает весь этот разгон: смена стейта и флаш конвейера, и чем тут спасают батчинг с инстансингом.

#devmath #графика #математика #gpu #simt #оптимизация
🔥11👍1
Гонитесь за числом дроуколлов, а тормозит другое

Допустим в оптимизации вы гонитесь за числом draw calls: объединяете меши, чистите объекты — и счётчик Batches ползёт вниз. Дело конечно хорошее. А кадр работает всё так же медленно, особенно на мобилке. Смотрите профайлер внимательнее: draw calls упали, а вторая цифра рядом, SetPass calls, — нет. Что это за цифра?

Дело в том, что это две разные вещи. Batch (draw call) — это просто команда «нарисуй» на текущем состоянии. А SetPass случается, только когда состояние надо СМЕНИТЬ: другой шейдер, другая текстура, другой блендинг, другой Z-тест. Сто вызовов на одном материале — это один SetPass и куча дешёвых команд следом. А смена материала между вызовами — это новый SetPass: перенастройка конвейера, и в тяжёлом случае видеокарта сливает всё, что уже запустила, прежде чем переключиться. Это и есть флаш конвейера (pipeline flush).

Собственно, вот тема дня: цену кадру набивает не сколько вы рисуете, а сколько раз между вызовами меняете стейт. Поэтому один материал со статик-батчингом летит — сотни мешей идут одним SetPass'ом; а десяток материалов вперемешку кладёт кадр, хотя вы не добавили ни полигона — вы добавили переключений контекста. Жёстче всего флаш на смене render target: у мобильных тайловых GPU это выгрузка целого тайла в память,.

Техника дальше весьма логичная. Меньше разных материалов и шейдеров, свести похожее в один материал и атлас, сортировать по стейту, чтобы объекты с одним состоянием шли подряд. Батчинг, инстансинг и SRP Batcher по-разному делают одно и то же — держат стейт на месте, чтобы конвейер переставлять приходилось как можно реже.

Подробнее разберу завтра.

#devmath #графика #математика #gpu #батчинг #оптимизация
🔥91
Путь одного draw call

Всю неделю мы считали, чем платим за кадр: в понедельник — командами, во вторник — байтами на шине, в среду — когерентностью варпов, в четверг — сменами состояния. Обещанная статья готова: «Путь одного draw call» — проходим дорогу команды рисования целиком, от строчки в вашем коде до пикселя на экране, со всеми четырьмя валютами на местах.

В общем я довольно подробно описал разные части графического конвейера, и не как стандартную схему и так далее, а более практично в контексте движка.

Читать: https://dev-math.ru/articles/drawcall/

#devmath #графика #математика #gpu #drawcall #оптимизация
🔥12
Туман в Silent Hill — это оптимизация
https://youtube.com/shorts/Az1gxLJHjQw?si=oIxRUaGmCBWrtM-B

1999 год, первая PlayStation. Команде нужен целый город в реальном времени — а железо не тянет: мир схлопывается в маленький пузырь вокруг героя, и на голой границе дома выскакивают из пустоты прямо на глазах. Конкуренты прятали слабость железа в пре-рендеренных задниках — красивых, но с намертво прибитой камерой. Silent Hill пошла иначе: границу мира просто залили туманом.

Дальше не видно — и не нужно. Всё, что за молоком, не рисуется вовсе, и кадр укладывается в бюджет. А бонусом — страх: силуэт проступает не на горизонте, а в двух шагах. Ограничение железа стало фирменным ужасом жанра.

Так что самый страшный монстр серии появился из-за бюджета кадра.

#devmath #разработкаигр #математикавиграх #computergraphics #gamedev
4👍3🔥3
Про отражения в Duke Nukem
https://youtube.com/shorts/2yb0Pn-n94k?si=I6mKOw7dFLzbh3xX

В зеркале Duke Nukem 3D — не отражение, а построенная за стеной вторая комната.

Движок 96-го не умел в отражения: за зеркалом просто копировали комнату целиком, и твой двойник вставал по ту сторону вживую, кадр за кадром.

#devmath #gamedev #dukenukem #рендеринг #математикавиграх
🔥4
Где на экране этот треугольник
https://dev-math.ru/articles/spaces/

Всю неделю я рассказывал о том где именно находится вершина. Что камеру в 3D на самом деле не двигают — двигается весь мир вокруг неё. Что далёкие поверхности мерцают не из-за материалов, а из-за near, прижатого к нулю. Обещанная статья готова: проходим дорогу одной вершины целиком — от строчки в вашем коде до конкретного пикселя на экране.

Плюс разбор, почему отметка Static и статик-батчинг в Unity вообще не спорят с «мир едет навстречу камере».

Да, если серия нравится, и статья интересна. Делимся ей с друзями и ставим 🔥. А то я что, не блогер, это не сказать.

#devmath #графика #математика #3d #mvp #проекция
🔥14
Квиз: Как игры рисуют кадр?
https://youtube.com/shorts/F6enWvT6lQk?si=pXOCdl87qk5F6BiC

А что вы думали? Статьи просто так? А ну взяли ручки и проходим контрольную. По статье по дроуколлам и пространствам.

Собрал небольшой видео-квиз. Такие штуки вообще проходить полезно, так как информация лучше запоминается, когда с ней сталкиваешься несколько раз в разных контекстах. Хотя мы пока разбираем только базу.

#геймдев #игры #графика #квиз #devmath
🔥5
Как вы получаете пиксель

Далёкие провода ЛЭП, перила моста, ванты, тонкая антенна на крыше — вроде на месте, но стоит чуть повести камерой, и линия начинает рваться: тут кусок есть, тут его нет, и всё это дрожит. Первое объяснение, которое приходит в голову: «ну, деталь мельче пикселя, разрешения не хватает». Оно почти верное, но только почти.

Растеризатор (узел, который превращает три вершины в закрашенные пиксели) не считает, какую долю пикселя накрыл треугольник. Для него пиксель — вообще не маленький квадратик, который можно зацепить краем. Пиксель — это одна точка в его центре. И треугольник закрашивает пиксель тогда и только тогда, когда накрывает эту точку. Так записано в правилах растеризации Direct3D, так же ведут себя OpenGL и Vulkan.

Собственно, отсюда и мерцание. Он не «слишком маленький» — он проходит между центрами пикселей. Камера сдвинулась на полпикселя: где-то центры попали внутрь, где-то нет — по линии побежали мерцающие дырки.

Вывод: тонкую геометрию не чинят геометрией. Добавить проводу полигонов бесполезно — толще в экранных пикселях он от этого не станет. Работает другое: держать деталь шириной хотя бы в пиксель на экране (билборд или шейдер, который не даёт ширине упасть ниже пикселя), брать несколько точек покрытия на пиксель (MSAA), либо увести деталь из геометрии в текстуру, где у неё есть альфа и фильтрация. Если у вас мерцают перила — разговор не про меш, а про то, чем вы их рисуете.

Ну и крайний случай: треугольник может не нарисоваться совсем. Мелкий треугольник или длинный тонкий слайвер проскакивает в зазоре между центрами и не даёт в этом кадре ни одного фрагмента, хотя площадь у него есть и вершины на месте. Сдвинулись на полпикселя — даёт. Для геометрии размером около пикселя вопрос «виден или нет» решает субпиксельная позиция.

И из того же правила растёт вопрос, о котором обычно не думают. А если точка-сэмпл легла ровно на ребро, общее у двух соседних треугольников меша? Закрасить у обоих — по шву пройдёт двойная краска, на прозрачности это заметный тёмный рубец. Не закрасить ни у кого — по мешу побежит строчка дырок. Железо этот случай разруливает, и разруливает одинаково у всех.

Как именно растеризатор решает «внутри или снаружи», как он поступает с сэмплом на общем ребре и кто из тысячи треугольников, попавших в один пиксель, остаётся на экране — разберём в пятницу.

#devmath #графика #математика #рендер #растеризация #gamedev
🔥4
Поговорим про Overdraw

Сложная сцена проседает по fps. Первым делом вы идёте резать полигоны: упрощаете меши, расставляете LOD'ы, чистите вершины. Треугольников стало заметно меньше — а кадр не полегчал. Профайлер показывает, что видеокарта всё время сидит в пиксельном шейдере, а не в геометрии.

Дело в том, что один пиксель за кадр закрашивается не по разу. Поставьте три объекта друг за другом по глубине: дальше всех стена, перед ней ящик, перед ящиком стойка. Рисуются они в том порядке, в каком их отдал движок, — и если дальняя стена попадёт под кисть первой, она закрасит свои пиксели и целиком отработает шейдер: текстуры, свет, туман. Потом поверх ляжет ящик — и тоже целиком. Потом стойка. На экране виден только ближний слой, а пиксельный шейдер отработал по числу слоёв. Это и есть overdraw — работа, оплаченная за пиксели, которые в итоге никто не увидит.

Плотный интерьер, свалка пропов, наслоённые полупрозрачные эффекты — там overdraw набегает быстро. Горит fillrate. Fillrate (филлрейт) — это скорость, с которой видеокарта заливает пиксели в буфер кадра: её пропускная способность на закраску, отдельная от пропускной способности на геометрию.

Поймать довольно просто. Подведите камеру вплотную к скоплению объектов: если fps проседает там, где в кадр влезает даже меньше геометрии, но она плотно перекрывается по глубине, — вы уперлись в overdraw, а не в треугольники. А ещё лучше — включите режим overdraw: он есть у Unity, Unreal и мобильных профайлеров. Места, перекрашенные много раз, светятся всё ярче. Это буквально ваш счёт за кадр, нарисованный поверх сцены, — один раз увидев его, вы перестаёте гоняться за полигонами.

Видеокарта умеет выбрасывать перекрытые пиксели ещё ДО того, как посчитает их шейдер. Но у этого механизма есть условие, и когда объекты рисуются «как легли в сцене», условие не выполняется — и каждый невидимый слой всё равно оплачен. Что это за условие и что делать, когда шейдеры настолько дорогие, что и сортировка не спасает, — разберём в пятницу.

#devmath #графика #математика #рендер #оптимизация #gamedev
🔥9