Аппаратный рендеринг что это
Что такое рендер-фермы и рендер-станции — для чего они нужны
Содержание
Содержание
За несколько последних лет возрастает потребность пользователей в визуальном контенте. Это не только графика для игр или фильмов, а еще и разнообразные архитектурные, дизайнерские проекты, анимация для бизнес-идей и многое другое. Причем визуализация постоянно усложняется и для процесса рендеринга требуется немалое количество ресурсов. Вот здесь-то и приходит на помощь рендер-ферма или рендер-станция.
Что такое рендеринг
Рендеринг — это процесс, который не смогут обойти стороной те, кто работает с двухмерной или трехмерной графикой, анимацией. В переводе с английского рендеринг означает «визуализация». В ходе рендеринга происходит преобразование трехмерной сцены в статическое изображение (рендер) или же в последовательность кадров. Скажем проще: созданный в специальной программе набросок изображения превращается непосредственно в само изображение со своими цветами, тенями, освещением и т.п. А еще проще и банальнее – это процесс получения изображения с помощью специальной компьютерной программы.
Как работает рендеринг и что для него необходимо
Рендеринг — это довольно трудоемкий и сложный процесс, в ходе которого происходит множество математических вычислений. Проходит просчет и определение теней, текстур, отражения и многого другого.
Разработчики специальных программ для 3D-моделирования и рендеринга позаботились о том, чтобы пользователи не утруждали себя многочисленными просчетами и работали с привычными для них настройками.
Естественно, что для рендеринга требуется один или несколько компьютеров, программы для 3D-моделирования и визуализации (с соответствующими плагинами), программы для работы с графикой. Чаще всего рендер-движки уже встраиваются в графические программы, например, в такие как 3ds Max, Maya. Помимо этого, есть самостоятельные профессиональные системы для рендера, например, V-ray, Mental ray, Corona Renderer. Такие программы часто именуют рендерером.
Если говорить о значимости «начинки» компьютера для рендеринга, то здесь мы встретим подразделение на CPU Rendering и GPU Rendering. Первый вариант при просчете использует ресурсы процессора и оперативной памяти, а в случае с GPU, основная задача по визуализации ложится на видеокарту (графический процессор). Чему именно будет отдано предпочтение, зависит от используемой системы рендеринга.
Время просчета будет зависеть от массы факторов. Сюда входят сложность сцены, прозрачность или отражающие свойства материалов, текстуры, тени и освещенность, высокополигональность моделей и вычислительная мощь оборудования. Также есть зависимость и от того, какой метод использует система рендеринга.
Сколько же времени может занять рендеринг? В зависимости от всего вышеперечисленного от нескольких секунд до нескольких часов и даже дней.
Возьмем простой калькулятор на рендер-ферме и попробуем рассчитать стоимость и приблизительное время, выбрав примерные настройки. Результаты видны на трех скринах, причем выделено время просчета на домашнем компьютере и на ферме.
Что такое рендер-ферма
Рендер-ферма — это множество компьютеров, объединенных в единую вычислительную сеть. Такие сети или системы обычно именуют узлами. В зависимости от фермы, число таких узлов может доходить до нескольких тысяч.
Ферма как правило бывает двух типов: собственная (частная) и облачная (коммерческая). Первая создается под нужды какой-либо фирмы занимающейся, например, выпуском фильмов. Или же когда у отдельного дизайнера, фрилансера имеется несколько компьютеров с соответствующим программным обеспечением, и он использует их для рендеринга.
Облачные фермы предлагают услуги всем желающим пользователям. Данные фермы имеют на вооружении десятки серверов, необходимое оборудование, обслуживающий персонал. По сути это сложные профессиональные системы, располагающие мощнейшими, современными ресурсами для процедуры рендеринга.
Стоят профессиональные фермы довольно дорого. В стоимость входит не только цена на оборудование, софт, но и его обслуживание, охлаждение и т.п.
Что такое рендер-станция
Если ферма представляет собой несколько компьютеров объединенных в узлы, то рендер-станция (графическая станция) является отдельной машиной, предназначенной для работы с графикой, видео, дизайном. Такие станции базируются на различных платформах и комплектуются мощным «железом», которое чаще всего создается именно для работы с графикой. В пример можно привести профессиональную (и доступную рядовому пользователю) графическую карту Nvidia Quadro.
Как работают коммерческие рендер-фермы
Загруженные на фермы сцены могут рассчитываться на нескольких десятках и сотен рендер-узлах, что максимально сокращает время визуализации. Благодаря этому, несколько дней рендеринга возможно сократить до нескольких часов. Чтобы объяснить еще проще принцип работы ферм, сравним их с видеомонтажом.
Представьте, что у вас есть масса памятных видеокадров, которые вы хотели бы объединить в единый фильм с музыкальными вставками и по возможности, со спецэффектами. Чтобы это сделать, нужен компьютер, программное обеспечение и умение делать монтаж. Если же у вас что-либо из этого отсутствует, то вы естественно отдадите свои видеозаписи профессионалу, который за определенную сумму сделает для вас фильм. Тоже самое можно сказать и о рендере.
Работа рендер-фирм строится практически по одинаковому сценарию. Пользователь проходит процесс регистрации, пополняет счет (многие фермы предлагают попробовать бесплатно) и приступает к процессу. Для этого необходимо загрузить 3D-сцены на ферму, задать желаемые настройки и запустить процесс.
Важным моментом является загрузка с сайта программы или плагина, который встраивается в используемую пользователем программу (например, 3ds Max). Его задача — проверить все сцены и экспортировать их в ферму, сохраняя заданные пользователем настройки. Стоит отметить, что все фермы поддерживают наиболее часто используемые программы, приложения и плагины.
Рендерить на ферме или у себя дома?
На этот вопрос должен ответить сам пользователь, который делает статичные изображения, анимацию, спецэффекты и многое другое. Чтобы выполнять рендеринг дома необходим мощный компьютер, в котором главную роль будут играть процессор (архитектура, количество ядер, кэш) и количество оперативной памяти. Можно сказать, чем мощнее будет сборка, тем лучше для процесса.
В домашней ферме также используют несколько компьютеров, объединив их в локальную сеть. Также многое зависит от объемов работы. При больших объемах и сложных проектах, пользователю может не хватить одной или нескольких машин и в любом случае придется собирать свою ферму или же обратиться за помощью к коммерческой ферме. В сравнении с этим у рендер-ферм есть несколько существенных преимуществ.
1. Простота и поддержка. Пользоваться фермами довольно легко, к тому же на каждой из них пользователь сможет обратиться в службу поддержки.
2. Экономия средств и времени. В первом случае пользователь, которому нужен рендеринг, заплатит только за процесс на ферме и будет избавлен от закупки «железа» для собственной фермы и ее обслуживания. Ну и конечно же экономия времени, которое так необходимо, когда, например, у фрилансера масса заказов. Не стоит забывать о том, что дома на последних часах и минутах может отключиться электричество и все многочасовые труды пропадут.
Особенности рендеринга на рендер-ферме
Остановимся на некоторых особенностях, которые желательно знать и помнить всем посетителям ферм.
Онлайн-калькулятор.
Он есть на каждой ферме и с его помощью пользователь сможет рассчитать и оценить время и стоимость рендеринга. Однако, все это примерно и условно, так как до сих пор нет четкого и реального метода оценки времени рендеринга.
Совместимость ПО
Прежде чем приступить к работе, необходимо определить совместимость ПО, на котором был сделан проект и ПО имеющегося на ферме. Хотя, выше уже указывалось на то, что многие фермы имеют все последние версии ПО, плагины и загружают для пользователя свои плагины для проверки проекта. Несмотря на это, нужно быть внимательным, так как в некоторых ситуациях потраченные деньги фермы могут не вернуть.
Хранение данных
Фермы осуществляют хранение проектов и всех данных, однако через какой-то промежуток времени они могут быть удалены. Через какой промежуток именно, нужно узнавать на самой ферме.
Правила пользования
Прежде чем начинать работу на той или иной ферме, необходимо детально ознакомиться с правилами пользования фермой. Узнать каким образом она предоставляет кредиты, можно ли вернуть деньги и т.п. Для разрешения всех спорных или непонятных вопросов на каждой из ферм должна работать служба поддержки в режиме 24/7.
Аппаратное ускорение рендеринга в браузере Chrome
Введение
Для большинства веб-разработчиков фундаментальным представлением веб-странницы является DOM. В то время как процесс преобразования этого представления в изображение на экране (далее рендеринг) часто покрыто пеленой непонимания. В последние годы разработчики браузеров активно оптимизируют этот процесс, перекладывая часть работы на плечи графических процессоров: то что называется “аппаратным ускорением (hardware acceleration)”. Мы рассмотрим рендеринг в контексте обычных страниц, исключая Canvas2D и WebGL. Эта статья попытается пролить свет на фундаментальную концепцию использования аппаратного ускорения при генерации изображения веб-контента в браузере Chrome.
Предупреждение
Мы здесь будем говорить только о движке WebKit, а если быть более точным о его форке в Chromium’е. Эта статья покрывает детали реализации в Chrome’е, которые не коем образом не задокументированы в стандартах. Поэтому нет гарантий в том, что, что-либо описанное в этой статье будет применимо к другим браузерам. Тем не менее, знание этих тонкостей поможет вам провести тонкую отладку и повысить производительность.
Также, примите во внимание, что движок рендеринга в Chrome’е развивается стремительными шагами. И нет гарантий в том, что всё здесь описанное будет справедливо так же через полгода.
Важно иметь в виду, что на данный момент Chrome поддерживает два механизма рендеринга: аппаратный и старый программный. На момент написания этой статьи все страницы рендерятся с привлечением аппаратного ускорения в Windows’е, ChromeOS и Chrome’е на Android. В MacOS и Linux только страницы, содержащие композиционные элементы сваливаются в этот режим (ниже мы поговорим об этом), но совсем скоро это исправят.
В конечном счёте мы рассмотрим лишь верхушку движка рендерера, уделяя внимания тем вещам, которые призваны существенно повысить производительность. Практика на вашем собственном сайте поможет вам лучше понять модель слоёв. Но не стоит бездумно плодить их количество, это может внести лишние накладные расходы на весь графический стек.
Путь от DOM’а на экран
Концепция слоёв
Когда страница загружена и обработана в браузере, она превращается в то, что всем известно как DOM. Процесс рендеринга страницы протекает через серию промежуточных преобразований непосредственно недоступных разработчику. Важнейшая из порождаемых в процессе структур — слой.
Слои в Chrom’е бывают двух типов: Слои Преобразования (RenderLayers), которые содержат поддеревья DOM, и Графические Слои (GraphicsLayers), которые содержат поддеревья предыдущих. Сейчас последние для нас представляют больший интерес, так как Графические Слои это то что отправляется графическому процессору в качестве текстур. Далее по тексту везде, где встречается слово “слой”, я подразумеваю именно Графический Слой.
Слегка отвлечёмся на GPU терминологию и попробуем разобраться, что такое текстура?
Думайте о ней как о порции битового отображения графического объекта (bitmap), которая передаётся из основной памяти (RAM) в память графического процессора (VRAM). Когда текстура попадает в руки GPU, он может натянуть её на полигональную сетку — в видео играх это техника используется для натяжки “кожи” на трёхмерные объекты. Chrome использует текстуры для передачи порций контента страницы графическому процессору, что бы тот впоследствии мог наложить их на простую квадратную сетку и вытворять с ней любые трёхмерные преобразования. Именно так работает 3D CSS и также это прекрасно сказывается на производительности прокрутки страницы — но обо всём по порядку.
Давайте рассмотрим пару примеров для более наглядной демонстрации концепции слоёв.
Есть очень полезный инструмент для понимания слоёв в Chrome’е — опция “show composited layer borders” в разделе “rendering” окна настроек панели инструментов разработчика. Эта опция просто подсвечивает границы расположения слоёв на экране оранжевым цветом. Давайте её включим. Все скриншоты для примеров были сделаны в Chrome Canary, Chrome 27 на момент написания данной статьи.
Пример 1: Однослойная страница
Эта страница имеет единственный слой. Голубая сетка представляет собой границы тайлов — частей слоя, которые Chrome использует для разбивки большого слоя и отправки их GPU. Сейчас они не представляют для нас большого интереса.
Пример 2: Элемент в своём собственном слое
Задавая 3D CSS свойство элементу, которое поворачивает его, мы можем видеть, что ему выделяется его собственный слой: обратите внимание на оранжевую рамку, которая оборачивает его на скриншоте выше. (Это делается для того, что бы можно было рендерить каждый слой независимо от других)
Условия создания слоёв
Практическое применение: Анимация
Мы знаем, что посредством анимации CSS трансформации элемент свалится в свой собственный слой. А слои очень хороши для анимации.
Пример 3: Анимированные слои
В простейшем случае Chrome’е программно отрисует содержимое слоя и отправит его на отмашку графическому процессору в качестве текстуры. Если это содержимое не будет изменяться в будущем, то нет нужды его перерисовывать. И это хорошо: перерисовка отнимает время, которое может быть потрачено, например, на отработку скриптов. И если перерисовка занимает много времени, это может привести к досадным дефектам в виде падения частоты кадров и задержек в анимации.
Можете убедится, что на временной шкале панели разработчиков нет ни каких операций перерисовки, пока слой вращается туда — сюда.
Осторожно! Repainting
Но если содержимое слоя изменить, это приведёт к его перерисовке.
Пример 4: Перерисовка слоёв
Каждый раз, когда кнопка будет нажата, вращающийся элемент будет прибавлять пиксель в ширине. Это приведёт к перерисовке всего элемента, который в данном случае находится в отдельном слое.
Простой способ, увидеть какие части страницы перерисовываются включить опцию “show paint rects” всё в том же разделе “Rendering” окна настроек панели разработчиков. После включения обратите внимание как анимированный элемент, и кнопка мерцают красным при клике по кнопке.
События перерисовки можно также заметить на временной шкале. Зоркий глаз заметит сдвоение событий перерисовки: одно собственно для слоя, другое для смены отображения состояния кнопки.
Заметьте, что Chrome’е не всегда перерисовывает слой полностью. Он пытается быть выборочным и перерисовывать только те фрагменты слоя (те самые тайлы отмеченные голубыми границами) в которых DOM подвергся изменению. В нашем случае только один элемент на весь слой, но в большинстве реальных проектов элементов на слой приходится гораздо больше.
Напрашивается следующий вопрос: что приводит к устареванию DOM’а и его перерисовке? Сложно в нескольких предложения охватить все случаи, которые могут привести к невалидности DOM’а. В большинстве случаев это порча DOM’а посредством изменения CSS стилей и динамическая генерация видимого контента в рантайме. Тони Гентилкор описывал их в своём блоге, также Стоян Стояноф писал очень детализированную статью, но она ограничивается описанием перерисовки без деталей этой новой штуки, о которой мы пытаемся говорить — композиционирование посредством видео подсистемы.
Складываем всё вместе: DOM в экран
Как нам должно было стать понятным, основанная на слоях композиционная модель даёт большое преимущество для производительности рендеринга. Композиционирование более недорогой способ рендеринга в сравнение с полной перерисовкой. Поэтому попытка избежать перерисовки слоя, дёргая только композиционные свойства элементов — неплохой способ оптимизации производительности. Здравомыслящие разработчики возьмут на заметку список свойств активирующих композиционирование, что бы в подходящий момент мочь форсировать создание слоёв. Но будьте аккуратны в создание слоёв — это не бесплатно: им нужна системная память и память видео подсистемы (которая ограниченна на мобильных устройствах).
Software renderer — 1: матчасть
Программный рендеринг (software rendering) — это процесс построения изображения без помощи GPU. Этот процесс может идти в одном из двух режимов: в реальном времени (вычисление большого числа кадров в секунду — необходимо для интерактивных приложений, например, игр) и в «оффлайн» режиме (при котором время, которое может быть потрачено на вычисление одного кадра, не ограничено настолько строго — вычисления могут длиться часы или даже дни). Я буду рассматривать только режим рендеринга в реальном времени.
У этого подхода существуют как недостатки так и достоинства. Очевидным недостатком является производительность — CPU не в состоянии конкурировать с современными видеокартами в этой области. К достоинствам стоит причислить независимость от видеокарты — именно поэтому он используется как замена аппаратного рендеринга в случаях, когда видеокарта не поддерживает ту или иную возможность (так называемый software fallback). Существуют и проекты, цель которых — полностью заменить аппаратный рендеринг программным, например, WARP, входящий в состав Direct3D 11.
Но главным плюсом является возможность написания подобного рендерера самостоятельно. Это служит образовательным целям и, на мой взгляд, это — самый лучший способ понять лежащие в основе алгоритмы и принципы.
Это именно то, о чем будет рассказано в серии этих статей. Мы начнем с возможности закрашивать пиксель в окне заданным цветом и построим на этом возможность отрисовки трехмерной сцены в реальном времени, с движущимися текстурированными моделями и освещением, а так же с возможностью перемещаться по этой сцене.
Но для того, чтобы вывести на экран хотя бы первый полигон, необходимо освоить математику, на которой это построено. Первая часть будет посвящена именно ей, поэтому в ней будет много различных матриц и прочей геометрии.
В конце статьи будет ссылка на гитхаб проекта, который можно рассматривать как пример реализации.
Несмотря на то, что статья будет описывать самые основы, читателю все же необходим определенный фундамент для ее понимания. Этот фундамент: основы тригонометрии и геометрии, понимание декартовых систем координат и базовые операции над векторами. Так же, хорошей идеей будет прочтение какой-либо статьи об основах линейной алгебры для разработчиков игр (например, этой), поскольку я буду пропускать описания некоторых операций и останавливаться только на самых, с моей точки зрения, важных. Я постараюсь показать, при помощи чего делается вывод некоторых важных формул, но не буду расписывать их детально — полные доказательства можно сделать самому или найти в соответствующей литературе. По ходу статьи я сначала постараюсь давать алгебраические определения того или иного понятия, а затем уже описывать их геометрическую интерпретацию. Большая часть примеров будет в двухмерном пространстве, поскольку мои навыки рисования трехмерных и, тем более, четырехмерных пространств оставляют желать лучшего. Тем не менее, все примеры легко обобщаются на пространства других размерностей. Все статьи будут в большей степени ориентированы на описание алгоритмов, а не на их реализацию в коде.
Вектора
Вектор — одно из ключевых понятий в трехмерной графике. И хотя линейная алгебра дает ему очень абстрактное определение, в рамках наших задач под n-мерным вектором мы можем понимать просто массив из n действительных чисел: элемент пространства R n :
Интерпретация этих чисел зависит от контекста. Две самые частые: эти числа задают координаты точки в пространстве или же направленное смещение. Несмотря на то, что их представление одинаково (n действительных чисел), их концепции различаются — точка описывает положение в системе координат, а смещение не имеет положения как такового. В дальнейшем, мы так же можем определять точку с помощью смещения, подразумевая, что смещение происходит от начала координат.
Как правило, в большинстве игровых и графических движков присутствует класс вектора именно как набора чисел. Смысл же этих чисел зависит от контекста, в котором они используются. В этом классе определены методы как для работы с геометрическими векторами (например, вычисление векторного произведения) так и с точками (например, вычисление расстояния между двумя точками).
Во избежание путаницы, в рамках статьи мы не будем больше понимать под словом «вектор» абстрактный набор чисел, а будем так называть смещение.
Скалярное произведение
Единственная операция над векторами, на которой я хочу остановиться подробно (ввиду ее фундаментальности) — это скалярное произведение. Как ясно из названия, результат этого произведения — скаляр, и определяется он по следующей формуле:
У скалярного произведения есть две важные геометрические интерпретации.
Записав для него теорему косинусов и сократив выражение, придем к записи:
Поскольку длина ненулевого вектора по определению больше 0, то косинус угла определяет знак скалярного произведения и его равенство нулю. Получаем (считая, что угол от 0 до 360 градусов):
Из определения косинуса угла получаем:
Мы так же уже знаем из предыдущего пункта, что:
Выразив из второго выражения косинус угла, подставив в первое и домножив на ||w||, получаем результат:
Таким образом, скалярное произведение двух векторов равно длине проекции вектора v на вектор w, умноженное на длину вектора w. Часто встречающийся частный случай этой формулы — w имеет единичную длину и, следовательно, скалярное произведение вычисляет точную длину проекции.
Эта интерпретация очень важна, поскольку она показывает, что скалярное произведение вычисляет координаты точки (заданной вектором v, подразумевая смещение от начала координат) вдоль заданной оси. Самый простой пример:
Мы в дальнейшем воспользуемся этим при построении трансформации в систему координат камеры.
Системы координат
Этот раздел призван подготовить основу для введения матриц. Я, на примере мировой и локальной систем, покажу, по каким причинам используются несколько систем координат, а не одна, и о том, как описать одну систему координат относительно другой.
Представим, что у нас возникла необходимость в отрисовке сцены с двумя моделями, например:
Таким образом, одна система координат уже есть. Теперь необходимо подумать о том, в каком виде к нам приходят модели, которые мы будем использовать в сцене.
Для простоты будем считать, что модель описывается просто списком точек («вершин») из которых она состоит. Например, модель B состоит из трех точек, которые приходят нам в следующем формате:
v0 = (x0, y0)
v1 = (x1, y1)
v2 = (x2, y2)
На первый взгляд, было бы здорово, если бы они уже были описаны в нужной нам «мировой» системе! Представляете, вы добавляете модель на сцену, а она уже находится там, где нам и нужно. Для модели B это могло бы выглядеть вот так:
Решение этой проблемы — «локальная» система координат модели. Мы моделируем объект таким образом, чтобы его центр (или то, что можно условно за таковой принять) был расположен в начале координат. Затем мы программно ориентируем (перемещаем, поворачиваем и т.д.) локальную систему координат объекта в нужное нам положение в мировой системе. Возвращаясь к сцене выше, объект A (единичный квадрат, повернутый на 45 градусов по часовой стрелке) может быть смоделирован следующим образом:
Описание модели в этом случае будет выглядеть следующим образом:
Это — один из примеров, почему наличие нескольких систем координат упрощает жизнь разработчикам (и художникам!). Есть еще и другая причина — переход к другой системе координат может упростить необходимые вычисления.
Описание одной системы координат относительно другой
Не существует такого понятия как «абсолютные координаты». Описание чего-либо всегда происходит относительно какой-то системы координат. В том числе и описание другой системы координат.
Мы можем построить своеобразную иерархию систем координат в примере выше:
В нашем случае эта иерархия очень проста, но в реальных ситуациях она может иметь гораздо более сильное ветвление. Например, у локальной системы координат объекта могут быть дочерние системы отвечающие за положение той или иной части тела.
Нам не нужно описывать мировую систему координат относительно какой-либо другой, потому что мировая система — корень иерархии, нас не волнует где она расположена или как она ориентирована. Поэтому для ее описания мы используем стандартный базис:
Перевод координат точек из одной системы в другую
Координаты точки P в родительской системе координат (обозначим как Pparent) могут быть вычислены при помощи координат этой точки в дочерней системе (обозначим как Pchild) и ориентации этой дочерней системы относительно родительской (описаной с помощью начала координат Ochild и базисных векторов i’ и j’) следующим образом:
Снова вернемся к примеру сцены выше. Мы сориентировали локальную систему координат объекта A относительно мировой:
Для примера возьмем первую вершину v = (-0.5, 0.5) и вычислим ее координаты в мировой системе:
В верности результата можно убедиться, посмотрев на изображение выше.
Матрицы
Матрица размерности m x n — соответствующей размерности таблица чисел. Если количество столбцов в матрице равно количеству строк, то матрица называется квадратной. Например, матрица 3 x 3 выглядит следующим образом:
Перемножение матриц
Предположим, что у нас есть две матрицы: M (размерностью a x b) и N (размерностью c x d). Выражение R = M · N определено только в том случае, если количество столбцов в матрице M равно числу строк в матрице N (т.е. b = c). Размерность полученной матрицы будет равна a x d (т.е. количество строк равно кол-ву строк M а число столбцов — числу столбцов в N), а значение, находящееся на позиции ij, вычисляется как скалярное произведение i-й строки M на j-й столбец N:
Если результат умножения двух матриц M · N определен, то это вовсе не значит, что определено и умножение в обратную сторону — N · M (могут не совпадать кол-во строк и столбцов). В общем случае, операция умножения матриц так же не коммутативна: M · N ≠ N · M.
Единичная матрица — это матрица, которая не изменяет домноженную на нее другую матрицу (т.е. M · I = M) — своеобразный аналог единицы для обычных чисел:
Представление векторов в виде матриц
Мы так же можем представить вектор как матрицу. Есть два возможных способа это сделать, которые называют »вектор-строка» и »вектор-столбец». Как понятно из название, вектор-строка — это вектор, представленный в виде матрицы с одной строкой, а вектор-столбец — вектор, представленный в виде матрицы с одним столбцом.
Далее мы очень часто будем сталкиваться с операцией умножения матрицы на вектор (для чего — будет обьяснено в следущем разделе), и, забегая вперед, матрицы с которыми мы будем работать будут иметь размерность либо 3 x 3 либо 4 x 4.
Рассмотрим, каким способом мы можем умножить трехмерный вектор на матрицу 3 x 3 (аналогичные рассуждения применяются для других размерностей). Согласно определению, две матрицы могут быть перемножены, если количество столбцов первой матрицы равняется количеству строк второй. Таким образом, поскольку мы можем представить вектор и как матрицу 1 x 3 (вектор-строка) и как матрицу 3 x 1 (вектор-столбец), мы получаем два возможных варианта:
Как видно, мы получаем разный результат в каждом из случаев. Это может привести к случайным ошибкам, если API позволяет умножать вектор на матрицу с обоих сторон, поскольку, как мы увидим в дальнейшем, матрица трансформации подразумевает что вектор будет умножен на нее одним из двух способов. Так что, по моему мнению, в API лучше придерживаться только одного из двух вариантов. В рамках этих статей я буду использовать первый вариант — т.е. вектор умножается на матрицу слева. Если вы решили использовать другой порядок, то, для того чтобы получить корректные результаты, вам нужно будет транспонировать все матрицы, которые в дальнейшем встретятся в этой статье, а слово «строка» заменить на «столбец». Это так же влияет на порядок умножения матриц при наличии нескольких трансформаций (подробнее будет рассмотрено далее).
Из результата умножения так же видно, что матрица определенным образом (зависящем от значения ее элементов) изменяет вектор, который был на нее домножен. Это могут быть такие трансформации как поворот, масштабирование и другие.
Еще одно крайне важное свойство операции умножения матриц, которое нам в дальнейшем пригодится — дистрибутивность относительно сложения:
Геометрическая интерпретация
Как мы увидели в предыдущем разделе — матрица определенным образом трансформирует домноженный на нее вектор.
Еще раз вспомним, что любой вектор может быть представлен как линейная комбинация базисных векторов:
Умножим это выражение на матрицу:
Используя дистрибутивность относительно сложения, получим:
Мы уже видели подобное ранее, когда рассматривали, как перевести координаты точки из дочерней системы в родительскую, тогда это выглядело следующим образом (для трехмерного пространства):
Между этими двумя выражениями есть два различия — в первом выражении нет перемещения (Ochild, мы рассмотрим позже этот момент подробнее, когда будем говорить о линейных и афинных трансформациях), а вектора i’, j’ и k’ заменены на iM, jM и kM соответственно. Следовательно, iM, jM и kM — есть базисные вектора дочерней системы координат и мы переводим точку vchild(vx, vy, vy) из этой дочерней системы координат в родительскую (vtransformed = vparentM).
Процесс трансформации можно изобразить следующим образом на примере вращения против часовой стрелки (xy — изначальная, родительская система координат, x’y’ — дочерняя, полученная в результате трансформации):
Видно, что базисные вектора новой дочерней системы координат, полученной в результате умножения на матрицу M, совпадают со строками матрицы. Это и есть та самая геометрическая интерпретация, которую мы искали. Теперь, увидев матрицу трансформации, мы будем знать куда смотреть, чтобы понять что она делает — достаточно представить ее строки как базисные вектора новой системы координат. Трансформация, происходящая с системой координат, будет та же что и трансформация, происходящая с вектором, домноженным на эту матрицу.
Мы так же можем комбинировать трансформации, представленные в виде матриц, при помощи умножения их друг на друга:
Таким образом, матрицы представляют собой очень удобный инструмент для описания и комбинирования трансформаций.
Линейные трансформации
Для начала рассмотрим самые часто используемые линейные трансформации. Линейная трансформация — это трансформация, которая удовлетворяет двум свойствам:
Важное следствие — линейная трансформация не может содержать перемещения (это так же является причиной, по которой слагаемое Ochild отсутствовало в предыдущем разделе), поскольку, согласно второй формуле, 0 всегда отображается в 0.
Вращение
Рассмотрим вращение в двумерном пространстве. Это трансформация, которая поварачивает систему координат на заданный угол. Как мы уже знаем, нам достаточно вычислить новые координатные оси (полученные после вращения на заданный угол), и использовать их как строки матрицы трансформации. Результат легко получается из базовой геометрии:
Результат для вращения в трехмерном пространстве получается аналогичным образом, с той лишь разницей что мы вращаем плоскость составленную из двух координатных осей, и фиксируем третью (вокруг которой и происходит вращение). Например, матрица вращения вокруг оси x выглядит следующим образом:
Масштабирование
Мы можем изменить масштаб объекта относительно всех осей применив следующую матрицу:
Оси трансформированной системы координат будут направлены так же, как и у изначальной системы координат, но будут требовать в S раз большей длины для одной единицы измерения:
Пример:
Масштабирование с одинаковым коэффициентом относительно всех осей называется равномерным (uniform). Но мы так же можем произвести масштабирование с разными коэффициентами вдоль разных осей (nonuniform):
Сдвиг
Как понятно из названия, эта трансформация производит сдвиг вдоль координатной оси, оставляя остальные оси нетронутыми:
Соответственно, матрица сдвига оси y выглядит следующим образом:
Матрица для трехмерного пространства строится аналогичным образом. Например, вариант для сдвига оси x:
Несмотря на то, что эта трансформация используется очень редко, она пригодится нам в будущем, когда мы будем рассматривать афинные трансформации.
В нашем запасе уже есть приличное кол-во трансформаций, которые могут быть представлены как 3 x 3 матрица, но нам не хватает еще одной — перемещения. К несчастью, выразить перемещение в трехмерном пространстве при помощи 3 x 3 матрицы невозможно, поскольку перемещение не является линейной трансформацией. Решением этой проблемы являются однородные координаты, которые мы рассмотрим позже.
Центральная проекция
Наша итоговая цель — изобразить трехмерную сцену на двухмерном экране. Таким образом, мы должны тем или иным способом спроектировать нашу сцену на плоскость. Существует две самые часто используемые типы проекций — ортографическая и центральная (другое название — перспективная).
Когда человеческий глаз смотрит на трехмерную сцену, объекты, находящиеся дальше от него, становятся меньше в итоговом изображении, которое и видит человек — этот эффект называется перспективой. Ортографическая проекция игнорирует перспективу, что является полезным свойством при работе в различных САПР-системах (а так же в 2D играх). Центральная же проекция обладает этим свойством и потому добавляет значительную долю реалистичности. В этой статье мы будем рассматривать только ее.
В отличии от ортографической проекции, линии в перспективной проекции не параллельны друг другу, а пересекаются в точке, называемой центром проекции. Центром проекции выступает «глаз», которым мы смотрим на сцену — виртуальная камера:
Изображение формируется на плоскости, находящейся на заданном расстоянии от виртуальной камеры. Соответственно, чем больше расстояние от камеры, тем больше размер проекции:
Рассмотрим самый простой пример: камера расположена в начале координат, а плоскость проекции находится на расстоянии d от камеры. Нам известны координаты точки, которую мы хотим спроецировать: (x, y, z). Найдем координату xp проекции этой точки на плоскость:
На этой картинке видны два подобных треугольника — CDP и CBA (по трем углам):
Соответственно, отношения между сторонами сохраняются:
Получаем результат для x-координаты:
И, аналогично, для y-координаты:
Позже, нам необходимо будет использовать эту трансформацию для формирования спроецированного изображения. И тут возникает проблема — мы не можем представить деление на z-координату в трехмерном пространстве с помощью матрицы. Решением этой проблемы, так же как и в случае с матрицей перемещения, являются однородные (homogeneous) координаты.
Проективная геометрия и однородные координаты
Ниже мы будем рассматривать определения для двухмерных проективных пространств, поскольку их легче изобразить. Аналогичные рассуждения применяются и для трехмерных проективных пространств (мы в дальнейшем будем использовать именно их).
Определим луч в пространстве R 3 следующим образом: луч — это множество векторов вида kv (k — скаляр, v — ненулевой вектор, элемент пространства R 3 ). Т.е. вектор v задает направление луча:
К примеру, вектора (1, 1, 1) и (5, 5, 5) представляют один и тот же луч, а значит являются одной и той же «точкой» в проективной плоскости.
Таким образом мы и пришли к однородным координатам. Каждый элемент в проективной плоскости задается лучом из трех координат (x, y, w) (последняя координата называется w вместо z — общепринятое соглашение) — эти координаты называются однородными и определены с точностью до скаляра. Это означает, что мы можем домножать (или делить) на скаляр однородные координаты, и они все равно будут представлять ту же самую «точку» в проективной плоскости.
В дальнейшем мы будем использовать однородные координаты для представления афинных (содержащих перемещение) трансформаций и проецирования. Но до этого нужно решить еще один вопрос: каким образом представить в виде однородных координат уже данные нам вершины модели? Поскольку «дополнение» до однородных координат происходит за счет добавления третьей координаты w, вопрос сводится к тому, какое значение w нам необходимо использовать. Ответ на этот вопрос — любое не равное нулю. Разница в использовании различных значений w заключается в удобстве работы с ними.
Если эта плоскость — w = 1, нам достаточно будет поделить все координаты на w — в результате, мы получим единицу в w координате
Таким образом, мы выбираем значение w = 1, а значит наши входные данные в однородных координатах теперь будут выглядить следующим образом (разумеется, единица уже добавлена будет нами, а не будет находиться в самом описании модели):
v0 = (x0, y0, 1)
v1 = (x1, y1, 1)
v2 = (x2, y2, 1)
Теперь нужно рассмотреть особый случай при работе с w-координатой. А именно — ее равенство нулю. Выше мы говорили, что мы можем выбрать любое значение w, не равное нулю, для расширения до однородных координат. Мы не может взять w = 0, в частности, потому, что это не позволит перемещать эту точку (поскольку, как мы увидим дальше, перемещение происходит именно за счет значения в w координате). Так же, если у точки координата w — нулевая, мы можем рассматривать ее как точку «в бесконечности», поскольку при попытке вернуться на плоскость w = 1 мы получим деление на ноль:
И, хотя мы не можем использовать нулевое значение для вершин модели, мы можем использовать его для векторов! Это приведет к тому, что вектор не будет подвержен перемещениям при трансформациях — что имеет смысл, ведь вектор не описывает положение. Например, при трансформации нормали мы не можем перемещать ее, иначе получим неверный результат. Мы рассмотрим это подробнее, когда возникнет необходимость в трансформации векторов.
В связи с этим, часто пишут, что при w = 1 мы описываем точку, а при w = 0 — вектор.
Как я уже писал, мы использовали пример двухмерных проективных проективных пространств, но в действительно будем использовать трехмерные проективные пространства, а значит каждая точка будет описываться 4-мя координатами: (x, y, z, w).
Перемещение с использование однородных координат
Теперь у нас есть все необходимые инструменты для описания афинных трансформаций. Афинные трансформации — линейные трансформации с последующим смещением. Они могут быть описаны следующим образом:
Мы так же можем использовать 4 х 4 матрицы для описания афинных трансформаций, используя однородные координаты:
Рассмотрим результат умножения вектора, представленного в виде однородных координат, и умножения на 4 x 4 матрицу:
Как видно, отличие от трансформации, представленной в виде 3 x 3 матрица, состоит в наличии 4-й координаты, а так же новых слагаемых в каждой из координат вида vw · m3i. Используя это, и подразумевая w = 1, мы можем представить перемещение следующим образом:
Тут можно убедиться в правильности выбора w = 1. Для представления смещения dx мы используем слагаемое вида w · dx / w. Соответственно, последняя строка матрицы выглядит как (dx/w, dy/w, dz/w, 1). В случае w = 1 мы можем просто опустить знаменатель.
У этой матрицы так же есть геометрическая интерпретация. Вспомним матрицу сдвига, которую мы рассматривала ранее. Она имеет в точности такой же формат, разница лишь в том, что происходит сдвиг четвертой оси, таким образом сдвигая трехмерное подпространство расположенное в гиперплоскости w = 1 на соответствующие значения.
Описание виртуальной камеры
Виртуальная камера — это «глаз», которым мы смотрим на сцену. Прежде чем двигаться дальше, необходимо понять, каким образом можно описать положение камеры в пространстве, и какие параметры необходимы для формирования итогового изображения.
Параметры камеры задают усеченную пирамиду обзора (view frustum), которая определяет, какая часть сцены попадет в итоговое изображение:
Рассмотрим их по очереди:
При создании объекта камеры в API можно описывать все три оси — но это очень неудобно для пользователя. Чаще всего, пользователь знает в каком направлении должна смотреть камера, а так же приблизительную ориентацию вектора up (будем называть этот вектор up’). Используя эти два значения мы может построить верную систему координат для камеры по следующему алгоритму:
Важно не забыть нормализовать полученные в результате вектора — нам нужна ортонормированная система координат, поскольку ее удобнее использовать в дальнейшем
Поскольку сцена в дальнейшем будет спроецирована на плоскость проекции, то изображение на этой плоскости в дальнейшем будет отображено на экране компьютера. Поэтому важно, чтобы соотношение сторон у этой плоскости и окна (или его части), в котором будет показано изображение, совпадали. Этого можно добиться, дав пользователю API возможность задавать только один угол обзора, а второй вычислять в соответствии с соотношением сторон окна. Например, зная горизонтальный угол обзора, мы можем вычислить вертикальный следующим образом:
Когда мы обсуждали центральную проекцию, мы видели, что чем дальше находится плоскость проекции от камеры, тем больше размер полученного изображения — мы обозначали расстояние от камеры (считая, что она расположена в начале координат) вдоль оси z до плоскости проекции переменной d. Соответственно, следует решить вопрос, какое значение d нам необходимо выбрать. На самом деле, любое ненулевое. Несмотря на то, что размер изображения увеличивается при увеличении d, пропорционально увеличивается и та часть плоскости проекции, которая пересекается с пирамидой обзора, оставляя одним и тем же масштаб изображения относительно размеров этой части плоскости. В дальнейшем мы будем использовать d = 1 для удобства.
В итоге, для изменения масштаба изображения, нам необходимо менять углы обзора — это изменит размер части плоскости проекции, пересекающейся с пирамидой обзора, оставив размер проекции тем же.
Мы можем двигать и поворачивать камеру, меняя положение и ориентацию ее системы координат. Пример кода:
Графический конвейер
Теперь у нас есть весь необходимый фундамент, для того чтобы описать по шагам действия, которые необходимы для получения изображения трехмерной сцены на экране компьютера. Для простоты будем считать, что конвейер отрисовывает один объект за раз.
1. Переход в мировую систему координат
На этой стадии мы трансформируем вершины объекта из локальной системы координат в мировую.
Вспомним, что масштабирование происходит относительно начала координат. Если мы сначала сместим объект на желаемую точку, а потом применим к нему масштаб, мы получим неверный результат — положение объекта снова изменится после масштабирования. Простой пример:
Аналогичное правило применяется и относительно вращения — оно происходит относительно начала координат, а значит положение объекта изменится, если мы сначала произведем перемещение.
Таким образом, корректная последовательность в нашем случае выглядит следующим образом: масштабирование — поворот — смещение:
2. Переход в систему координат камеры
Переход в систему координат камеры, в частности, используется для упрощения дальнейших вычислений. В этой системе координат камера расположена в начале координат, а ее оси — вектора forward, right, up, которые мы рассматривали в предыдущем разделе.
Для реализации второго пункта мы воспользуемся свойством скалярного произведения, которое мы рассматривали в самом начале — скалярное произведение вычисляет длину проекции вдоль заданной оси. Таким образом, для того чтобы перевести точку A в систему координат камеры (с учетом того, что камера находится в начале координат — мы проделали это в первом пункте), нам достаточно взять ее скалярное произведение с векторами right, up, forward. Обозначим точку в мировой системе координат v, а эту же точку, переведенную в систему координат камеры, v’, тогда:
Эту операцию можно представить в виде матрицы:
Комбинируя эти две трансформации, мы получаем матрицу перехода в систему координат камеры:
Схематично, этот процесс можно изобразить следующим образом:
3. Переход в однородное пространство отсечения и нормализация координат
В результате предыдущего пункта, мы получили координаты вершин объекта в системе координат камеры. Следующее, что необходимо сделать, это произвести проекцию этих вершин на плоскость и «отсечь» лишние вершины. Вершина объекта отсекается, если она лежит вне пирамиды обзора (т.е. ее проекция не лежит на той части плоскости, которую охватывает пирамида обзора). Например, вершина v1 на рисунке ниже:
Обе эти задачи частично решает матрица проекции (projection matrix). «Частично» — потому что она не производит саму проекцию, но подготавливает w-координату вершины для этого. Из-за этого название «матрица проекции» звучит не очень подходяще (хотя это довольно распространенный термин), и я буду в дальнейшем называть ее матрицей отсечения (clip matrix), поскольку она так же производит отображение усеченной пирамиды обзора в однородное пространство отсечения (homogeneous clip space).
Но обо всем по порядку. Итак, первое — подготовка к проекции. Для этого мы кладем z-координату вершины в ее w-координату. Сама проекция (т.е. деление на z) будет происходить при дальнейшей нормализации w-координаты — т.е. при возвращении в пространство w = 1.
Следующее, что должна сделать эта матрица — это перевести координаты вершин объекта из пространства камеры в однородное пространство отсечения (homogeneous clip space). Это пространство, координаты не отсеченных вершин в котором таковы, что после применения проекции (т.е. деления на w-координату, поскольку в ней мы сохранили старую z-координату) координаты вершины нормализуются, т.е. удовлетворяют следующим условиям:
Неравенство для z-координаты может различаться для разных API. Например, в OpenGL оно соответствует тому, что выше. В Direct3D же z-координата отображается в интервал [0, 1]. Мы будем использовать принятый в OpenGL интервал, т.е. [-1, 1].
Подобные координаты называются нормализованными координатами устройства (normalized device coordinates или просто NDC).
Поскольку мы получаем NDC-координаты поделив координаты на w, вершины в пространстве отсечения удовлетворяют следующим условиям:
Таким образом, переведя вершины в это пространство, мы можем узнать, нужно ли отсекать вершину, просто проверив ее координаты на неравенства выше. Любая вершина, не удовлетворяющая этим условиям, должна быть отсечена. Само отсечение — тема для отдельной статьи, поскольку ее корректная реализация приводит к созданию новых вершин. Пока что, в качестве быстрого решения, мы не будем рисовать грань, если хотя бы одна из ее вершин отсечена. Конечно, результат получится не сильно аккуратным. Например, для модели машины это выглядит следующим образом:
Мы можем их вычислить, используя углы обзора:
Аналогично для top и bottom:
Таким образом, нам нужно отобразить интервал [left, right], [bottom, top], [near, far] в [-1; 1]. Найдем линейную функцию, которая дает такой результат для left и right:
Аналогично получается функция для bottom и top:
Выражение для z находится по-другому. Вместо того, чтобы рассматривать функцию вида az + b мы будем рассматривать функцию a · 1/z + b, потому что в дальнейшем, когда мы будем реализовывать буфер глубины, нам нужно будет линейно интерполировать величину, обратную z-координате. Мы не будем рассматривать этот вопрос сейчас, и просто примем это за данное. Получим:
Таким образом, мы можем записать, что нормализованные координаты получаются следующим образом из координат в пространстве камеры:
Мы не можем сразу применить эти формулы, потому что деление на z произойдет позже, при нормализации w-координаты. Мы можем воспользоваться этим, и вместо этого вычислить следующие значения (домножив на z), которые после деления на w в результате дадут нормализованные координаты:
Именно по этим формулам мы и производим трансформацию в пространство отсечения. Мы можем записать эту трансформацию в матричной форме (дополнительно вспомнив, что в w-координату мы кладем z):
Переход в систему координат экрана
Это отображение (viewport transformation) можно произвести при помощи простых формул:
Или, в матричной форме:
Мы оставим использование zndc до реализации буфера глубины.
В результате у нас есть координаты вершин на экране, которые мы и используем для отрисовки.
Реализация в коде
Пример использования SDL2 для реализации
В этом разделе я вкратце расскажу, как можно использовать SDL2 для реализации отрисовки.
Инициализация, создание текстуры
Отрисовка, отображение на экране
Рисование линий можно реализовать, например, по алгоритму Брезенхэма.
После использования SDL_UpdateTexture, необходимо скопировать текстуру в SDL_Renderer и отобразить на экране с помощью SDL_RenderPresent. Все вместе: