Советы по оптимизации VR-проектов

Разработка игр | |

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

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

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

В качестве введения

Хотя виртуальная реальность открывает перед разработчиками целый простор возможностей, графика во многих ВР-проектах выглядит так, будто ее делали несколько десятков лет назад. Поэтому я попробую рассказать о трудностях, возникающих при оптимизации ВР-проектов, о том, как с этими трудностями справиться, а также об оптимизационных технологиях, которые появятся в ближайшем будущем. Это будет глубоким погружением в тему производительности, с обилием технической зауми (в частности, будет рассказано о автоматизированных системах для оптимизации ассетов). Поэтому читатель должен хорошо разбираться в работе игровых движков и быть заинтересован в том, чтобы выжать из своих систем самый максимум. Это не стандартный набор приемов по игровой оптимизации – об этом было написано уже не раз. Вместо этого мы сфокусируемся на темах «Нам нужно оптимизировать эту тысячу ассетов, но людей категорически не хватает» или «Мы оптимизировали все, что могли, но фреймрейт по-прежнему пробивает дно».

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

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

Лаги и задержки

Каждая ВР-система (будь то Vive, Rift или PSVR) оснащена станциями, чьи вычислительные ресурсы используются, во-первых, для обработки данных о расположении/вращении игрока и игровых устройств в 3-мерном пространстве, и во-вторых, для расчета прогнозов (с частотой 90 раз в секунду или даже чаще) о том, где игрок или игровые девайсы будут находиться в будущем. К примеру, если человек идет вперед, эти станции будут считать, что человек будет по-прежнему идти вперед. На это тоже требуются вычислительные ресурсы, которые не бесконечны.

Другая проблема – в скорости отслеживания. Во-первых, она отличается у разных ВР-систем, а во-вторых, у одних ВР-систем она более масштабируема, чем у других. Данные, потерянные из-за несовпадения скорости отслеживания и фреймрейта, компенсируются за счет прогнозов. Базовые станции Vive обновляются каждые 4 мс, а на Rift обновление привязано к фреймрейту, но в любом случае при фреймрейте 90 к/сек объект, двигающийся быстрее заданной скорости, будет лагать, и этих лагов можно избежать, только если ВР-шлем высчитывает прогноз.

Есть и проблемы, характерные для конкретных ВР-систем. К примеру, у Vive возникают трудности из-за репроецирования и композитинга функций вроде «Chaperone», а на обработку данных, генерируемых OSVR, по сообщениям некоторых пользователей, уходят целые ядра.

Как быть разработчику?

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

Фреймрейт

Теперь давайте перейдем от «тяжелых ранений» (т.е. от задержкек и лагов) к «поверхностным ранам» (т.е. к проблемам с фреймрейтом). У большинства обычных ААА-игр фреймрейт составляет в пределах 30-60 к/сек – вроде бы немного, да? Всего-то на треть быстрее. Но на самом деле проблема очень серьезна. Конечно, у большинства ВР-систем случайный провал фреймрейта можно компенсировать за счет репроецирования, но при сильной нагрузке не помогает и оно. Поэтому важно, чтобы у вас всегда оставался запас вычислительных ресурсов – особенно при использовании Vive. Если ваш проект рассчитан на 90 к/сек и не имеет такого запаса, то на какой-нибудь навороченной сцене фреймрейт может запросто обвалиться до 45 к/сек. Во избежание этого свой проект нужно подогнать под 100-110 к/сек, чтобы у вас наверняка хватило производительности на того 45-голового босса, с которым игроку предстоит сражаться на протяжении 10 минут.

Есть еще один фактор, который очень часто остается незамеченным – ресурсы, которые расходуются на вычисления, выполняемые драйвером. Для разработчиков игра – это что-то вроде шампуня. Процессор отсылает данные видеопроцессору, а тот показывает их на экране. Намылить, смыть и повторить Х раз в секунду и… бум! Игра готова. Но эти маленькие надбавки наслаиваются друг на друга, что в конце концов влияет и на производительность. Картинка ниже демонстрирует ситуацию, при которой на драйвер уходят непомерные 35% всех вычислительных ресурсов, затрачиваемых на выполнение ВР-проекта.

