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

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

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

Сотрудничество: @it_bizdev
Download Telegram
Туман в Silent Hill — это оптимизация
https://youtube.com/shorts/Az1gxLJHjQw?si=oIxRUaGmCBWrtM-B

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

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

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

#devmath #разработкаигр #математикавиграх #computergraphics #gamedev
4👍3🔥3
Камера в 3D стоит на месте. Двигается весь мир вокруг неё

Вы летите камерой по уровню в редакторе: жмёте W — едете вперёд, ведёте мышью — поворачиваете. Камера ощущается обычным объектом сцены, просто через неё смотрят. Так и есть — на уровне движка. Но на уровне рендера всё наоборот.

Там камеры нет вообще. Есть точка (0, 0, 0), в которой намертво сидит наблюдатель и смотрит всегда вдоль одной оси. Когда вы «двигаете камеру вперёд», под капотом происходит обратное: весь мир — уровень, враги, ваша собственная модель — сдвигается назад, чтобы наблюдатель остался в нуле. Повернули камеру вправо? Это мир повернулся влево вокруг вас.

Это не метафора и не «ну как бы». Ровно это делает одна из матриц, через которые проходит каждая вершина по пути на экран: она перемещает не камеру к миру, а мир к камере — обратным преобразованием.

И тут важно не перепутать с другим. Живёт это преобразование в вершинной стадии конвейера — в вершинном шейдере, куда каждая вершина заезжает по пути на экран и пересчитывается заново каждый кадр. Оно не двигает объекты в сцене и ничего в мире не меняет — просто выражает их координаты в системе отсчёта камеры.

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

Тут же прячется ответ на вопрос, который наверняка зацепил глаз: если камера всегда в нуле и мир едет ей навстречу — выходит, статичные объекты тоже двигаются? И да, и нет. Отметка Static в движке — это про положение объекта в мире: оно зафиксировано, поэтому его матрицу Model (локальные → мировые координаты) можно применить один раз и запечь вершины прямо в мировом пространстве. Отсюда статик-батчинг, запечённый свет, окклюжн. А матрицу View (мир → камера) всё равно применяют каждый кадр ко всему подряд — и к статике, и к динамике. Поэтому неподвижная стена в мире честно стоит на месте, а по экрану едет, когда вы поворачиваетесь: в мире она не двинулась — двинулись вы, то есть система отсчёта. Коротко: Static — про матрицу Model, «мир вокруг камеры» — про View, это разные матрицы.

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

#геймдев #графика #математика #3d #камера #движки
🔥8👍3
Про отражения в Duke Nukem
https://youtube.com/shorts/2yb0Pn-n94k?si=I6mKOw7dFLzbh3xX

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

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

#devmath #gamedev #dukenukem #рендеринг #математикавиграх
🔥4
Почему далекие поверхности мерцают?

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

Виновник — ближняя плоскость near, выставленная слишком близко к нулю. Классика: near = 0.01, чтобы близкое не обрезалось, и far = 1000, чтобы видно было далеко. Выглядит безобидно. Но точность буфера глубины распределена не поровну. При near = 0.01 и far = 1000 у дальней плоскости соседние значения глубины (в типичном 24-битном буфере) ошибаются почти на 6 единиц — всё, что ближе ~6 единиц друг к другу, буфер там уже не различает. Отсюда мерцание.

И вот что неочевидно: далёкую точность рушит именно ближняя плоскость. Сдвиньте near с 0.01 до 1 при том же far — и различимость глубины вдали улучшится примерно в сто раз. Даже 0.01 → 0.1 даёт ×10. Решает не разность far − near, а их отношение far/near: чем оно больше, тем хуже вдали, а главный рычаг — не тянуть near к нулю.

Практический вывод, который снимает целый класс мерцания: держите near настолько далеко, насколько терпит сцена, а far — настолько близко, насколько можно. Чем у́же диапазон, тем чище глубина. Прежде чем крутить материалы — проверьте эти два числа.

А почему точность вообще нелинейна — это прямое следствие проекции и деления на w. Разберём в пятницу: в статье будет интерактив, где видно кривую глубины и что с ней делает ползунок near.

#геймдев #графика #математика #zfighting #рендер #камера
🔥17
Где на экране этот треугольник
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
🔥5
Поговорим про Overdraw

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

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

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

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

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

#devmath #графика #математика #рендер #оптимизация #gamedev
🔥11
Мобильный рендер: Дело не в мощности GPU
https://youtube.com/shorts/6WtXTERxLaI?si=65mL_ZQZno6b0fdO

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

Про шину помнить очень важно. Как она загружается и тому подобное. В статье про дроуколлы это разбиралось. Просто где-то с 2017 года я в основном разбирался в том, как запустить красивую картинку на «калькуляторе». Если взять даже этот проект, где нужно было под вебгл с поддержкой мобилки сделать рабочую красивую 3д сцену. Когда нужно завести миллионы полигонов в вебе или что еще страшнее в WebAR, то нужно знать что вертексные параметры можно подрезать.

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

#devmath #gamedev #unity3d #оптимизацияигр #мобильныйгеймдев
🔥5
Треугольник становится пикселями
https://dev-math.ru/articles/raster/

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

Разбираем:

— почему тонкий провод вдали мерцает и рвётся;
— и почему добавить ему полигонов бесполезно;
— отчего далёкие поверхности дерутся полосами друг сквозь друга — и при чём тут near, а не far;
— как один и тот же кадр выходит то в пять слоёв, то в один — не тронув ни единого полигона;
— почему на PlayStation 1 текстуры «плыли» — и что за одно деление это лечит.

#devmath #графика #математика #рендер #растеризация
🔥6🤝1
Please open Telegram to view this post
VIEW IN TELEGRAM
Фильтрация текстур

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

Текстура для шейдера — не картинка, а функция: подали координату, получили цвет. Только в памяти лежит конечный массив, и значение каждого текселя — это точка в центре его клетки. Ровно как пиксель на экране, про который мы говорили на прошлой неделе.

Когда вы подошли к стене вплотную, на один тексель приходится несколько экранных пикселей — и между известными точками надо что-то придумать. Nearest придумывает «возьму ближайшую» — получаются квадратики. Билинейная фильтрация придумывает «смешаю четыре соседние точки по расстоянию» — получается плавно, то есть мыло. Новой информации ни там, ни там не появилось: обе читают одну и ту же текстуру. Фильтр не создаёт новых деталей — он выбирает, как будет выглядеть их нехватка.

Отсюда вывод, который экономит время: если текстура мылит вблизи, крутить фильтр бесполезно. Мылит — значит текселей на этот кусок экрана просто мало. Лечится плотностью текселей: крупнее текстура, мельче тайл на поверхности, отдельная detail-карта поверх — или, если так и задумано, point-фильтр и пиксель-арт как художественное решение (Minecraft именно поэтому и не мылит).

И бонус. Если картинка не столько мылит, сколько «чуть мылит и чуть съехала» — обычно это забытые полтекселя. Значение текселя лежит в центре клетки, поэтому при переводе координаты в текселе-пространство вычитают 0.5. Забыли — и вся текстура уехала на полклетки; на кирпичной стене незаметно, на UI и атласах иконок видно сразу.

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

#devmath #графика #математика #рендер #текстуры #gamedev
🔥7👍3
10 000$ за баг в GTA
https://youtube.com/shorts/3iJa_wPmGKM?si=pdZ1e-VWVgGtrMhh

Семь лет GTA Online грузилась по пять минут — на любом железе. Чинить взялся не Rockstar, а игрок под ником t0st взялся, и хватило ему выходных. И получил за это 10 000$.

Оказалось, при каждом входе игра разбирала список из 63 тысяч товаров так, что на каждой позиции пересчитывала всю строку заново. Плюс проверка каждого с каждым на дубли. Около двух миллиардов холостых проверок — и минуты ожидания у миллионов людей.

#devmath #разработкаигр #gta #программирование #gamedev
🔥2👎1
Пол вдали превратился в кашу?

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

Дальше начинается знакомый заход. Раз мыло — значит мало разрешения. Дорогу поднимают с 1024 до 2048, потом до 4096, память растёт вчетверо на каждый шаг, стриминг начинает захлёбываться на въезде в локацию — а дорога вдали всё такая же мутная. Ловили такое?

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

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

По сути отсюда и ответ, почему апскейл ассета не помогает: текстура крупнее — обе оси следа растут одинаково, вытянутость остаётся той же. Вы отыгрываете один уровень подробности ценой вчетверо большей памяти, а перекос как был, так и остался. Настройка, которая чинит именно перекос, называется анизотропная фильтрация — та самая строчка «Anisotropic Filtering: 16x» в настройках графики. Она щупает вытянутый след не одной точкой, а несколькими вдоль длинной оси.

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

Ну а самое интересное — дальше. Откуда вообще берётся то самое одно число на весь след и почему в нём стоит именно максимум; сколько на деле стоит ×16 и почему не в шестнадцать раз дороже; и почему выкрученная анизотропия при выключенных мипмапах не работает вовсе — а такую комбинацию встречаешь регулярно. Разберём в пятницу. Там же пол, который можно наклонить ползунком и кликнуть по нему: видно и сам след пикселя в пространстве текстуры, и как в нём выстраиваются точки выборки, когда вы переключаете ×1 → ×16.

#геймдев #графика #математика #рендер #текстуры #gamedev
🔥43🫡2
Статья немного задерживается, так как автор в ловушке
17😱5
Почему далёкая текстура мерцает
https://dev-math.ru/articles/textures/

Мне очень нравится конечно составлять эти статьи, так как помимо описаний для понимания компьютерной графики, для меня они являются погружением в ностальгическое прошлое. Как всё было придумано и зачем.

В статье разберем:

- почему тексель — не пиксель и что на самом деле делают nearest и билинейная фильтрация;
- откуда берётся мерцание вдали — алиасинг при уменьшении — и как его лечат мипмапы;
- почему при взгляде вдоль пола картинка мылится и что с этим делает анизотропная фильтрация;
- сколько всё это весит в памяти и как устроено блочное сжатие BCn и ASTC;
- и как эти решения складываются в один запрос к сэмплеру.

Ставьте 🔥 и делитесь с друзьями, если статья была интересна. В будущем возможно я сошью это в какой-то учебник по компьютерной графике, который уже будет за символическую цену, чтобы желающие могли поддержать автора так сказать. А пока поделиться постом лучшая поддержка.

#devmath #графика #математика #рендер #текстуры
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13