Оптимизация рендеринга сцены из диснеевского мультфильма «Моана». Часть 1

http://pharr.org/matt/blog/2018/07/08/moana-island-pbrt-1.html
  • Перевод
Walt Disney Animation Studios (WDAS) недавно сделала сообществу исследователей рендеринга неоценимый подарок, выпустив полное описание сцены для острова из мультфильма «Моана». Геометрия и текстуры для одного кадра занимают на диске более 70 ГБ. Это потрясающий пример той степени сложности, с которой сегодня приходится иметь дело системам рендеринга; никогда ранее исследователи и разработчики, занимающиеся рендерингом вне киностудий, не могли поработать с подобными реалистичными сценами.

Вот, как выглядит результат рендеринга сцены с помощью современного pbrt:


Остров из «Моаны», отрендеренный pbrt-v3 в разрешении 2048x858 с 256 сэмплами на пиксель. Общее время рендеринга на 12-ядерном/24-поточном инстансе Google Compute Engine с частотой 2 ГГц с последней версией pbrt-v3 составило 1 ч 44 мин 45 с.

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

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

Хэш, которого не было


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

Впервые я уяснил этот урок во время продакшена Toy Story 2. Однажды кто-то заметил, что удивительно много времени тратится на парсинг файлов описаний сцен RIB. Кто-то ещё из команды рендеринга (полагаю, это был Крейг Колб) запустил профайлер и начал разбираться.

Оказалось, что бОльшую часть времени парсинга занимали операции поиска в хэш-таблице, использовавшейся для string interning. Хэш-таблица имела довольно маленький размер, наверно, 256 элементов, а когда в одну ячейку хэшировалось несколько значений, она организовывала цепочку. После первой реализации хэш-таблицы прошло много времени и в сценах теперь были десятки тысяч объектов, поэтому такая маленькая таблица быстро заполнялась и становилась неэффективной.

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

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

int hash(const char *str) {
    return str[0];
}

(Прости меня, Pixar, если я раскрыл ваш сверхсекретный исходный код RenderMan.)

«Хэш»-функция была реализована ещё в 1980-х. В то время программист, вероятно, посчитал, что вычислительные затраты на проверку влияния всех символов строки на значение хэша будут слишком высоки и не стоят того. (Думаю, что если в сцене было всего несколько объектов и 256 элементов в хэш-таблице, то этого было вполне достаточно.)

Свой вклад внесла и ещё одна устаревшая реализация: с момента начала создания студией Pixar своих фильмов, названия объектов в сценах довольно сильно разрослись, например, «BuzzLightyear/LeftArm/Hand/IndexFinger/Knuckle2». Однако какой-то начальный этап конвейера использовал для хранения названий объектов буфер фиксированной длины и сокращал все длинные названия, сохраняя только конец, и, если повезёт, добавлял в начале многоточие, давая понять, что часть названия утеряна: "…year/LeftArm/Hand/IndexFinger/Knuckle2".

В дальнейшем все названия объектов, которые видел рендерер, имели такую форму, хэш-функция хэшировала их всех в один фрагмент памяти как ".", а хэш-таблица на самом деле была большим связанным списком. Добрые старые времена. По крайней мере, разобравшись, мы довольно быстро исправили эту ошибку.

Интригующая инновация


Этот урок вспомнился мне в прошлом году, когда Хитер Притчет и Расмус Тамсторф из WDAS связались со мной и спросили, интересно ли мне будет проверить возможное качество рендеринга сцены из «Моаны» в pbrt1. Естественно, я согласился. Я рад был помочь и мне интересно было, как всё получится.

Наивный оптимист внутри меня надеялся, что огромных сюрпризов не будет — в конце концов, первая версия pbrt была выпущена около 15 лет назад, и многие люди долгие годы использовали и изучали его код. Можно быть уверенным, что не будет никаких помех наподобие старой хэш-функции из RenderMan, правда?

Разумеется, ответ был отрицательным. (И именно поэтому я пишу этот и ещё несколько других постов.) Хотя я был немного разочарован, что pbrt не был идеальным «из коробки», но считаю, что опыт моей работы со сценой из «Моаны» был первым подтверждением ценности опубликования этой сцены; pbrt уже стал более качественной системой благодаря тому, что я разобрался с обработкой этой сцены.

Первые рендеры