Как быть разработчику?

Чем выше фреймрейт, тем эффективней должна быть оптимизация. К примеру, что касается игр, то для них запас производительности должен быть очень большим. На переднем краю борьбы с этой проблемой находятся, собственно, сами драйверы; в частности, интересные возможности есть у DX12, Mantle и Vulcan. Кроме того, в будущем этих возможностей, а также драйверов и чипсетов, наверняка станет еще больше. Более подробно о методах борьбы с этой проблемой можно почитать тут.

Многоэкранный рендер

Внутри каждого ВР-шлема находятся два экрана – по одному на каждый глаз. Изображения на них смещены относительно друг друга, чтобы создать у пользователя иллюзию глубины и убедить его, что он находится именно на горе Эверест, а не в своей комфортной московской квартире. Но при использовании двух экранов удваивается и количество объектов, которые нужно отрендерить на экранах. Добавьте сюда необходимость два раза отрендерить одну и ту же текстуру. Добавьте сюда эффекты вроде карт нормалей или спрайтовых частиц. Плюс фреймрейт в 90 к/сек. Плюс обработка данных о движении игрока и девайсов. Но самый смак – это разрешение экранов, составляющее 2160 х 1200 (по 1080 х 1200 на каждый глаз), что сопоставимо с разрешением среднестатистического монитора формата 1080p. Кроме того, свою долю привносит и сглаживание. Поскольку экраны ВР-шлема находятся прямо у ваших глаз, качество картинки должно быть гораздо лучше, чем в обычных играх.

Как быть разработчику?

Во-первых, разработчик может воспользоваться функцией, принцип работы которой очень прост: если игра лагает, она уменьшает размер экрана и снижает качество сглаживания. Во-вторых, можно обеспечить постоянную отправку команд на GPU, чтобы ему всегда было, чем заняться. В-третьих, можно воспользоваться так называемым «экземплярным стерео-рендерингом» (от англ. «instanced stereo rendering») – это технология, которая берет информацию обо всех объектах на сцене, подготавливает ее на CPU, но пока только для одного глаза, затем упаковывает эту информацию, выполняет смещение, чтобы подогнать картинку под второй глаз, и лишь после этого выполняет рендер. Более подробно об этой технологии можно почитать тут.

Кроме того, есть технология «ямковый рендеринг» (от англ. «foveated rendering»), которая увеличивает разрешение центральной части экрана, попутно снижая разрешение периферийных областей. Это позволяет существенно снизить количество данных, идущих на каждый экран. При использовании технологии «кругового разуплотнения» (от англ. «radial density masking») картинка в центре остается неизменной, а на периферии формируется «шахматное» изображение, в котором одна половина пикселей рендерится, а вторая – нет, после чего на основе этой «шахматной» картинки формируется еще одна, где оставшиеся пиксели несут в себе информацию об утерянных пикселях. Более подробно об этих технологиях можно узнать в этой презентации от Алекса Влачоса (Alex Vlachos).

Большинство этих техник реализовано, к примеру, в программе Lab Render for Unity от Valve.

Решения будущего

Более эффективные устройства и ПО

В последнее время разработчики пытаются улучшить производительность игровых движков при помощи многопоточности (т.е. одновременной обработки данных несколькими процессорами), но для многих платформ эта технология пока в новинку. Если производители PC-комплектующих уже провели серьезную работу над повышением производительности ВР-проектов, то у производителей ВР-шлемов все еще впереди. Сегодняшний ВР-шлем – это, как правило, довольно примитивное устройство, просто импортирующее картинки на экраны. Новые версии должны быть оснащены собственными вычислительными ресурсами для обработки данных о движении, стерео-рендеринга и репроецирования (на случай низкого фреймрейта).

Улучшенное охлаждение на мобильных устройствах

