Полигональный бюджет: как сокращать и чем за это платят
Глава «Экспорт в GLB» закончилась измерением и обещанием: у безобидной кружки 407 884 треугольника и 9,4 МБ, сокращать есть чем, начинать надо с замера. Замер сделан — пора тратить.
Эта глава про то, какие рычаги вообще есть, сколько каждый даёт и чем за него платят. Ни одной вершины руками мы при этом не тронем: у модели, собранной кодом, сокращение — это правка чисел, а не работа скальпелем.
Откуда берётся 407 тысяч
Полигонаж процедурной модели — это произведение, а не сумма. Каждый шаг цепочки из главы «Первая модель» выглядит скромно, пока смотришь на него в одиночку:
| Шаг | Что делает с геометрией | Множитель |
|---|---|---|
| Цилиндр, 96 сегментов | задаёт исходную плотность кольца | — |
Solidify |
строит вторую оболочку и торец по краю | ×2 плюс кольцо |
Bevel, 3 сегмента |
каждое подходящее ребро превращается в три | ×3…×4 |
Subsurf, уровень n |
делит каждый четырёхугольник на четыре | ×4ⁿ |
| Кривая ручки | сечение × шагов вдоль пути | ×(сечение × длина) |
Подразделение — главный виновник. Уровень 2 это ×16, уровень 3 — уже ×64, и разница между ними в одной цифре в скрипте.
Точное произведение в уме не считается: сколько рёбер получат фаску, решает
угловое ограничение limit_method = 'ANGLE', а сколько четырёхугольников
породит торец Solidify — зависит от того, что осталось от верхней грани.
Поэтому единственный честный ответ на вопрос «сколько у меня треугольников» —
тот же, что во всём этом треке: посмотреть, а не прикинуть.
Замер до экспорта
Считать по файлу, как в главе про экспорт, — правильно, но поздно: файл уже записан. Внутри Blender то же число берётся из вычисленной сцены, то есть уже с применёнными модификаторами:
import bpy
def triangles(obj):
deps = bpy.context.evaluated_depsgraph_get()
evaluated = obj.evaluated_get(deps)
mesh = evaluated.to_mesh()
mesh.calc_loop_triangles()
count = len(mesh.loop_triangles)
evaluated.to_mesh_clear() # без этого копия меша остаётся в памяти
return count
for obj in bpy.context.scene.objects:
if obj.type in {'MESH', 'CURVE'}:
print(f"{obj.name:20s} {triangles(obj):>8d}")
Три детали, каждая из которых кого-то подводила.
evaluated_get(deps), а не obj.data. Исходный меш ничего не знает про
модификаторы: там по-прежнему цилиндр на 96 сегментов. Число, снятое с него,
меньше правды в сотни раз — и оно очень успокаивает.
to_mesh_clear(). Временный меш живёт до явной очистки. В цикле по сотне
объектов забытая очистка съедает память быстрее, чем вы успеваете это заметить.
Кривые считаются тоже. Ручка кружки — не меш, а кривая с толщиной, и в экспорт она уедет триангулированной. Пропустить её при подсчёте — получить приятное число и неприятный файл.
Дальше бюджет перестаёт быть намерением и становится проверкой:
BUDGET = {"Mug": 20_000, "Robot": 40_000}
over = [(o.name, triangles(o), BUDGET[o.name])
for o in bpy.context.scene.objects
if o.name in BUDGET and triangles(o) > BUDGET[o.name]]
assert not over, f"бюджет превышен: {over}"
Число в словаре — это решение, принятое один раз и записанное. Число в голове — это то, что через неделю окажется у каждого своим.
Лестница: что даёт каждый рычаг
Прогоняем ту же кружку, меняя по одному параметру. Каждая строка — отдельный замер, а не пересчёт предыдущей:
| Конфигурация | Треугольники | GLB |
|---|---|---|
Исходная: 96 сегментов, Subsurf 3, Bevel 3 сегмента |
407 884 | 9,4 МБ |
render_levels 3 → 2 |
101 968 | 2,4 МБ |
render_levels 2 → 1 |
25 492 | 620 КБ |
| Плюс цилиндр 96 → 32 сегмента | 8 596 | 214 КБ |
Плюс Decimate с коэффициентом 0,5 |
4 298 | 112 КБ |
Первая строка и последняя различаются в девяносто пять раз. Между ними — четыре правки чисел, ни одного касания геометрии руками и минут пятнадцать работы.
Обратите внимание на порядок величин. Одна цифра в render_levels стоит
дороже, чем всё остальное вместе: подразделение уровня 3 отдаёт три четверти
файла. Именно поэтому оно первое в списке рычагов — и именно поэтому его так
часто оставляют по умолчанию, ни разу не спросив, зачем кружке 64-кратное
уплотнение сетки.
Чем платит каждый рычаг
Сокращение без цены не бывает. Цена у рычагов разная, и знать её важнее, чем знать выигрыш.
| Рычаг | Выигрыш | Цена | Когда применять |
|---|---|---|---|
render_levels у Subsurf |
кратный, ×4 на уровень | силуэт грубеет на крупных планах | первым делом, всегда |
| Число сегментов примитива | кратный | на цилиндре становятся видны грани | если объект не в кадре крупно |
segments у Bevel |
заметный | скругление превращается в плоскую фаску | почти всегда безболезненно |
| Угловое ограничение фаски | умеренный | ничем, если угол подобран | сразу, это не оптимизация, а корректность |
Decimate COLLAPSE |
любой, до заданного коэффициента | рвёт четырёхугольную сетку, портит UV, делает скиннинг непредсказуемым | только для статичных LOD |
Decimate PLANAR |
хороший на плоских деталях | съедает мелкий рельеф | архитектура, техника, ящики |
| Пересобрать генератор дешевле | лучший результат на треугольник | ваше время | если ассет живёт в проекте долго |
Строка про COLLAPSE — та, из-за которой стоит читать таблицу целиком.
Decimate работает всегда и на любой модели, поэтому к нему тянутся первым. Но
он единственный в списке, кто портит топологию: вместо аккуратных
четырёхугольников остаётся треугольный винегрет. На статичном ящике это никого
не волнует. На модели, которую предстоит развернуть в UV
(глава 09) или привязать к скелету
(глава 10), это отложенная поломка:
разъедутся швы, поплывут веса, и виноват будет кто угодно, только не тот, кто
поставил коэффициент 0,3 три недели назад.
Рычаг, которого нет у скана
Здесь стоит остановиться, потому что это довод в пользу всего подхода из этого трека, и он редко проговаривается.
У модели, полученной из генератора по картинке или со сканера, нет исходника. Есть только готовая сетка. Единственный способ сделать её легче — что-нибудь у неё отнять: декимировать, ретопологизировать, перерисовать руками. Любое сокращение здесь — порча, вопрос лишь в аккуратности.
У модели, собранной кодом, исходник есть — это сам код. «Сделать легче» тут
означает не «испортить осторожно», а «пересобрать дешевле»: те же формулы,
другие числа, полноценная чистая сетка на выходе. Разница видна в таблице выше:
переход с Subsurf 3 на 2 даёт вчетверо меньше треугольников и ровно ту же
модель, просто с менее гладкими скруглениями. Decimate такого не умеет в
принципе.
Отсюда практический вывод для LOD: облегчённую версию дешевле и честнее сгенерировать заново другими параметрами, чем декимировать полную. Обе версии тогда получаются из одного описания, обе имеют нормальную топологию, и правка формы автоматически доезжает до обеих — ровно та же выгода, что в главе «Живой кейс» дала одна модель на две команды вместо двух копий.
Где сокращение видно раньше, чем в силуэте
Неочевидное: первым ломается не форма, а закраска светом — то, что в Blender называют шейдингом.
Сглаживание в Blender опирается на нормали, а нормали считаются по граням. Пока сетка регулярная, автоматическое сглаживание из главы «Первая модель» даёт ровный переход. После Decimate грани становятся разного размера и разной формы, нормали в них разъезжаются — и на модели проступают пятна и полосы, хотя силуэт остался прежним.
Лечится тем, что сглаживание пересчитывается после сокращения, а не до:
dec = obj.modifiers.new("Decimate", 'DECIMATE')
dec.decimate_type = 'COLLAPSE'
dec.ratio = 0.5
bpy.ops.object.modifier_apply(modifier=dec.name)
bpy.ops.object.shade_auto_smooth(angle=math.radians(30))
И проверяется не числом, а картинкой: рендер до и после, как в главе «Материалы, свет и рендер». Счётчик треугольников про пятна на боку ничего не скажет — он честно покажет, что стало вдвое легче.
Обобщение, полезное далеко за пределами 3D: у оптимизации почти всегда есть второй, не измеряемый вашим счётчиком выход. Вы смотрите на метрику, ради которой всё затевалось, а ломается соседнее свойство, которое никто не мерил. Поэтому к любой оптимизации нужны две проверки: та, что показывает выигрыш, и та, что ловит порчу.
Треугольники — не весь бюджет
Последнее, и это чаще всего пропускают.
Полигонаж — только одна из статей расхода. Рядом стоят как минимум две другие.
Количество на экране. Арифметика из главы «Живой кейс»: 32 тысячи треугольников — приемлемо для персонажа и катастрофа для пятидесяти фигур на доске. Первый вопрос к любому числу полигонов — «сколько таких видно одновременно».
Число вызовов отрисовки. Каждый материал на объекте — обычно отдельный вызов. Модель на 30 тысяч треугольников с одним материалом может обойтись дешевле, чем десять моделей по 3 тысячи с шестью слотами каждая. Робот из главы про кейс несёт шесть именованных слотов — это осознанная плата за перекраску в рантайме, и её тоже стоит держать в уме, когда считаете бюджет сцены.
Отсюда правило приёмки: бюджет формулируется не как «не больше N треугольников на модель», а как «не больше N треугольников и M материалов при K объектах в кадре». Первое проверяется скриптом за секунду и ничего не гарантирует; второе требует посмотреть на сцену целиком — и только оно отвечает на вопрос, будет ли игра идти.
Что дальше
Мы сократили геометрию, и теперь у кружки 214 КБ вместо 9,4 МБ. На настоящем ассете с текстурами эта победа выглядела бы иначе: геометрия перестала бы быть главной статьёй веса, потому что её место заняли бы картинки.
Дальше — «UV-развёртка и текстуры»: как объект получает координаты для текстур, что такое плотность текселя, зачем запекать — и почему у робота из главы про кейс текстур нет вовсе, и это решение, а не упущение.