Получив доступ к сцене, я сразу же её скачал (с моим домашним Интернет-подключением на это ушло несколько часов) и распаковал из tar, получив 29 ГБ файлов pbrt и 38 ГБ текстурных карт ptex2. Я беспечно попытался отрендерить сцену на моей домашней системе (с 16 ГБ ОЗУ и 4-ядерным ЦП). Вернувшись через какое-то время к компьютеру, я увидел, что он завис, вся ОЗУ заполнена, а pbrt всё ещё пытается завершить парсинг описания сцены. ОС стремилась справиться с задачей, используя виртуальную память, но это казалось безнадёжным. Прибив процесс, мне пришлось ждать ещё около минуты, прежде чем система начала реагировать на мои действия.

Следующей попыткой был инстанс Google Compute Engine, позволяющий использовать больше ОЗУ (120 ГБ) и больше ЦП (32 потоков на 16 ЦП). Хорошая новость заключалась в том, что pbrt смог успешно отрендерить сцену (благодаря труду Хитер и Расмуса по её переводу в формат pbrt). Было очень волнующе увидеть, что pbrt может генерировать относительно хорошие пиксели для качественного киноконтента, но скорость оказалась совсем не такой восхитительной: 34 мин 58 с только на парсинг описания сцены, причём во время рендеринга система тратила до 70 ГБ ОЗУ.

Да, на диске лежало 29 гигабайт файлов описаний сцен формата pbrt, которые нужно было спарсить, поэтому я не ждал, что первый этап займёт пару секунд. Но тратить полчаса ещё до того, как начнут трассироваться лучи? Это сильно усложняет саму работу со сценой.

С другой стороны, такая скорость говорила нам, что в коде, вероятно, происходит что-то очень дурно пахнущее; не просто «инверсию матрицы можно выполнить на 10% быстрее»; скорее, что-то уровня «ой, мы проходим по связанному списку из 100 тысяч элементов». Я был настроен оптимистично и надеялся, что разобравшись, смогу значительно ускорить процесс.

Статистика не помогает


Первым местом, в котором я начал искать подсказки, была статистика дампа pbrt после рендеринга. Основные этапы выполнения pbrt настроены так, что можно собирать приблизительные данные профилирования благодаря фиксации операций с периодичными прерываниями в процессе рендеринга. К сожалению, статистика нам мало чем помогла: по отчётам, из почти 35 минут до начала рендеринга 4 минуты 22 секунды было потрачено на построение BVH, но про остальное время не было указано никаких подробностей.

Построение BVH — это единственная значимая вычислительная задача, выполняемая во время парсинга сцены; всё остальное по сути является десериализацией описаний геометрии и материалов. Знание о том, сколько времени тратилось на создание BVH, дало понимание того, насколько (не)эффективной была система: оставшееся время, а именно около 30 минут, уходило на парсинг 29 ГБ данных, то есть скорость составляла 16,5 МБ/с. Хорошо оптимизированные парсеры JSON, по сути выполняющие такую же задачу, работают со скоростью 50-200 МБ/с. Ясно, что пространство для усовершенствования ещё есть.

Чтобы лучше понять, на что тратится время, я запустил pbrt с инструментом Linux perf, которым раньше никогда не пользовался. Но, похоже, он справился с задачей. Я проинструктировал его искать символы DWARF для получения названий функций (--call-graph dwarf), и чтобы не получить стогигабайтные файлы трассировки, вынужден был снизить частоту сэмплирования с 4000 до 100 сэмплов в секунду (-F 100). Но с этими параметрами всё прошло замечательно, и я был приятно удивлён, что инструмент perf report имеет интерфейс с nice curses.

Вот, что он смог мне сказать после запуска с pbrt:


Я не шутил, когда говорил об «интерфейсе с nice curses».

Мы видим, что больше половины времени тратится на механику парсинга: yyparse() — это парсер, сгенерированный bison, а yylex() — это лексический анализатор (лексер), сгенерированный flex. Больше половины времени в yylex() тратится на strtod(), преобразующую строки в значения double. Мы отложим атаку на yyparse() и yylex() до третьей статьи этой серии, но теперь уже можем понять, что хорошей идеей может быть снижение количества вбрасываемых в рендерер данных.

Из текста в PLY


Один из способов тратить меньше времени на парсинг данных в текстовом виде — преобразовать данные в формат, который парсится более эффективно. Довольно большая часть из 29 ГБ этих файлов описаний сцен являются мешами из треугольников, а в pbrt уже есть нативная поддержка формата PLY, который является эффективным двоичным представлением полигональных мешей. Также в pbrt есть флаг командной строки --toply, который парсит файл описания сцены pbrt, преобразует все найденные меши треугольников в файлы PLY и создаёт новый файл pbrt, который ссылается на эти файлы PLY.

Загвоздка заключается в том, что в сцене Disney активно используются ptex-текстуры, которые, в свою очередь, требуют, чтобы с каждым треугольником было связано значение faceIndex, определяющее, из какой грани исходного подразделённого меша он взят. Для переноса этих значений было достаточно просто добавить поддержку новых полей в файле PLY. При дальнейших исследованиях выяснилось, что в случае преобразования каждого меша — даже если в нём всего десяток треугольников — в файл PLY приводит к тому, что в папке создаются десятки тысяч мелких файлов PLY, и это создаёт свои проблемы с производительностью; от этой проблемы удалось избавиться, добавив в реализацию возможность оставлять мелкие меши неизменными.

Я написал небольшой сценарий командной строки для преобразования всех файлов *_geometry.pbrt в папке, чтобы использовать PLY для крупных мешей. Заметьте, что в нём есть жёстко заданные допущения о путях, которые необходимо изменить, чтобы скрипт работал в другом месте.

Первое повышение скорости


После преобразования всех больших мешей в PLY размер описания сцены на диске снизился с 29 до 22 ГБ: 16,9 ГБ файлов сцены pbrt и 5,1 ГБ двоичных файлов PLY. После преобразования общее время первого этапа системы снизилось до 27 минут 35 секунд, а экономия составила 7 минут 23 секунды, то есть мы ускорились в 1,3 раза3. Обработка файла PLY намного эффективнее, чем обработка текстового файла pbrt: всего 40 секунд времени запуска тратилось на парсинг файлов PLY, и мы видим, что файлы PLY обрабатывались со скоростью примерно 130 МБ/с, или примерно в 8 раз быстрее, чем текстовый формат pbrt.

Это была хорошая лёгкая победа, но нам ещё предстояло сделать многое.

В следующий раз мы разберёмся, где на самом деле используется вся память, исправим здесь несколько ошибок и добьёмся в процессе ещё большего повышения скорости.