У современных мобильных устройств (включая ноутбуки и прочие компактные компьютеры) самым узким «бутылочным горлышком» является мощность CPU. По этому показателю они заметно проигрывают настольным компьютерам. Кроме того, если использовать смартфон на пределе мощности, он начинает испытывать тротлинг. Мобильный телефон создается с прицелом на максимизацию заряда батареи – чтобы пользователь мог и позвонить, и полазать в интернете, и немого поиграть. В итоге – ужасающее количество вершин и команд отрисовки.

Круговое разуплотнение (radial density masking)

Эта технология рендерит каждый второй пиксель, находящийся на периферии, а затем использует «уцелевшие» пиксели для восстановления неотрендеренных пикселей. Такой подход дает неплохой (но и не гигантский) прирост производительности – в некоторых случаях вплоть до 10%. Если использовать эту технологию вместе с ямковым рендерингом, можно добиться улучшения производительности, практически не жертвуя качеством картинки.

Ямковый рендеринг (foveated rendering)

Эта технология снижает разрешение периферийных участков изображения. Сейчас эту технологию правильней будет назвать фиксированным ямковым рендерингом: у центральной трети экрана разрешение увеличено, а у двух оставшихся – уменьшено. В ближайшем будущем должны появиться две новые версии этой технологии: акцентный ямковый рендеринг (от англ. «perceptually based foveated rendering») и следящий ямковый рендеринг (от англ. «gaze based foveated rendering»). В первом случае детализация изменяется в зависимости от важности объектов, находящихся в центре, а во втором – в зависимости от того, куда направлен взгляд пользователя (для этого используется слежение за движениями глаз). Эти технологии разбиваются на мириад других технологий вроде глубины резкости, зависящей от взгляда пользователя, или уровня детализации, зависящего от взгляда пользователя и т.д. Правда, при использовании следящего ямкового рендеринга разработчикам придется оснастить свои ВР-шлемы дополнительными вычислительными ресурсами, чтобы обработка данных о слежении за глазами не ложилась на плечи PC-комплектующих.

Но наличие всех этих технологий не отменяет творческого подхода к работе с аппаратными, программными и гейм-дизайнерскими ограничениями. Если использовать эти технологии по отдельности, они могут создать загруженность в других участках системы. К примеру, экземплярный стерео-рендеринг может увеличить нагрузку на GPU на 257%. Другие технологии могут переместить проблемы с производительностью в места, которые уже испытывают сильную нагрузку. К примеру, оптимизировать GPU при сильной загруженности CPU – это не только потеря времени, но и медвежья услуга всему проекту.

Как оптимизировать свой проект?

В данный момент возможности разработчиков ограничены, но большинство проблем ВР-проектов – такие же, как и в обычных играх. Следовательно, инструментарий для борьбы с этими проблемами будет примерно тем же. Разработчики мобильных игр могут задействовать уже известные им методы вроде атласинга (от англ. «atlasing»; это технология, при которой текстуры/материалы комбинируются в одном месте, которое и называется «атласом»; близкий термин – «текстурный атлас»), использования одного большого шейдера или запекания большинства (а то и всех) текстур. Если вы разрабатываете мобильную игру или используете для ВР свой ноутбук, то на производительность вашего проекта будет очень сильно влиять тротлинг. Современные GPU всегда испытывают максимальную загруженность – из-за особенностей своего устройства. Но если задействовать более мощный CPU (вроде 7-го поколения Intel Core i7 или лучше), это позволит разгрузить GPU и тем самым увеличить производительность. Это проблема, с которой разработчики сражаются годами: один GPU, один экран, отложенный рендеринг, полноэкранные эффекты. Но в ВР не один, а целых два экрана, и в итоге GPU загружен почти постоянно, поэтому умение оптимизировать GPU становится все важнее. В частности, Oculus не рекомендует использовать полноэкранные эффекты – как раз из-за последствий для производительности.

Анализ проекта

Чтобы выяснить, насколько сильно ваш проект нагружает CPU и GPU, можно воспользоваться инструментом Intel Graphics Performance Analyzers. Он удобен тем, что собирает все данные о производительности в одном месте. Скачать его можно отсюда, а обучающие материалы можно найти тут и тут.

