[Перевод] Learnopengl. Урок 4.4 — Отсечение граней

OGL3Отсечение граней
Попробуйте представить куб и подсчитать максимальное число его граней, которое вы можете увидеть с любого направления. Если ваше воображение не излишне живое, то, верней всего, вы придете к выводу, что это число 3. Из какой бы точки или с какого бы направления вы не смотрели на куб, вы никогда не сможете увидеть больше чем три грани. Так к чему же тратить вычислительные мощности на отрисовку оставшихся трех граней, если их не будет видно? Если бы мы могли отбросить их обработку каким-то образом, то сэкономили более чем половину выполнений фрагментного шейдера!

В передыдущих сериях
Часть 1. Начало
  1. OpenGL
  2. Создание окна
  3. Hello Window
  4. Hello Triangle
  5. Shaders
  6. Текстуры
  7. Трансформации
  8. Системы координат
  9. Камера

Часть 2. Базовое освещение
  1. Цвета
  2. Основы освещения
  3. Материалы
  4. Текстурные карты
  5. Источники света
  6. Несколько источников освещения

Часть 3. Загрузка 3D-моделей
  1. Библиотека Assimp
  2. Класс полигональной сетки Mesh
  3. Класс модели Model

Часть 4. Продвинутые возможности OpenGL
  1. Тест глубины
  2. Тест трафарета
  3. Смешивание цветов


Здесь сказано «более половины» поскольку в определенных ситуация видно только две, а то и одну грань куба. В таких случаях сэкономлено будет больше, чем 50%.


Идея хорошая, но появляется новая задача: нужно определить, какая же грань не видна из положения наблюдателя.

Представьте любую замкнутую объемную фигуру: каждая из её граней имеет две стороны. И одна из этих сторон будет обращена на наблюдателя, а другая от него. Что если мы будем выводить только обращенные на наблюдателя грани?

Именно в этом процессе и заключена суть процедуры отсечения граней. OpenGL проверяет все грани на ориентацию, допуская к рендеру только обращенные на наблюдателя, одновременно отсекая те, что обращены от него, что в итоге экономит множество вызовов фрагментного шейдера (выполнение которого весьма затратно!). Нам лишь только остается как-то передать OpenGL информацию о том, какие грани считать лицевыми, а какие — нет. OpenGL использует находчивое решение для определения ориентации граней: анализ порядка обхода в списке вершинных данных.

Порядок обхода


Когда мы задаем набор вершин для треугольника мы определяем их в определенном порядке обхода: либо по часовой стрелке (clockwise, CW), либо против часовой стрелки (counter-clockwise, CCW). Каждый треугольник состоит из трех вершин и задаем мы их в порядке обхода, определённом относительно центра треугольника.

mip3fo9pieur1pfxdblpjjty2ug.png


Как видно из схемы, сперва задается вершина 1, а затем у нас есть выбор: задать вершину 2 или 3, тем самым и определяя порядок обхода треугольника. Приведем код для наглядности:

float vertices[] = {
    // обход по часовой
    vertices[0], // вершина 1
    vertices[1], // вершина 2
    vertices[2], // вершина 3
    // обход против часовой
    vertices[0], // вершина 1
    vertices[2], // вершина 3
    vertices[1]  // вершина 2  
};


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

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

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

u8ub7kgf5_mh58wfg-5h607utog.png


В списке вершин оба треугольника мы определили в порядке против часовой стрелки (ближний треугольник задан как 1–2–3, и задний также задан как 1–2–3 (если бы наблюдатель смотрел на его лицевую сторону)). Однако, с указанной позиции порядок описания дальнего треугольника 1–2–3 наблюдается заданным по часовой стрелке. Несмотря на то, что вершины дальнего треугольника задавались с порядком обхода против часовой, при рендере он стал порядком по часовой стрелке. И такое поведение как раз и необходимо для успешного проведения отсечения невидимых граней.

Отбраковка граней


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

Включить функцию отсечения можно так:

glEnable(GL_CULL_FACE);


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

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

glCullFace(GL_FRONT);  


У функции три возможных параметра:

GL_BACK: Отбрасывает только нелицевые грани.
GL_FRONT: Отбрасывает только лицевые грани.
GL_FRONT_AND_BACK: Отбрасывает и те и другие грани.

Значение по умолчанию — GL_BACK.

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

glFrontFace(GL_CCW);  


Значение по умолчанию — GL_CCW, подразумевающее обход против часовой. Второе возможное значение — GL_CW, что устанавливает обход по часовой.

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

glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW); 


В результате видны останутся только задние грани куба:

jkmvmgc_a0ndw0jyrywqj9yr6oa.png


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

glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);  
glFrontFace(GL_СCW); 


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

Упражнения


Попробуйте переопределить данные в списке вершин так, что каждый треугольник теперь определен в порядке по часовой стрелке? Выведите сцену на экран при определении лицевых треугольников как описанных по часовой стрелке.

Решение тут.

© Habrahabr.ru