Часть 1. Введение
Привет меня зовут baldurk. Я уже несколько лет работаю программистом графики, поэтому хоть я и не в коем случае не являюсь экспертом, кажется, я уже многое понимаю во всём том, что касается работы с графикой.
Идея этой серии постов уже давно витала где-то на периферии моего сознания, и снова всплыла после прочтения интересной статьи с разбором последней Deus Ex.
Мне кажется, что графика, и в особенности та сложность, какой она достигает в современных играх — это интересная тема. Очень немногим людям любопытно глубоко погружатьcя во все её подробности, но я считаю, что есть темы, которые интересны каждому. Я думаю, что большинству людей, игравших в игры, было любопытно, как получаются те или иные эффекты, или с помощью какой технологии удалось создать такую потрясающую графику в какой-нибудь новой игре.
Есть много составляющих, необходимых для создания даже простой 3D-игры, не говоря уж о таком проекте, как Watch Dogs.
У меня есть только общее представление о том, что нужно рассмотреть в этой статье, но это будет зависеть от того, какие темы вызовут интерес. Тем не менее, основная идея заключается в том, чтобы создать общее описание того, что происходит внутри современной игры, не отпугнув при этом никого из читателей — я буду предполагать, что у вас нет знаний математики и программирования. Если вы знаете разницу между ЦП и графической картой, и отличаете оперативную память от жёсткого диска, то этого будет вполне достаточно, а остальное я объясню.
Эта статья будет развиваться в соответствии с её видеопрохождением Chip & Ironicus's Let's Play of Watch Dogs, чтобы немного структурировать изложение. Игра хорошо известна своей графикой (причём мнения о ней могут быть полностью противоположными), и в ней есть множество аспектов, которые можно рассмотреть на отдельных примерах. Возможно, я расскажу и о других играх.
Начну я с объяснения основ, которые почти одинаковы для каждой игры, но также я рассмотрю и некоторые техники и визуальные эффекты Watch Dogs.
Я буду использовать инструмент под названием RenderDoc, который я написал в свободное время. Он применяется для отладки проблем с графикой — инструмент позволяет разложить на часть кадр графики, и благодаря этому мы увидим, как он собирается вместе.
В этой анимации показана часть постепенно создаваемого кадра в процессе его отрисовки графической картой.
Большинство людей знает, что компьютерная графика (да и графика любого другого видео) состоит из серии неподвижных кадров, каждый из которых отображается определённую долю секунды. В кино традиционно использовали 24 кадра в секунду (frames per second, FPS), в телевидении частота примерно такая же, около 24-30 кадров. В играх FPS может быть переменчивым, потому что в каждом кадре выполняется множество работы. Падение частоты ниже 30 нежелательно, хотя и случается довольно часто. Обычно верхним пределом для консольных игр является 60 FPS. Разработчики стремятся реализовать частоту 30 или 60, что зависит от целей игры. На PC при наличии дисплея с высокой частотой кадров можно добиться 90, 120 или даже выше. Причина этих конкретных чисел заключается в вертикальной синхронизации (vsync), о которой мы расскажем ниже.
Мысленно мы можем взглянуть на эту задачу с противоположной стороны — вместо того, чтобы смотреть, насколько высока частота FPS, мы смотрим на то, насколько мало время, выделенное на каждый кадр. Если мы хотим, чтобы игра работала с частотой 30 FPS, то на выполнение всей необходимой для кадра работы у нас есть только 33 миллисекунд. При 60 FPS время в два раза меньше — около 17 миллисекунд. Даже для компьютера такой промежуток времени не очень велик, учитывая тот объём работы, который нужно выполнить. Чтобы дать вам представление о величинах, то по приблизительным подсчётам пуля перемещается примерно на 1 метр в миллисекунду.
В основном мы будем говорить о PC, потому что эта платформа открыта, и я не могу рассказывать о консолях без опаски нарушить соглашения о неразглашении (NDA). Всё равно в основном я буду рассказывать о том, что не сильно отличается на консолях, а если что-то всё-таки будет отличаться, то я подчеркну это. Что касается мобильных платформ, то большинство различий между оборудованием PC/консолей и мобильного оборудования не относится к теме моей статьи.
Честно говоря, я поставил сюда эту картинку, чтобы вы понимали, что в статье будет не только текст.
Именно эта задача нас и интересует — мы не будем беспокоиться о том, как выполняет все свои вычисления ИИ, или как производится физическая симуляция для перемещения объектов. Границы дисциплины под названием «программирование графики» довольно размыты, но я скажу, что программирование графики начается тогда, когда у нас есть вся информация, необходимая для построения кадра: мы знаем, что происходит, все текстуры и модели находятся в памяти (не на диске), анимации уже анимированы, физика посчитана, и нам осталось отрисовать готовый кадр для отображения на экране.
Добавлю, что я буду рассматривать 3D-игру с достаточно традиционным рендерингом, наподобие Watch Dogs — многие из базовых принципов применимы и к 2D-играм, но на них демонстрировать концепции немного сложнее. Также поясню (особенно программистам графики), что в первую очередь стремлюсь к пониманию, поэтому, возможно, буду использовать достаточно сомнительные объяснения, если они позволят мне достичь своей цели.
Часть 2. Из чего состоит кадр
Большую часть времени мы будем рассматривать всего один кадр и рассказывать о строительных блоках, которые использует игра для создания готового кадра. Также в этой части появится несколько новых красивых картинок.
Существует несколько способов, которыми кадр собирается из строительных блоков. Готовое изображение, которое видит игрок, отрисовывается не мгновенно. Сразу же он отрисовывался много лет назад, но в современных графических движках почти всегда используется какая-то предварительная обработка. Прежде чем отобразить на экране готовый кадр, графический движок отрисовывает множество промежуточных изображений различных типов, помогающих в вычислении финального изображения.
Эти изображения сильно зависят от вида движка и от техник, которые требуется применить программисту графики. Например, если он хочет, чтобы солнечный свет создавал правильные тени, то для теней потребуется один тип изображения. Также ему могут понадобиться правильные отражения на управляемом игроком автомобиле, и для этого тоже нужно ещё одно изображение с отражениями.
Разные примеры промежуточных изображений, используемых при построении кадра Watch Dogs.
В этой статье я не буду рассматривать каждое из изображений, использованных в кадре Watch Dogs, а расскажу только об основных, чтобы вы могли чему-то научиться. Это область, в которой постоянно происходят исследования графики и возникают новые техники. Инновации возникают и на более мелких уровнях, но когда маркетинг говорит вам о какой-то новой графической функции, то обычно имеется в виду именно подобные усовершенствования.
Каждое из этих промежуточных изображений тоже строится из ещё меньших фрагментов. Каждый объект в сцене или группа связанных объектов создаётся отдельно как текстурированная модель. При разработке игры художники строят эти модели в 3D-редакторе и создают для них все необходимые ресурсы. Затем эти модели размещаются в мире с помощью редактора уровней и из них постепенно строится виртуальный город.
Наверно, это известно почти всем, и если последние 20 лет вы наблюдали за развитием 3D-графики реального времени, то знаете, насколько более сложными стали модели сегодня. На самых первых этапах становления графики наложение текстур было затратным процессом, и по возможности от него избавлялись, закрашивая объекты одинаковым цветом. Текстуры оставляли только для таких элементов, как глаза или лица, которым действительно требовалась детализация.
3D-модель целиком состоит из взаимосвязанных треугольников, образующих форму объекта. Каждый треугольник имеет три точки, называемые вершинами, и поскольку треугольники связаны друг с другом, вершины могут быть общими для нескольких треугольников. Позже мы вернёмся к этому, потому что вершины и треугольники достаточно важны. Также стоит помнить, что некоторые объекты, например, персонажей или деревья перед отрисовкой необходимо анимировать. Модель создаётся в стандартной статической форме, а анимации применяются в каждом кадре. К этому мы тоже вернёмся.
Это 3D-модель головы Эйдена Пирса после анимирования. Треугольники видны, потому что они отрисованы плоскими, а не сглажены, как это бывает обычно.
Чтобы добавить на 3D-модель больше деталей, накладываются текстуры. Текстуры — это обычные плоские файлы изображений, обычно квадратные или с простыми размерами, например, прямоугольники с соотношением 2:1. Текстуры накладываются на 3D-модель с помощью более сложного процесса, который я подробнее рассмотрю ниже, но концептуально он похож на процесс заворачивания подарка. Вместо простого повторяющегося узора бумажной обёртки, изображение точно соответствует размеру обёртки. Если вы видели бумажные модели для сборки при помощи клея, то принцип здесь такой же.
Эта аналогия более уместна, чем можно подумать, потому что эти текстуры обычно создаются «разворачиванием» 3D-модели в плоскую заготовку, как это делается с бумажной моделью, после чего поверх неё рисуется текстура. Такое разворачивание часто выполняется автоматически, но в случае особо сложных объектов может производиться вручную.
Это текстура, соответствующая показанной выше модели головы Эйдена Пирса. Тут есть части для зубов и языка. Заметьте, что область над его лбом не текстурирована, потому что постоянно закрыта Легендарной Бейсболкой Эйдена Пирса™.
Примечание
При разворачивании некоторые части объекта, требующие большей детализации, увеличиваются, а другие — уменьшаются.
Очень часто говорят о различных «скинах» моделей, особенно в случае настраиваемых персонажей. Сегодня то, что называют «скином», обычно относится к небольшим изменениям в модели — новый ремень или другая шляпа — но изначально этот термин возник, потому что использовалась одна и та же модель, но менялась текстура (или «скин» — буквально переводится как «кожа») для создания персонажа, выглядящего иначе. Даже сегодня с помощью таких текстур можно создать большую вариативность NPC или объектов, что экономит время и деньги — не нужно создавать множество уникальных 3D-моделей. Разная одежда, которую может надевать Эйден — это чаще всего просто различные текстуры одной и той же модели.
Вот краткий фрагмент поворота головы 3D-модели. Наложена только текстура, и ничего более.
В рассматриваемом нами кадре есть примерно 1700 объектов, отрисовываемых в части основного рендеринга. Некоторые из них будут одинаковыми моделями — такие объекты, как цветы в горшках и мусорные баки на самом деле никогда не создаются по отдельности, это одна модель или несколько моделей, размещаемых в разных местах. Однако примерное количество отрисовываемых для завершения кадра объектов близко к 4700 — это даёт нам представление о том, сколько дополнительной работы необходимо выполнить кроме отрисовки всех этих моделей.
Давайте взглянем ещё на один пример объекта — бейсболку, которую носит Эйден.
Это текстура и затекстурированная модель бейсболки. Видно, что текстура построена из отдельных частей, которые соединяются на модели.
Примечание
Козырёк и основная часть бейсболки на текстуре никак не касаются, потому что развёртка может быть довольно сложной и при необходимости может выполняться в нескольких разных частях. Иногда для текстурирования модели сложной формы без видимых проблем и швов требуется изрядное мастерство.
Те же самые принципы, которые мы видели в случае головы Эйдена, относятся и к кепке. На самом деле, если абстрагироваться от конкретной текстуры и чисел, необходимых для выполнения разворачивания, то принцип всегда одинаков.
Чтобы показать, что может пойти не так при разработке, и продемонстрировать, как можно повеселиться при программировании графики, мы можем провести небольшой эксперимент. Так как большинство текстур имеет стандартный квадратный размер, а способ заворачивания и разворачивания текстур на модели тоже одинаков, то почему бы немного с ними не поиграть? Что случится, если мы применим текстуру головы Эйдена к модели бейсболки?
Уже не очень похоже на Легендарную Бейсболку Эйдена Пирса.
Там, где на текстуре бейсболки был логотип, на текстуре головы расположены ухо и зубы. Там, где был козырёк, на текстуре головы только волосы. Наложение выполнено абсолютно так же, но использована другая текстура. Разумеется, этот пример будет в игре ошибкой, но подумайте о том, что можно сделать, если анимировать текстуру или заставить её мерцать — в играх подобные вещи используются для различных эффектов, которые теперь вы сможете замечать. В частности, игра Saint's Row 4 использует подобное для спецэффектов «симуляции».
Также полезно будет поразмыслить о последствиях этого — игры очень тщательно сочетают пары моделей и текстур, то есть наиболее уникальным моделям должны соответствовать их собственные уникальные текстуры.
Разумеется, это правило не абсолютно — в некоторых случаях для экономии места текстура является стандартным повторяющимся паттерном, который можно использовать для множества объектов. Связанные наборы объектов — например, газетные киоски — могут использовать одну текстуру для разных газет, причём каждая газета будет занимать на текстуре небольшую часть.
Тем не менее, это означает, что для построения конечного изображения необходимо наличие всех строительных блоков, то есть в результате может получиться очень много обязательных моделей и текстур. В следующей части мы поговорим о том, почему некоторые аспекты, например отражения, очень сложно реализовать правильно. Также я расскажу о том, как игры используют небольшие хитрости для экономии времени и ресурсов.
Часть 3. Что рисовать не нужно
Часто программирование графики — это задача уравновешивания десятка разных ограничений для получения идеального компромисса. В прошлой части мы увидели, что каждый раз при отрисовке сцены она собирается из множества мелких строительных блоков — людей, автомобилей, дорожных знаков, зданий. Всё, что есть на экране, составляется из отдельных компонентов, которые необходимо отрисовать. Существует несколько тонких операций балансировки, которые мы здесь рассмотрим.
Над отрисовкой кадра совместно трудятся центральный процессор и графическая карта. В ЦП выполняется вся остальная часть игры, поэтому он принимает решение о том, какие объекты должны отрисовываться в текущем кадре, куда смотрит камера, какие анимации воспроизводятся. Графическая карта — это «рабочая лошадка», которая выполняет всю сложнейшую работу, связанную с отрисовкой пикселей, именно поэтому она является отдельным специализированным устройством.
Оказывается, и у ЦП, и у графической карты есть ограничения на скорость или объём вычислений, но это разные типы ограничений.
В целом ЦП больше интересует собственная часть работы: сколько объектов нам нужно отрисовать всего? Насколько отличаются эти объекты — это 100 одинаковых фонарей, или 100 кустов/растений/деревьев? Анимированы ли эти объекты, движутся ли динамически, и какие из них статичны или неподвижны?
Первое, что мы делаем, чтобы как можно больше снизить нагрузку — отрисовываем только то, что видно на экране. Это кажется очевидным, но для реализации требуется тщательная работа. Не забывайте, что мы строим каждый кадр с нуля, поэтому в каждом кадре нам нужно смотреть на каждый объект и определять, видим он или нет. Это значит, что в каждой игре есть чёрная пустота, повсюду следующая за игроком, и когда он не смотрит на объекты и людей, те перестают существовать.
В этой анимации мы поворачиваем камеру, показывая пустоту за игроком. Внимательные зрители заметят, что она не совсем пуста...
Сложно выработать в этом случае какие-то практические правила, но в целом можно отрисовать в сцене около 1000 объектов, не заботясь о нехватке места. Однако если нужно отрендерить 5000 объектов, то стоит задуматься о применении трюков. Не забывайте, что в большинстве игр, где игрок может управлять камерой, мы не можем знать, под каким углом он взглянет, поэтому нужно сохранять пространство для манёвра.
Оказывается, существует достаточно много хитростей, позволяющих почти полностью использовать допустимый ресурс, и особенно важны они в играх, подобных Watch Dogs. Чем ближе ты будешь к границам и чем большее визуальное качество можно получить от одинакового количества объектов, тем лучше будет выглядеть игра.
Даже если вы смотрите на то, что находится перед вами в сцене, это не значит, что необходимо отрисовывать весь город полностью. Если слева или справа есть огромное здание, то всё за ним становится невидимым, поэтому можно это не отрисовывать. Аналогично, некоторые объекты в отдалении становятся очень мелкими, поэтому нам не нужно волноваться об отрисовке крошечных растений и кустарников вдалеке.
В этой анимации показано, что если пройтись по улице дальше того, что мы видим, то в боковых улочках ничего нет, а на большом расстоянии деталей становится меньше.
На самом деле в этой сцене всё равно есть множество объектов, которые в результате окажутся невидимыми. В этой области ещё много предстоит исследовать и необходимо применять различные сложные техники. Всегда приходится идти на компромиссы, но если можно потратить немного времени, или придумать очень умный способ, чтобы избежать отрисовки 100 объектов почти без лишних усилий, то мы сможем сделать сцену ещё более сложной или плотнее заполненной.
Также здесь есть ещё одна небольшая проблема. В некоторых случаях нам приходится отрисовывать здание, которое едва видно на экране и далеко выходит за его пределы. Это тратит ресурсы впустую. Мы всегда можем разбить эти объекты на несколько частей, тогда каждую часть можно отрисовывать или пропускать, то есть пустых трат будет меньше. Однако теперь мы увеличили общее количество отрисовываемых объектов, когда они находятся на экране, и создали противоположную проблему!
Этот пример — один из сотен, но на нём легко объяснить, какие решения приходится принимать и на какие эксперименты идти, чтобы найти для каждой игры идеальную точку равновесия.
Так как действие нашего кадра из Watch Dogs происходит в городе, мы можем взглянуть на него сверху, чтобы приблизительно понять, что же именно отрисовывается. На этой статичной картинке особенно заметно, что Watch Dogs выполняет работу поквартально.
Теперь мы отдалимся, чтобы показать видимую область перед камерой (простите за монтаж для экономии времени).
На изображение наложена примерна область видимости камеры. Ширина этого треугольника зависит от зоны видимости (Field of View) — иногда в играх это настраиваемая опция, иногда постоянное значение.
Вот каркасное отображение сцены, область видимости камеры ограничена белым.
Кто-нибудь из вас уже мог подумать о небольшом трюке, который бы позволил обойти ограничение отрисовки определённого количества объектов — почему бы не сделать объекты очень сложной комбинацией всего в небольшой области, вплоть до отдельных листьев? Тогда отрисовки 1000 объектов будет более, чем достаточно.
Но здесь мы сталкиваемся с совсем другим набором ограничений — графические карты имеют ограниченную производительность, и чем сложнее объект, тем дольше будет происходить его отрисовка. То есть даже один объект, если он достаточно сложен, может снизить частоту кадров игры до 20 FPS. В том числе и поэтому я говорил, что ограничение на количество объектов довольно нечёткое.
Объём временных затрат на объект зависит от сложности и детализированности моделей и текстур, а также от изощрённости освещения и теней. В том числе и поэтому игры, пытающиеся реализовать более сложную или изощрённую графику стремятся к использованию менее сложных и детализированных сцен — уравновешивание смещает весы в одну или другую сторону, поэтому можно обеспечить себе больше пространства для манёвра, пожертвовав тем, что не так важно для игры.
Это своего рода тепловая карта, демонстрирующая, в каких частях сцены есть особо сложные модели. Заметьте, сколько проблем могут вызывать деревья и растительность.
Есть ещё один набор техник под названием «уровни детализации» (level of detail, LOD), который специально создан для решения подобных проблем. Аналогично тому, что мы можем оптимизировать количество объектов, отсекая всё ненужное, мы можем увеличить запас «сложности», устранив лишнее.
Один из приёмов на самом деле хорошо известен — это изменение разрешения текстур. Эта тема обычно пересекается со множеством других, поэтому я постараюсь объяснить её доступно.
Текстуры в играх обычно являются прямоугольниками с размерами, равными степени двойки — 512, 1024, 2048, 4096. На то есть множество причин, но одно из преимуществ этого заключается в том, что можно взять текстуру размером 1024x1024 и запросто создать её уменьшенную версию размером 512x512.
По причинам, о которых я расскажу ниже, всегда необходимо, чтобы у текстуры были всевозможные версии меньшего размера. То есть у текстуры размером 1024x1024 будут уменьшенные версии размером 512x512, 256x256, 128x128, 64x64, 32x32, 16x16, 8x8, 4x4, 2x2 и 1x1. Однако одно из преимуществ этого в том, что если далёкие объекты имеют на экране небольшой размер, то накладывать на них текстуру 1024x1024 — значит зря тратить ресурсы. Мы можем сэкономить, использовав меньшие версии той же текстуры.
Аналогично, даже близкие объекты можно считать не особо значимыми и использовать для них текстуры поменьше.
Примечание
Обычно, когда игрок приближается к объекту, используется наибольшая текстура, но как видело большинство людей в той или иной игре, такое происходит не всегда и текстуры перед загрузкой выглядят излишне размытыми. Обычно так происходит потому, что текстуры невозможно загрузить с DVD или жёсткого диска сразу в память графической карты для рендеринга. Чаще всего такое случается, когда игрок резко меняет своё положение, например, при респауне, загрузке нового уровня или очень быстром движении. Во всех остальных случаях текстуры обычно загружаются постепенно, в процессе перемещения игрока по миру.
Также можно применить этот процесс упрощения и к используемым в игре моделям, хоть сделать это и гораздо сложнее. Создавая упрощённые версии сложных объектов, можно сделать так, чтобы на большом расстоянии они не отъедали часть ограниченного запаса сложности.
Благодаря упрощённым объектам и обрезанным моделям можно сильно сэкономить и такой подход приходится использовать в любой игре наподобие Watch Dogs. Но в то же время он может быть огромной растратой ресурсов. Необходимо принять очень продуманное и взвешенное решение о том, сколько потребуется упрощённых версий моделей. Если их будет слишком мало, то вам или не удастся слишком много сэкономить, или будет заметны скачки качества при их смене. Если их будет слишком много, то вы впустую займёте память и потратите человеко-часы, необходимые на создание объектов.
Вот как выглядят сложные объекты и персонажи на расстоянии, когда они неотличимы от своих высокодетализированных версий.
Надеюсь, теперь вы получили некоторое представление о проблемах, с которыми сталкиваются программисты графики, художники и дизайнеры уровней, пытающиеся совместить максимальное качество графики с высокой скоростью. На консолях это уравнение решить немного проще, чем на PC, потому что оборудование неизменно.
В предыдущей части я сказал, что расскажу о том, почему очень сложно правильно реализовать отражения и другие подобные вещи. Причина довольно проста — запасы производительности и ресурсов, о которых я постоянно твержу, не меняются в зависимости от того, есть у вас отражения или нет. Если вы хотите создать отражение, которое может снова отобразить всю сцену, то придётся заново выполнять всю ту работу, о которой я говорил. Кроме того, отражения очень сильно зависят от того, под каким углом мы на них смотрим, поэтому для получения точных отражений у каждого отражающего объекта они должны быть своими!
Это очень быстро может выйти из под контроля, и обычно игры, в которых есть отражения, позволяют себе определённые вольности или допущения. Игры очень редко создают отражения в сложных окружениях, максимум, что это может быть — зеркало в ванной комнате, где объектов не так много, сложность сцены мала, а потому можно позволить себе лишние траты. Возможно, истинные отражения в игре будут только на неровных или волнистых поверхностях воды, чтобы даже очень грубой и низкодетализированной сцены вполне достаточно для создания убедительных отражений.
Обычно полностью избежать отражающих поверхностей сложно, поэтому в играх используются заранее отрендеренные изображения ближайшего окружения, что даёт «достаточно хороший» результат. Но они не выдерживают пристального изучения, и если внимательно посмотреть на отражения, то вы увидите, что это имитация. Существуют современные техники, помогающие создавать отражения в определённых условиях, и возможно позже я о них расскажу, но такие заранее отрендеренные («пререндеренные») изображения всё равно нужны.
Это пререндеренное изображение называется «кубической картой» (cube map). Оно не совсем точно относительно места, в котором стоит Эйден, но достаточно к нему близко.
Watch Dogs выполняет рендеринг отражений в реальном времени. Я не исследовал этот вопрос подробно, но полагаю, что они всегда рендерятся, когда персонаж находится на улице, и в основном используются для того, чтобы получать его точные отражения на автомобиле, в котором он сидит, чтобы улучшить изображение и придать ему лёгкое ощущение реальности. Так как игрок всегда сосредоточен на своей машине и ближайшем окружении, то факт неправильности отражений на других автомобилях едва заметен.
Существует множество аппроксимаций для ускорения рендеринга этих отражений. Например, в отражении рендерится намного меньше объектов, чем есть в настоящей сцене — всего около 350 — и из них многие очень упрощены по сравнению с полными версиями. Я подозреваю, что сложные объекты, например люди, полностью отбрасываются вне зависимости от расстояния, но эту теорию я не проверял. Кроме того, на этих объектах нет теней, а освещение очень простое — только то, что поступает от солнца и неба. Отражения рендерятся с земли как в объективе «фишай», то есть отражения самой земли невозможны, а то, что находится рядом с ней, имеет очень низкую детализацию.
Но даже при всех этих упрощениях отражения справляются только с тем, что было задумано. Если проехать под рельсами, то можно получить правильное отражение с видом над автомобилем, чего на практике добиться не получилось бы.
Это решение было намеренным и принималось непросто. Запас — величина постоянная, поэтому если оставить место для этих отражений, то нужно принести в жертву что-то ещё.
Вот очень «фишайное» отображение сцены вокруг Эйдена с видом снизу, сделанное для отражений. Сориентироваться можно по двум фонарям и рельсам поезда.
Есть ещё одна часть работы, которую я хочу здесь упомянуть — тени. Позже я планирую рассказать о том, как работают тени, потому что это интересная тема, но сейчас самое важное — запомнить, что тени очень похожи на отражения. Каждый источник освещения, отбрасывающий тень, должен отрендерить изображение сцены со своей точки обзора. На этот раз способов упростить работу не так много — для правильного вычисления теней каждый источник освещения обязан иметь это изображение.
Примечание
Здесь я говорю только об освещении, вычисляемом во время игры. Раньше часть освещения вычислялась пререндерингом всей необходимой информации до запуска игры, то есть тени и освещение «запекались» в уровень, они были постоянными и их невозможно было менять или реагировать на них динамически. Такая техника в современных играх используется не так часто, и очевидно, что её нельзя применять к подвижным источникам света.
Наиболее очевидным и самым значимым источником дающего тени освещения является солнце (или луна, если дело происходит ночью). Так как солнце огромно, для него обычно рендерится 3-5 изображений, а не одно, как в случае фар или фонарика.
К сожалению, это один из случаев, когда Watch Dogs не может служить хорошим примером. Расчёт теней в игре довольно сложен и, как мне кажется, специально оптимизирован для случая отбрасывания теней в городе. Поэтому я лучше переключусь на Far Cry 4 и рассмотрю вычисление теней на примере кадра из этой игры.
Вот сцена из Far Cry 4, которую я использую просто для примера.
Вот изображение с информацией о тенях этой сцены — для каждого из них требуется совершенно новый рендеринг сцены.
Поэтому когда нам нужно добавить к источнику освещения отбрасывание теней, то придётся отрендерить сцену ещё один раз. Здесь тоже можно использовать некоторые из аппроксимаций, применяемых в случае отражений, только их намного меньше. Можно пропускать мелкие или далёкие объекты, но нужно учесть, что эти объекты будут казаться не отбрасывающими тени. Можно отрендерить изображение очень маленьким, но тогда тени будут зернистыми и низкодетальными. Обычно не получается использовать очень упрощённую версию объекта, потому что тогда будет казаться, что объект отбрасывает тень сам на себя, или появятся просветы между объектом и его тенью.
Ещё одно следствие, которое очень легко упустить — необходимость создания изображения затенения для каждого источника освещения. Во многих случаях удаётся упростить источники освещения, объединив их — в Watch Dogs такое происходит с фарами автомобилей.
Когда включены обе фары, то отрисовывается только один источник освещения, но имеющий особую форму, из-за которого он выглядит как два луча. Если у фар есть тени, то это сделать уже не так просто, и будет гораздо заметнее — когда игрок пройдёт перед автомобилем, то свет будет идти откуда-то между двумя фарами. Возможно, тогда придётся разделить фары, но при этом не только появятся дополнительные затраты на вычисление теней, но и придётся отрисовывать новое освещение.
Основное, что я хотел подчеркнуть всем этим — это компромиссы. Мы совершенно точно можем избавиться от всех этих аппроксимаций, но придётся потратить на это наши запасы ресурсов, то есть пожертвовать чем-то другим. Каждый разработчик игр должен решать, над чем ему важно сосредоточиться в игре, и что будет наиболее впечатлять или сильнее всего раздражать игрока.
Часть 4. Двигаем вершины
В этой части я подробнее расскажу о технических деталях анимирования объектов в сцене.
Программисты графики часто говорят о «графическом конвейере» (graphics pipeline). 3D-графика немного напоминает сборочный конвейер с чётко заданным перемещением от одного этапа к другому, однако она не работает одновременно только с одним объектом.
Все современные графические карты имеют приблизительно одинаковый конвейер, у них есть специальное оборудование и ПО, прошитое непосредственно «в кремнии», для максимального ускорения работы конвейера. Разумеется, между разными производителями и семействами графических карт есть множество вариаций, но обычно нам не нужно беспокоиться о том, как они работают на этом уровне.
Примечание
Если вам интересно, как всё работает на абстрактном аппаратном уровне, то рекомендую серию статей Фабиана Гисена о графическом конвейере. Эта серия статей намного подробнее и требует гораздо большего понимания, чем мой пост.
Я буду пропускать многие подробности, чтобы объяснить интересные и важные принципы. В этой части мы рассмотрим первую часть конвейера, которая называется «вершинным шейдером» (Vertex Shader).
Шейдеры получили широкое распространение около 16 лет назад, после выпуска DirectX 9, в котором появились вершинные и пиксельные шейдеры. Чтобы объяснить, что такое шейдеры, и сравнить их с тем, что было раньше, я расскажу о той работе, которую они выполняют.
Каркасная голова Эйдена снова с нами.
Напомню, что в части 2, мы рассматривали модели, из которых создаётся игровой мир. Эти модели состоят из отдельных точек, называемых вершинами, которые соединены в треугольники. Я сказал, что подробнее расскажу о них позже, и теперь выполняю своё обещание.
Так как всё в мире игры состоит из вершин, то всё, что нужно делать с этими моделями, необходимо выполнять с вершинами. Когда дело доходит до графической карты, то всё, что она видит — это длинный список вершин. Для неё нет таких вещей, как анимация бега, или раскачивающиеся листья деревьев, или любые другие абстрактные концепции.
Давайте рассмотрим простой пример того, чем нам предстоит заниматься — перемещение и расположение объектов в мире. Для начала возьмём простой случай, не персонажа.
При создании объектов в 3D-редакторах наподобие Maya и 3D Studio Max художники всегда строят из в собственном отдельном мире. Эти объекты не создаются сразу в антураже Чикаго, скорее их окружение похоже на «пустую белую комнату» из «Матрицы». Каждый объект располагается в центре абсолютной пустоты.
Вот какой-то светофор, находящийся в игре где-то под рельсами, и он расположен в собственном мире.
При сохранении на диск модель не имеет никакого представления о том, где будет находиться в мире и когда она будет загружена и передана в графическую карту. Это значит, что когда наступает время отрисовки объектов, нам нужно переместить её из собственного мира в сцену, которую мы отрисовываем. Это происходит с каждым отрисовываемым объектом, и даже такие неподвижные объекты, как здания и мосты тоже в каждом кадре перемещаются из собственного мира в сцену.
Здесь мы видим несколько светофоров, уже размещённых в частично построенной финальной сцене.
Как я упоминал выше, единственно, с чем мы можем работать — это вершины. Мы не можем просто сказать графической карте: «Можешь поместить этот светофор под мост? А потом поставить ещё один чуть дальше? Отлично!»
Поэтому вместо того, чтобы говорить графической карте, что ей нужно делать с самим объектом, нам нужно сказать, что делать со всеми его вершинами. Оказывается, что в таком случае всё становится очень просто. Если мы меняем все вершины абсолютно одинаково и они остаются относительно друг друга неподвижными, то это аналогично перемещению объекта как целого. Всё, что нам нужно — это разобраться с тем, что такое «изменение», которое называется преобразованием.
Примечание
Математические расчёты всего этого не особо сложны, но выходят за рамки статьи. Если вы уже изучали линейную алгебру, то, вероятно, знаете всё необходимое — в основном всё сводится к матричному умножению векторов.
До появления вершинных шейдеров возможные варианты действий с преобразованиями вершин в графической карте были очень ограничены. Доступны были перемещение и повороты объектов, а также некоторые другие операции, но ничего особо излишнего или сложного.
Вершинные шейдеры — это небольшие компьютерные программы, выполняемые в графической карте. Они берут одну вершину, выполняют с ней любые нужные действия и выдают выходную вершину. Они могут не просто переместить её, но и заставить прыгать вниз и вверх, отодвигаться от ближайшей вершины, раскачиваться в зависимости от ветра, анимироваться и многое другое.
Я взял один из вершинных шейдеров, которые используются в Watch Dogs, и немного поэкспериментировал с ним, чтобы показать, что он делает. Это довольно кропотливая работа, но мне удалось найти вершинный используемый для персонажей шейдер и изменить его. Есть другой вершинный шейдер, используемый для кожи, например, для лиц и рук, но вы быстро поймёте принцип.
Я внёс очень простое изменение, добавляющее в модели персонажей изгиб, но всё остальное используется как обычно.
На показанной выше анимации видно, что персонажи как-то странно искажены. Это возвращает нас к принципу «если переместить каждую вершину, то это аналогично перемещению всего объекта». Внесённое в вершинный шейдер изменение работает только для одной вершины за раз, но поскольку все они работают с одинаковым искажением, эффект в результате применяется ко всему объекту.
Можно также внести изменения в часть преобразования, ответственное за «простое перемещение на место», чтобы вместо расположения персонажа в нужном месте она поднимала его над землёй. Это не симулируется с помощью физики, поэтому никак не связано с гравитацией или коллизиями с другими объектами — при желании мы можем заставить всех парить в воздухе.
В этой анимации мы заставили вершинный шейдер постепенно поднимать и опускать объект.
Очевидно, что всё это не слишком конструктивно, однако даёт нам общее представление о том, как работают вершинные шейдеры — если бы теперь мы хотели анимировать листья на деревьях, чтобы они раскачивались, то сделали бы примерно то же самое. Только вместо поднятия и опускания они бы качались в направлении ветра. Затем мы можем менять силу ветра, чтобы деревья раскачивались сильнее или слабее.
Здесь мы видим, что происходит, когда всё увеличивается примерно в семь раз.
Я немного упрощаю, но в основном задачей большинства вершинных шейдеров и является «перемещение объектов на место». Исключение составляют все анимированные объекты — люди, животные, а также такие предметы, как качающиеся верёвки, развевающаяся одежда и т.д.
Анимирование людей связано с идеей применения скелета и «скина». Скелет — это простое описание подвижного персонажа. На этом этапе нас волнуют не анимированные пряжки, шляпы или джинсы — нам важны только важные движения, наиболее соответствующие скелету человека.
Здесь показан простой скелет фигуры человека. Он взят не из Watch Dogs, потому что скелет сложно визуализировать вне пределов 3D-редактора.
Изображение лицензировано по Attribution-ShareAlike CC BY-SA © MakeHuman team 2001-2014
Это поза привязки или T-поза, демонстрирующая, как выглядит персонаж без применения анимаций.
Анимации — бег, ходьба, прыжки — применяются только к этому скелету. Это делает весь процесс очень простым, потому что нам нужно учитывать только около сотни костей вместо тысяч и тысяч вершин.
После создания скелета каждая вершина привязывается к одной или нескольким костям в этой статичной позе, и эта связь называется «скиннингом». Во времена Half-Life 1, когда начала использоваться эта технология, каждая вершина связывалась только с одной костью. В наши дни могут присоединяться даже к четырём костям, при этом каждой кости придаётся вес, от которого зависит степень влияния кости на вершину. Благодаря этому можно получать более плавную анимацию, позволяя костям пересекаться в разных областях без создания резких углов при движении рук или ног.
Это показанный выше скелет с повёрнутой костью бедра. Цвета показывают веса кости бедра относительно вершин модели.
Изображение лицензировано по Attribution-ShareAlike CC BY-SA © MakeHuman team 2001-2014
У этой технологии есть свои ограничения, особенно в тех местах, где одежда и кожа сжимаются или растягиваются в шарнирах, например, на локтях и плечах. Это неидеальный, но очень эффективный способ анимирования. Самое серьёзное ограничение — таким образом очень сложно создавать убедительные анимации лиц. Можно создать на лице множество «фальшивых» костей, например для бровей и вокруг рта, но это будет всего лишь грубым приближением мышц и кожи.
Другая важная особенность этой техники заключается в том, что сопоставление вершин и костей очень конкретно и связано с тем, как выполнено это сопоставление. Вполне возможно использовать анимации для нескольких разных моделей, но каждая отдельная модель, в которой нужно использовать скелет, должна быть связана с костями. Причина в том, что анимации перемещают вершины из исходного положения относительно к их скелетам. Если вершины не находятся в ожидаемых позициях, то возникнут проблемы.
Если модель не соответствует используемому ею скелету, то анимации будут совершенно неверными.
Если мы сделаем персонажа раза в два шире, то получим эффект, похожий на «объевшегося пончиков Дрейка», но заметно, что ближе к кистям анимации становятся неправильными, потому что именно здесь они дальше всего от своих исходных позиций.
Первая анимация может показаться вам странно знакомой — подобные глитчи анимации возникают в играх очень часто. Обычно они вызываются тем, что к скелету применены неправильные анимации, или в модели используется неверный скелет. Как и в случае с текстурами и моделями, скелет, скиннинг и модель нужно очень тщательно сопоставлять, иначе результаты быстро окажутся печальными.
Надеюсь, вы получили некоторое представление о предназначении вершинных шейдеров и поняли, как применяются анимации для превращения статичной модели в живого персонажа.
Комментарии (11)
Olorin111
04.12.2018 00:443D-модель целиком состоит из взаимосвязанных треугольников
Мне, кстати, казалось, что треугольники считаются дурным тоном, что их стараются всячески избегать, предпочитая четырехугольники. Это не так?PeterSikachev
04.12.2018 01:53На стадии моделирования 3Д художники стараются использовать квады, потому что с ними проще работать с геометрией, например, тесселлировать. Однако при экспорте из DCC tools (Maya, Max, Blender etc) в формат для игры вся геометрия конвертируется (опустим для простоты случай применения тесселляции в реальном времени на GPU) в треугольники: GPU для простоты умеет растеризовывать только их (ибо зачем усложнять hardware и создавать стопицот разных конвейеров для разных примитивов, если можно любой многоугольник разбить на треугольники).
volchonokk1
04.12.2018 13:50Да, это правда. На этапе создания модели лучше работать с четырёхугольнкиами — с ними проще работать (делить, резать, выстраивать в линии...) и анимировать (меньше искажений чем с треугольниками). Но на этапе эскпорта в движок, все полигоны автоматически конвертируются в треугольники — на базовом уровне игровому движку и видеокарте проще всего визуализировать наименее сложные и однотипные плоскости.
Tutanhomon
04.12.2018 14:58ну, не совсем. Импорт в движок бывает разный и зависит от движка. В Юнити, например, можно импортировать модель оставив квады. А вот когда движок отправляет модель на рендер, туда уходит грубо говоря набор вершин, в треугольники они связаны лишь посредственно, только тем, что их индексы находятся в одном массиве. Но да, по сути видеокарта рисует именно треугольники.
lgorSL
04.12.2018 01:54Во времена Half-Life 1, когда начала использоваться эта технология, каждая вершина связывалась только с одной костью. В наши дни могут присоединяться даже к четырём костям, при этом каждой кости придаётся вес, от которого зависит степень влияния кости на вершину.
К сожалению, в РПГ играх по неизвестной мне причине части доспехов привязываются к нескольким костям сразу, из-за чего железный доспех крайне неестесственно растягивается или мнётся при каждом движении персонажа.
DelphiCowboy
04.12.2018 05:52Потому что чтобы доспех не мнулся и не растягивался он должен иметь свой собственный жёсткий «скелет», но, увы, 3D-дизайнеры поступают проще не заморачиваясь с отдельным «скелетом» для латного доспеха, и в результате он мнётся и растягивается как обычная одежда.
DrZlodberg
04.12.2018 08:55Тут вопрос не в дизайне. Сделать скелет для доспеха не проблема, тем более, что (как уже замечено в комментарии выше) достаточно использовать оригинальный скелет, но прописать только по одной кости на треугольник. Проблема в другом: если он будет жестким — придётся очень тщательно следить за тем, чтобы ни в одной фазе тушка не вылезала сквозь доспех, что с учётом частично процедурной анимации вообще нереально. Разве что дополнительно следить за их коллизиями, что лишний гимор уже для движка.
volchonokk1
04.12.2018 14:01Проблема в том что при привязке к одной кости, игнорируются анимации других костей. Если условно привязать нагрудный доспех только к «грудной» кости персонажа, то доспех при повороте персонажа будет пролезать сковзь персонажа в районе рук и пояса. Выход из этого есть — большее кол-во костей, в идеале отдельные кости под доспехи. Но чем больше костей — тем больше работы аниматорам и риггерам (это те кто привязывают модели к костям). А бюджеты у игр к сожалению не бесконечные. И даже в этом варианте доспех всё равно будет мнуться, просто не настолько сильно.
Чтобы сделать совсем реалистичное сочетание жёсткого доспеха и динаимического тела персонажа, нужна симуляция взаимодействия динамики и нединамики (условно говоря чтобы тело и мягкая одежда мнулась под воздействем жёстких пластин доспеха) — а это уже более затратный (в т.ч. для рендеринга) процесс.
Вообще процесс анимации и привязки к костям в играх это вечный геморой. Чем более свободна игра — тем больший геморой, т.к. надо предусматривать множество анимаций и множество персонажей (часто на одном скелете) и всё это делать ограниченными средствами. Это ещё более заметно в играх где можно менять одежду — привязывать разноразмерную одежду под одного персонажа это совсем невесело.
janonymous
04.12.2018 12:17-1Есть еще куча всяких трюков для увеличения производительности, например использование билбордов (плейнов для деревьев, вместо сложной модели), использование импостеров (почти что спрайтов, то есть картинки вместе модели), так же разбитие пространства на сегменты при помощи деревьев (это кстати сразу напоминает ГТА) и куча куча всего, что можно на несколько больших статей расписать
spam312sn
T-поза персонажа напомнила о забавном баге в игре "The Last of Us". В зимнем эпизоде (после Колорадского университета), в ходе боя с канибалами, Элли проходит через трубу и если замешкаться на выходе, то она впадает в Т-образное состояние и так продолжает перемещаться, пока игрок не подлетит к трубе обратно