Количество полигонов

Для обычных игр это практически не проблема, т.к. современные компьютеры могут с легкостью выдать несколько миллионов треугольников и даже не запыхаться. Однако для ВР-проекта проблемой будут даже 3 миллиона полигонов. Полигонную оптимизацию можно использовать и на «сырой» версии ассета, и на LOD-версиях.

Как определить

Как правило, у ВР-проекта нет ограничения на количество полигонов, но этот показатель тесно связан со скоростью заполнения (от англ. “fillrate”), освещением в реальном времени и сложностью шейдера. Проблемы со скоростью заполнения могут возникнуть из-за чрезмерного «загораживания» (от англ. «overdraw»; это ситуация при которой одни объекты загорожены другими, поэтому загороженные объекты рендерятся впустую), и снижение количества полигонов придется здесь весьма кстати. Если в вашем проекте используется много освещения в реальном времени, каждая из этих «ламп» дополнительно рендерит полигоны для упреждающего рендеринга (от англ. «forward rendering»; это рекомендуемая технология для ВР). Еще одна гремучая смесь – это сложный шейдер плюс большое количество полигонов, поэтому я рекомендую использовать для ВР мобильные шейдеры. Но чтобы картинка засияла, к делу нужно подойти творчески. Если просто снизить количество полигонов, «шейдерная» выгода будет совсем небольшой.

Что делать

Если у вас достаточно рабочих рук, то ни секунды не сомневайтесь и делайте эту работу руками. Если у вас достаточно средств, воспользуйтесь программами вроде Simplygon и Decimator от Maximo. Но есть и альтернативная техника, которая может разом обработать целую кучу ассетов. Используя ее, не забывайте про управление версиями, потому что такая «ковровая» оптимизация вряд ли сработает для всех моделей. К примеру, она плохо подходит для персонажных ассетов, и в этом случае вы наверняка захотите откатить оптимизацию назад.

import pymel.core as pm
# выделяем все объекты по типу геометрии:
pm.select(pm.listRelatives(pm.ls(geometry=True), p=True, path=True),r=True)
objectsToReduce = pm.ls(sl=True)
for objectToReduce in objectsToReduce:
pm.select(objectToReduce)
pm.polyReduce(percentage=35, version=1)

Уровни детализации (LOD)

Здесь проблемы возникают из-за двух вещей: ограниченного количества вершин и ограниченного количества команд отрисовки. Работа над уровнями детализации позволяет оптимизировать сцены за счет снижения детализации у объектов, находящихся на заденем плане. К примеру, перед носом у вас щелкает своими многочисленными челюстями 45-головый монстр, а на заднем плане ютится маленький чайник, на который приходится 2 тысячи полигонов и 6 команд отрисовки: цвет, карта нормалей, карта высот, карта отражений и запеченный объемный свет. 45-головый монстр, напомню, дышит вам прямо в лицо, а чайник тихонько булькает в дальнем углу. Какому объекту снизить детализацию? Монстру, верно? LOD в помощь!

Как определить

Конечно, LOD’ы не помогут вам в каждой сцене и с каждым объектом. Порой от использования LOD’ов становится даже хуже. LOD’ы подойдут для объектов с большим количеством полигонов и команд отрисовки – вроде большой или открытой комнаты, заполненной различными точками интереса. Но разумней всего использовать LOD’ы в сцене с небольшим количеством объектов, потому что люди склонны фокусироваться на отдельных предметах. Во всех этих случаях не следует сначала применять команды отрисовки, а потом делать LOD’ы, как и не стоит использовать LOD’ы в маленьких и тесных сценах. Дело в том, что переход от одного уровня детализации на другой происходит даже из-за малейших движений игрока, а эти переходы тоже стоят вычислительных «денег». Наслаиваясь друг на друга, они могут вызвать проблемы с производительностью, так что пользоваться LOD’ами нужно мудро.

Что делать