Примечания


  1. Теперь вам должна быть более понятна мотивация добавления поддержки ptex с моей стороны и преобразования Disney BSDF в pbrt в прошлом году.
  2. Всё время здесь и в последующих постах указывается для WIP-версии (Work In Progress), с которой я работал до официального релиза. Похоже, что финальная версия немного больше. Мы будем придерживаться результатов, которые я записал при работе с первоначальной сценой, несмотря на то, что они не совсем соответствуют результатам финальной версии. Подозреваю, уроки из них можно извлечь одинаковые.
  3. Заметьте, что повышение скорости по сути соответствует тому, чего можно было ожидать при приблизительно 50-процентном снижении объёма парсящихся данных. Количество времени, которое мы тратим по показаниям профайлера, подтверждает нашу идею.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 31
    –7
    Геометрия и текстуры для одного кадра занимают на диске более 70 ГБ. Это потрясающий пример низкого профессионализма тех, кто создавал эту сцену.
    Вот эта картинка имеет 40 тыс. полигонов.
    image
    1996 г. Графическая станция Ingigo-2.
      +1
      Какая связь между весом одной СЦЕНЫ и скриншотом из старенькой архивизной проги с указанием кол-ва полигонов?
        –2
        Может я тупой и не понимаю глубины Вашей мысли.
        Но, я подумал, что связь самая прямая — в количестве полигонов на сцене.
        Кроме того, на диске тоже полигоны (часто) хранятся в виде мешей.
          0
          Больше всего места занимает не геометрия, а кэш симуляций (жидкости, снег, волосы, ткань, анимации и тд) и текстуры. А голая геометрия в 20 млн полигонов (взято с потолка, вполне нормальная цифра, я думаю, для целей визуализации) будет весить пару гигов и это вполне ок.
            0
            А голая геометрия в 20 млн полигонов — здесь я согласен, спасибо.

            Хотя в сцене очень много деревьев. И одно только дерево может весить 1 млн полигонов.
            У меня есть такие деревья.
              0
              У диснея, я думаю, они (деревья) еще и сделаны по иным технологиям.
                0
                Вот это меня и заинтересовало.
                Потому и комментарий дал.
              0
              Из Readme к сцене:
              The scene is made up of 20 elements containing meshes with more than 90 million unique
              quads and triangles along with 5 million curves. Many of the primitives are instanced many
              times giving rise to a total of more than 28 million instances of everything from leaves and
              bushes to debris and rocks. When everything is fully instantiated the scene contains more than
              15 billion primitives.


              90 миллионов квадов/треугольников. 20 действительно маловато для фильмовой сцены. Плюс все это хранится в .obj (геометрия всмысле), который текстовый, что тоже увеличивает вес по сравнению с бинарными форматами
          +2
          Ага, и деревья им надо было сделать билбордами.
            –4
            Не нужно только глумиться.
            Сцена моя 22-х летней давности.
          +2
          Очень крутая статья. Интересно насколько реалистичной будет полнометражная анимация через скажем лет 10-15, когда алгоритмы еще больше оптимизируют, а железо станет еще более производительным? Может даже для этого будут распределенные вычисления, я б поучаствовал своим железом в создании какого-нибудь мульта :)
            +3

            я один из создателей рендер фермы для вычисления мульта буба. На cpu считать глупо, очень глупо, т.к дорого. Мы начинали с cpu, потому что gpu не умел рендерить шерсть, но сейчас шерсть на gpu идет неплохо. Считаем мы на 12 видеокартах где-то, все полностью от отдельных картинок до финалки. На рабочих станциях по 1 cpu. разница между видеокартой и процессором в 10-100ни раз.

              0
              Что за рендер, который и на цпу и на гпу, и что бы до сотен раз быстрее на гпу?
              Как я понимаю, спрашивать как бы эта сцена диснеевская (или иная киношная) поместилась в память гпу не стоит?
                0
                Arnold, результат всегда будет разный, зависимый от сотен факторов, в нашем случае так. Нужно откопать таблицу с тестами, там смогу сказать точное кол-во, но как скоро смогу откопать, сказать не могу, полтора года прошло
                  0
                  А как вы получили гпу арнольд? я знаю что он сейчас в чем-то вроде закрытой beta, не могли бы вы у своих узнать сложно ли выбить доступ от компании? Буду благодарен
                    0
                    Напишите в саппорт, решилось как-то крайне элементарно, на сколько помню по первому же запросу стать тестерами, как текущие клиенты их компании
                      0
                      Arnold GPU чем то отличается от обычного в плане рабочего процесса?
                +2

                Gpu пока что только для несложных мультов и волосатых шариков в эвридейках. Сейчас ни один ГПУ рендер не в состоянии съесть даже десятой части геометрии текстур и сиквенций волюмов как в сабжевой сцене. А парситься это будет очень долго. В большом продакшене царствуют arnold, mantra, и немного vray, оптимизация и скорость работы которого меня иногда пугает) У мейджоров, разумеется, свои решения, но я сомневаюсь что они на gpu.

                  0
                  Тем не менее, многие двигаются к решениям на GPU (но это не видеокарты, это акселераторы massive GPU, типа Tesla). http://www.nvidia.com/object/wetadigital_avatar.html
                    0
                    при рендере, в нашем случае Arnold, нужна скорость и кол-во процессоров, tesla тут заоблочно по деньгам выходит. Хотели тестить v100, но не нашли у кого на тесты взять. Да и при нынешней цене 1080, нам проще раз в несколько лет выкидывать сломанное от 24*7. Потом явно наблюдали зависимость кол-ва и скорости процов на скорость отработки задачи.
                    0
                    А в чем заключается сложность мульта?
                    Если в кол-ве объектов детализации, то просто доставляются рендер ноды с 4GPU, а уж рендер сам по ним раскладывает, не хватает производительности-добавь нод. По крайне мере у нас такая картинка происходящего. Рендеры уже параллеляться и 3-4GPU в ПК без всяких SLI успешно работают. Вопрос только в цене и GPU выходит дешевле. У нас вот вышло, что докупить пару нод дешевле, чем отправлять финальный просчет на коммерчесскую ноду на GPU, сейчас не помню уже за сколько окупилась, но меньше чем за год наверняка.

                    PS я админ, для тестов и реального понимая внутренностей у нас спец человек.
                      +1
                      Честно говоря, для меня — большой вопрос ограниченная производительность gpu рендеров: первое что приходит в голову — ограниченное кол-во ram на борту. Но я, как практик, могу сказать что два самых популярных коммерческих gpu рендера (Octane, Redshift) при тестах в моей работе (пром. виз ролики и архитектурка) показали неспособность сколь-нибудь быстро съедать и переваривать сцены хотя бы с 4М поликов (Modo+Octane, C4D+Octane, C4D+Redshift), на парсинг уходит адово время. Делать превьюхи и настраивать лайтинг в этом случае крайне неудобно. В то же время можно загрузить какую-нибудь очень тяжелую сцену (>100м поликов) и увидеть превью в vray rt через 40 секунд.
                      image
                      image

                      При тестах Ocatane я брал 3 вида деревьев из этой сцены, а тут разной растительности около 40 единиц (~500-1200k poly каждое), инстансил их на плоскости ~ 200шт. и подумав минут 10 октан с грустью падал.
                      Помимо этого я только поверхностно представляю математику современных рендер движков, и кучу всего типа SSS сложно было переводить на GPU. В итоге всё равно будет какой-то симбиоз

                      PS я админ, для тестов и реального понимая внутренностей у нас спец человек


                      очень интересно было — бы послушать его мнение по поводу gpu рендеров и тяжёлых сцен
                    0
                    Спасибо, интересно. Можно еще полюбопытствовать?
                    Считаем мы на 12 видеокартах где-то
                    — какова конфигурация, по сколько видеокарт стоит в одной машине?
                    разница между видеокартой и процессором в 10-100ни раз.
                    — как происходит обсчет, каждая видеокарта считает отдельный кадр или как?
                    Какова скорость рендеринга, за сколько рендерится кадр?
                      0
                      Если детально описывать выбор железа и ПО, то потянет на отдельный обзор на этом ресурсе, я подумаю как лучше написать и расписать почему, что немаловажно, но боюсь за мои ошибки, а пишу я неграмотно — меня заминусят)
                        0
                        Мой вопрос был вопросом сугубо любопытствующего обывателя (в данной сфере). Поэтому зачем так усложнять, достаточно было двух-трех строчного ответа. Но если это вдруг внезапно секрет — я не обижусь. :)
                          0
                          А напишите статью. Если опасаетесь, что из-за грамматики оценят плохо — пришлите мне, я вычитаю.
                        0
                        На cpu считать глупо, очень глупо, т.к дорого.

                        Да именно по этому на CPU считают практически все рендеры (PrMan, Arnold, Moonlight и тд.), на видеокарте нормально не посчитаешь, в память тупо не влезут достаточно большие сцены (если делить то индирект будет выглядеть как говно), А поддержку GPU добавляют не для того чтобы считать финальный результат а для артистов чтобы шейдить в IPR режиме, ибо Gpu рендер не очень стабильный, арнольд юзает оптикс как бэк для гпу, а там косяков предостаточно. (Работаю в RnD отделе WizartAnimation)
                        0
                        Интересно насколько реалистичной будет полнометражная анимация через скажем лет 10-15, когда алгоритмы еще больше оптимизируют, а железо станет еще более производительным?

                        Производительность с алгоритмами на реалистичность никак не повлияют. Даже сейчас все это прекрасно параллелится и рендерится за практически незаметные на фоне остального бюджета деньги.
                          –1
                          Вы писатель-фантаст? Данные занимают всё отведенное для них место. Данные растут быстрее, чем процессор, GPU и оперативка. Гэп между оперативкой, GPU и процессором тоже растет. Данные передавать сложно. Вся проблема в передаче и подготовке данных. Закон мура уже не работает. Да, мы будем получать лучшую картинку. Но она не будет как Half-Life 1 и Half-Life 2. Она будет как BF1 и BF V. Тоесть получше, но не в разы. Пока не поменяют железо и представление данных там (воксели, полигоны, кеш процессора), то скачка не будет.

                          > Может даже для этого будут распределенные вычисления
                          Это есть уже лет 40. Называется рендер-ферма.
                            0
                            Я думал уже давно уходят от парадигмы 'тупо полигоны' к объектам и алгоритмам их генерации.

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

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

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

                        Самое читаемое