3D-ассеты через агента Полигональный бюджет: как сокращать и чем за это платят
0%

Полигональный бюджет: как сокращать и чем за это платят

Полигональный бюджет: как сокращать и чем за это платят

Глава «Экспорт в 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-развёртка и текстуры»: как объект получает координаты для текстур, что такое плотность текселя, зачем запекать — и почему у робота из главы про кейс текстур нет вовсе, и это решение, а не упущение.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов
Дальше