При работе с LOD’ами разработчик занимается тремя вещами: оптимизацией моделей, оптимизацией материалов и оптимизацией шейдеров. Для примера давайте возьмем все тот же чайник. Хотите реализма? Добавьте чайнику карту деталей. Хотите показать, что бабушка главного героя лелеяла этот чайник до своего самого последнего дня? Добавьте ему какой-нибудь приятный шейдер вроде PBR. Как только главный герой отдалится от чайника, уберите карты высот и нормалей, а затем вполовину уменьшите количество вершин, а когда удалится еще дальше – еще раз урежьте вершины и поменяйте шейдеры на мобильные.

import pymel.core as pm
# не забудьте выбрать объект:
reductionPrecentages = [0,10,20]
nameOfLODS = []
selectedObject = pm.ls(sl)
for x in range(0,ReductionPrecentages.length):
newName = (selectedObject + "_LOD[%s]")%(x)
pm.duplicate(newName)
pm.parent(selectedObject)
pm.polyReduce(percentage=reductionPrecentages[x], version=1)

Это обновленный скрипт хорошо работает для Unity и даже выполняет за вас группировку LOD’ов. Мы модифицируем цикл for(), создаем переменную с процентом оптимизации и добавляем к этой переменной суффикс _LOD[переменная]. Затем пробегаем по списку необходимых размеров и экспортируем через FBX. Далее импортируем в Unity и – вуаля! – у нас есть группа LOD’ов.

Количество объектов

Все просто – чем больше объектов, тем больше ресурсов система тратит на их обработку. То же касается и текстур, наложенных на эти объекты.

Как определить

Давайте снова вспомним про чайник и добавим к нему целый чайный набор – с блюдцами, ложками, чашками и т.д. В результате у нас получится много разных объектов, для каждого из которых нужны специальные материалы и текстуры. Следовательно, для оптимизации этих объектов потребуются разные методы: просто комбинирование, комбинирование + атласинг и комбинирование + атласинг + LOD (для «дальних» уровней с низкой детализацией).

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

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

Комбинирование + атласинг + LOD – это техника при которой объекты объединяются в одну сетку и оснащаются одной текстурой, но только на самых дальних уровнях детализации. С одной стороны, это позволяет сохранить детализацию, если игрок смотрит на объект вблизи, а с другой, позволяет быстро переключиться на оптимизированную версию, когда игрок отдаляется от объекта. В частности, эта техника полезна, если игрок может взять объект в руки.

Что делать

Если вы решили воспользоваться простым комбинированием, просто создайте скрипт с этим кодом (это из документации к Unity о функции Mesh.CombineMeshes):

using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class ExampleClass : MonoBehaviour {
void Start() {
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length) {
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
meshFilters[i].gameObject.active = false;
i++;
}
transform.GetComponent<MeshFilter>().mesh = new Mesh();
transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine);
transform.gameObject.active = true;
}
}

Если вы решили воспользоваться комбинированием с атласингом, советую программу Mesh Baker – она сэкономит вам множество рабочих часов. Для связки «комбинирование + атласинг + LOD» советую воспользоваться Mesh Baker LOD. Более подробно можно почитать тут и тут.

Есть и более продвинутая техника, которая, впрочем, потребует творческого подхода. Ее суть в том, чтобы экспортировать каждый LOD для каждого фрагмента каждого объекта. Сначала при помощи Mesh Baker скомбинируйте все сетки с высокой детализацией, но без объединения текстур. Затем дайте Mesh Baker оптимизировать среднюю детализацию и удалите некоторые текстуры. Наконец, для сеток с низкой детализацией замените все шейдеры максимально «легкими», но приемлемыми, а затем скомбинируйте. Эта техника потребует много времени, но зато сэкономит вам кучу вычислительных ресурсов.

Команды отрисовки (draw calls)

Команда отрисовки – это то, что через драйвер передается от CPU к GPU. Чем меньше команд прорисовки и чем они проще – тем лучше. Самый простой способ оптимизации команд отрисовки – это оптимизация количества разных материалов и текстурных атласов, а также сокращение количества второстепенных текстур вроде карт высот, карт нормалей и карт отражения. Выше я уже упоминал об оптимизации команд отрисовки, но в этом разделе я расскажу об этом поподробней.

Как определить

Во-первых, ищите объекты, которые могут использовать один и тот же шейдер. К примеру, в нашем случае одинаковый шейдер можно применить на все предметы чайного набора. Во-вторых, ищите второстепенные карты с большим количеством лишних деталей – вроде тех, которыми украшен чайник. Это важный объект, который наверняка привлечет внимание игрока, поэтому оптимизировать его мы не будем (просто возьмем его на заметку на всякий случай). Наконец, если объект использует маленькие текстуры, объедините их в одну большую текстуру. К примеру, если каждое блюдце нашего чайного имеет собственную текстуру, объединение этих текстур в атлас снизит количество команд отрисовки.

Что делать

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

Также полезно изучить игру самостоятельно, с надетым ВР-шлемом. Объекты, которые первыми бросаются в глаза, в будущем могут стать серьезной нагрузкой на вычислительные ресурсы. Если ассет не видно, просто удалите его, а если видно – попробуйте объединить его текстуры. Хотя это и требует дополнительной RAM-памяти и места на диске, зачастую комбинирование текстур стоит того, даже если вы заполняете атлас не полностью. Объединение текстур в один материал без объединения сеток дает гибкость в детализации.

Еще одно решение – самостоятельно разработать гибридную систему LOD’ов для объектов, двигающихся независимо друг от друга. Она начинает обрабатывать объекты, если они оказываются на такой дистанции, когда их можно будет объединить. Учтите, однако, что время пребывания объектов на экране может варьироваться, поэтому использование этой техники может быть хуже для производительности, чем ситуация, когда объекты обрабатываются раздельно. С другой стороны, этот прием может помочь, если GPU сильно загружен, а у CPU, наоборот, еще остались ресурсы для работы над другими задачами.

Еще одна техника, о которой разработчики почему-то часто забывают – это комбинирование текстур. Это НЕ атласинг, о котором я рассказывал выше; эту технику еще называют «укладкой в каналы» (от англ. «channel packing»). Ее суть в том, чтобы поместить разные карты в разные каналы одного RGB-файла. К примеру, у нашего чайника есть карта объемного света, карта отражений и карта высот. Если поместить объемный свет в красный канал, отражения – в зеленый канал, а высоты – в синий канал, в итоге у нас получается всего одна команда отрисовки. Останется лишь повозиться с тем, чтобы шейдеры знали, где искать нужную карту, но в этом, в общем-то, вся суть оптимизации слабодетализированных объектов.

Итак, процедура такова:

  • Выберите слой
  • Перейдите в каналы
  • Уделите зеленый и синий каналы, чтобы у вас остался один лишь красный канал
  • Перейдите к слоям и повторите то же самое для зеленого канала
  • Сделайте то же самое для синего канала
  • PROFIT!

Запечение обычного света и объемного света (baking)

Как вы уже, наверно, поняли, в ВР любые данные лучше обрабатывать заранее. CPU почти все время занят, пока GPU пыжится обработать спид-дейтинг сразу двух игр. Это значит, что чем больше вещей запечено – тем лучше. Используете везде освещение в реальном времени? Готовьтесь к куче проблем.

Как определить

Почти всему нужны карты теней, т.е. всем объектам, которые не взаимодействуют с освещением, обрабатываемом в реальном времени. Более подробно смотрите ниже. О том, как работать с картами теней, смотрите в Lab Render от Valve.

Что делать

Во-первых, ищите все, что можно запечь. К сожалению, в данный момент карты теней в Unity устроены так, что запечение света в больших сценах – это очень замороченный (а временами и вовсе безрезультативный) процесс. Поэтому рекомендую сначала экспортировать все ассеты в 3D-редактор вроде Maya, а затем спокойно запечь и обычный, и объемный свет. Создание хороших карт теней – это тема для отдельной статьи. По этой ссылке – документация по плагину Turtle для Maya, а по этой – документация по запечению объемного света при помощи Substance.

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

Тени от освещения в реальном времени не обязаны быть, собственно, тенями. Просто определите направление света, чтобы создать имитацию тени на земле – например, с помощью анимированного текстурного атласа, сопоставимого с персонажной анимацией или даже с древней методой, известной как «пузырь» (от англ. «blob shadow»; это обычная круглая тень), к которому прикручена возможность угловой изменчивости. Удивительно, но такую подделку замечают очень немногие.

Шейдеры

Шейдеры – корень зла (если говорить исключительно об оптимизации ВР-проектов). Лишь немногие разработчики умеют писать шейдеры, и многие из этих шейдеров плохо оптимизированы под ВР. Поэтому самый лучший подход – это либо воспользоваться мобильными шейдерами, либо творчески подойти к созданию собственного шейдера.

Как определить

Если у шейдера больше 4 слотов, и объект, для которого он используется, вряд ли привлечет пристальное внимание пользователя, то этот шейдер, по всей видимости, плохо оптимизирован для вашего ВР-проекта. Единственное, что нужно объектам «массовки» в ВР – это цвет, карта нормалей, карта обычного света и карта объемного света.

Что делать

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

Технология Occlusion Culling

Вы, вероятно, знаете, что в Unity по умолчанию включена технология Frustum Culling, которая отсекает все объекты, которые не находятся в поле зрения игрока. Но вдобавок к этому в Unity есть и технология Occlusion Culling (можно перевести как «отсечение загороженных объектов»). Представьте, к примеру, что позади нашего чайника лежат столовые ложки. При включенном Occlusion Culling эти ложки рендериться не будут, что благоприятно повлияет на производительность.

Как определить

Эта техника отлично подходит для закрытых пространств (к примеру, для домов и зданий). Но если вы работаете над открытой локацией, где объекты накладываются друг на друга, это может плохо сказаться на производительности.

Что делать

Occlusion Culling может быть и благом, и проклятьем. Лично моя точка зрения такова, что об Occlusion Culling не нужно беспокоиться вовсе. К примеру, представьте, что на столе стоит чайный набор, а вы стоите в соседней комнате, поэтому отгорожены от этого набора стеной. Как правило, в играх этот набор рендерится, а затем выбрасывается – как раз из-за стены. Но технология Occlusion Culling видит всю сцену заранее просчитанной решеткой, которая состоит из ячеек и знает, где какие объекты находятся. Камера отсылает лучи, чтобы проверить, какие ячейки попадают в поле ее видимости, а какие – нет. Ячейки, которые в поле видимости не попадают, не рендерятся. Советую для начала попробовать стандартные настройки – чтобы посмотреть, дает ли это хоть какой-то результат. Если результата нет, можно немного повозиться с настройками, благо много времени это не отнимет. А если отнимет – бросайте настройку и смело ставьте сцену на рендер. Если заметили улучшение, измерьте точный «вес» всех объектов на сцене.

И еще одна техника напоследок

Она совсем новая и называется «гибридный моно-рендеринг» (от англ. «hybrid mono rendering»). Если вкратце, ее суть в том, что объекты, находящиеся вблизи, рендерятся в стерео, а удаленные объекты – в моно. Подробнее о ней можно прочесть тут.

Дополнительные материалы

Надеюсь, эта статья поведала вам немало новых и интересных идей об оптимизации в сфере ВР. Но это – лишь капля в море, потому что…

  • здесь можно почитать об оптимизации ВР в Unity
  • …а здесь – об оптимизации ВР в Unreal

Также выражаю большую благодарность программе Intel Innovator Program за поддержку в написании этой статьи.

Об авторе

Тим Портер (Tim Porter) трудится разработчиком графики для PC и ВР в Underminer Studios. Он имеет богатый опыт в работе с передовыми графическими технологиями, благодаря чему научился множеству полезных приемов и трюков. Кроме того, ранее он работал техническим художником, что позволяет ему видеть проблемы с оптимизацией максимально широко. Связаться с ним можно по почте.

Оригинал можно почитать тут.

Владимир FrostBite Хохлов frostbite@progamer.ru

Поделиться

Обсудить