Главная » Программирование игр под Android » ОСВЕЩЕНИЕ В OPENGL ES – РАЗРАБОТКА ИГР ДЛЯ ОС ANDROID

0

 

Освещение в OpenGL ES – это полезная особенность, которая может придать 3D -играм приятный оттенок. Чтобы использовать подобную функциональность, сначала нам необходимо понять модель освещения OpenGL ES.

Как работает освещение

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

Интенсивность, с которой свет отражается объектом, зависит от множества факторов. Самый главный фактор, на который мы обращаем внимание, – это угол, с которым световой луч падает на поверхность. Чем ближе этот угол к прямому, тем больше интенсивность, с которой свет отразится от объекта. Это проиллюстрировано на рис. 11.1.

Как только световой луч упадет на поверхность, он отразится в двух различных направлениях. Большая часть света отразится рассеянно. Это означает, что отраженные световые лучи неравномерно рассыпаны в случайном порядке по поверхности объекта. Некоторые лучи отражаются зеркально. Это означает, что световые лучи отразятся назад так, будто они падают на идеальное зеркало. На рис. 11.2 показана разница между рассеянным и зеркальным отражениями.

Рис. 11.1. Чем ближе угол к прямому, тем больше интенсивность отраженного света

Рис. 11.2. Рассеянное и зеркальное отражения

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

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

OpenGL ES позволяет имитировать реальное поведение, определяя источники света и материалы объектов.

Источники освещения

Нас окружает множество разнообразных источников освещения. Солнце постоянно посылает свои фотоны. Мониторы излучают свет, окружающий нас приятным свечением по ночам. Лампочки и фары помогают нам избегать столкновений с различными предметами в темноте. OpenGL ES позволяет создавать четыре типа источников света.

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

Точечные источники света. Имеют позицию в пространстве и испускают свет во всех направлениях. Например, точечным источником света является лампочка.

Направленные источники освещения. Выражаются как направления в OpenGL ES. Предполагается, что они находятся бесконечно далеко. В идеале Солнце может являться таким источником. Мы можем предположить, что все световые лучи, исходящие от Солнца, попадают на Землю под одинаковым углом из-за расстояния между Землей и Солнцем.

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

Мы будем рассматривать только подсветку, а также точечные и направленные источники света. Светильники часто сложно использовать на ограниченных GPU Android-устройств из-за способа расчета освещения в OpenGL ES. Скоро вы поймете, почему это так.

Помимо позиции и направления источника света OpenGL ES позволяет определять цвет или интенсивность света. Эти характеристики выражаются с помощью цвета RGBA. Однако OpenGL ES требует определять четыре различных цвета для одного источника вместо одного.

Подсветка – интенсивность/цвет, вносящий вклад в создание затенения объекта. Объект будет освещен одинаково со всех сторон, независимо от его позиции или ориентации относительно источника света.

Рассеянный – интенсивность/цвет света, которым будет освещен объект после расчета рассеянного отражения. Грани объекта, которые не смотрят на источник света, не будут освещены, как и в реальной жизни.

Зеркальный – интенсивность/цвет, похожий на рассеянный цвет. Однако он влияет только на те точки объекта, которые имеют определенную ориентацию по отношению к источнику света и сенсору.

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

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

Материалы

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

Подсветка – цвет, который объединяется с фоновым цветом любого источника света на сцене.

Рассеянный – цвет, который объединяется с рассеянным цветом любого источника света.

Зеркальный – цвет, который объединяется с зеркальным цветом любого источника света. Он используется для создания бликов на поверхности объекта.

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

Рисунок 11.3 иллюстрирует первые три типа свойств материала/источника света: подсветка, рассеянный и зеркальный.

Рис. 11.3. Различные типы материалов/источников света: только подсветка (слева), только рассеянный (посередине), подсветка и рассеянный цвет с зеркальными бликами (справа)

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

Как OpenGL ES рассчитывает освещение: нормали вершин

Вы знаете, что интенсивность света, отраженного от объекта, зависит от его угла падения на объект. OpenGL ES использует этот факт для расчета освещения. Он применяет для этого нормали вершин, которые необходимо определять в коде так же, как и координаты текстур или цвета вершин. На рис. 11.4 показана сфера и ее нормали вершин.

Рис. 11.4. Сфера и ее нормали вершин

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

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

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

На практике

Теперь выполним все действия, необходимые для того, чтобы работать с освещением с помощью OpenGL ES. Создадим несколько небольших вспомогательных классов, которые немного упростят работу с источниками света, и поместим их в пакет com.badlogi с.androi dgames.framework.gl.

Разрешение и запрещение освещения

Как и для прочих состояний OpenGL ES, сначала следует подключить названную функциональность. Это можно сделать следующим образом:

После этого освещение будет применено ко всем отрисовываемым объектам. Чтобы получить результат, необходимо определить источники света и материалы, а также нормали вершин. Как только мы закончим отрисовывать все необходимые объекты, освещение можно отключить:

Определение источников освещения

OpenGL ES предоставляет 4 типа источников освещения: подсветка, точечный, направленный и светильник. Рассмотрим, как определить первые три. Чтобы светильники были эффективными и хорошо выглядели, каждая модель должна состоять из огромного количества треугольников. Для множества теперешних мобильных  устройств это невозможно.

OpenGL ES позволяет определять максимум 8 источников освещения одновременно, а также один глобальный источник подсветки. Каждый из 8 источников освещения имеет идентификатор, от GL10.GL LIGHT0 до GL10.GL LIGHT7. Если нужно изменить свойства одного из этих источников освещения, это можно сделать, определив соответствующий ему ID.

Разрешить использование источников освещения можно с помощью следующего синтаксиса:

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

Подсветка – это особый случай, поскольку у нее нет идентификатора. На сцене OpenGL ES может существовать только одна подсветка. Рассмотрим этот источник освещения подробнее.

Подсветка

Подсветка – это особый тип освещения. У него нет позиции или направления, только цвет, который применяется ко всем освещаемым объектам одинаково. OpenGL ES позволяет определять глобальную подсветку следующим образом:

Массив ambi entCol or содержит значения RGBA цвета подсветки, представленные как числа с плавающей точкой в диапазоне от 0 до 1. Метод gl LightModel fv принимает в качестве первого параметра константу, определяющую, что мы хотим установить цвет источника фонового освещения, массив чисел с плавающей точкой, который содержит цвет источника, и смещение для массива чисел с плавающей точкой, из которого метод начнет считывать значения RGBA. Поместим код, решающий эту задачу, в небольшой класс. Его код приведен в листинге 11.2.

Листинг 11.2. Класс AmbientLight.java. простая абстракция глобальной подсветки ODenGL ES

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

Точечные источники освещения

Точечные источники освещения имеют позицию, а также фоновые, рассеянные и зеркальные цвет/интенсивность (мы не рассматриваем эмиссивные цвет/интенсивность). Определить разные типы цветов можно следующим образом:

Первый параметр – это идентификатор источника света. В этом случае мы используем четвертый источник. Следующий параметр определяет атрибут, который мы хотим изменить. Третий параметр – это массив чисел с плавающей точкой, содержащий значения RGBA, а последний – это смещение в данном массиве. Определить позицию источника так же просто:

Мы снова определяем атрибут, который хотим изменить (в данном случае позицию), массив из четырех элементов содержит х-, у- и z-координату источника света в создаваемом мире. Обратите внимание, четвертый элемент массива должен быть равен единице, если источник света имеет позицию. Поместим это во вспомогательный класс. Его код содержится в листинге 11.3.

Листинг 11.3. Класс Point.Light.java, простая абстракция точечных источников света OpenGL ES

Наш вспомогательный класс содержит фоновые, рассеянные и зеркальные цветовые компоненты света, а также позицию (четвертый элемент равен единице). В дополнение мы храним последний идентификатор, используемый для данного источника, поэтому становится возможно создать метод disableO, который отключит свет при необходимости. Также у нас есть метод enableO, который принимает экземпляр класса GL10 и идентификатор источника света (например GL10.GL LIGHT6). Он разрешает использование освещения, устанавливает его атрибуты и сохраняет использованный идентификатор. Метод disableO просто запрещает использование освещения, используя член класса 1ast.Ligh.tId, установленный в методе enablе.

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

Направленные источники света

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

Мы можем преобразовать его в вектор:

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

Листинг 11.4. Класс Directi onLi ght.java, простая абстракция направленных источников света в OpenGL ES

Этот вспомогательный класс практически идентичен классу PointLight. Единственное различие заключается в том, что в массиве directi on четвертый элемент равен единице. Кроме того, вместо метода setPosition  появился метод setDirecti on . Он позволяет определять направление, например так: (-1; 0; 0), в этом случае свет будет исходить с правой стороны. Внутри метода все компоненты вектора меняют свой знак, таким образом мы преобразовываем направление к формату, ожидаемому OpenGL ES.

Определяем материалы

Материал определяется несколькими атрибутами. Как и в случае с любыми другими объектами OpenGL ES, материал – это состояние, которое будет активно до тех пор, пока мы не изменим его снова или пока не потеряется контекст OpenGL ES. Чтобы установить текущие атрибуты материалов, мы можем сделать следующее:

Как и обычно, нам необходимо определить фоновый, рассеянный и зеркальный RGBA-цвета. Это можно сделать так же, как и ранее, – с помощью массивов чисел с плавающей точкой, состоящих из четырех элементов.

Объединить эти действия в один вспомогательный класс очень просто. Результат вы можете увидеть в листинге 11.5.

Листинг 11.5. Класс Material Java, простая абстракция материалов OpenGL ES

Здесь тоже нет ничего удивительного. Мы просто сохраняем три компонента, описывающих материал, а также предоставляем функции для установки их значений и метод enabl е, которые передают их OpenGL ES.

У OpenGL ES есть еще один козырь в рукаве, когда речь идет о материалах. Обычно он вместо метода glMaterialfvO использует нечто, называемое цветом материала. Это означает, что вместо фонового и рассеянного цветов, определяемых методом glMateri al fv, OpenGL ES примет цвет вершин наших моделей в качестве фонового и рассеянного цветов материала. Чтобы разрешить использование этой особенности, необходимо просто вызвать ее:

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

Определяем нормали

Чтобы в OpenGL ES работало освещение, необходимо определить нормали вершин для каждой вершины модели. Нормаль вершины должна представлять собой единичный вектор, указывающий (обычно) в ту сторону, в которую повернута поверхность, к которой принадлежит вершина. На рис. 11.5 проиллюстрированы нормали вершин для нашего куба.

Рис. 11.5. Нормали вершин для каждой вершины нашего куба

Нормаль вершины – это еще один атрибут вершины, такой же, как позиция или цвет. Чтобы воспользоваться нормалями вершин, нам необходимо еще раз изменить класс Verti ces3. Для того чтобы указать OpenGL ES, где он может найти нормали для каждой вершины, мы будем использовать метод gl Normal PointerO, точно так же, как мы ранее применяли методы gl VertexPointer или gl Col or Pointer . В листинге 11.6 показана финальная версия класса Verti ces3.

Листинг 11.6. Класс Vertices3.Java, финальная версия, поддерживающая нормали

В классе появился новый член hasNormal.s, отслеживающий, имеют ли вершины нормали.

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

Как вы можете видеть, методы setVertices и setlndices остаются без изменений.

В только что продемонстрированном методе bind О используем те же приемы с буфером ByteBuffer, что и ранее, но в этот раз добавляем нормали с помощью метода gl Normal Pointer . Для вычисления смещения указателя нормали необходимо принять в расчет то, заданы ли координаты текстур и цвета.

Как вы можете видеть, метод draw также не изменился; все действо происходит в методе bind О.

Наконец, мы несколько изменяем метод unbindO. Запрещаем использование указателей нормали, если таковые имелись, соответственно очищая состояние OpenGL ES.

Применить измененный класс Verti ces3 так же просто, как и ранее. Рассмотрим небольшой пример:

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

Все, что нам остается, – создать экземпляр класса Verti ces3 и установить значения вершин. Довольно легко, не правда ли?

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

Собираем все воедино

Соберем все вместе. Нам необходимо нарисовать сцену, имеющую глобальную подсветку, точечные и направленные источники света. Они будут освещать куб, расположенный в начале координат. Нам также нужно вызвать метод gl uLookAt, . чтобы расположить камеру. На рис. 11.6 показан внешний вид нашего мира.

Как и для всех прочих примеров, создадим класс, который будет называться LightTest, как обычно расширяющий класс GLGame. Он будет возвращать экземпляр класса LightScreen с помощью метода getStartScreenO. Класс LightScreen наследует от класса GLScreen (листинг 11.7).

Рис. 11.6. Наша первая освещенная сцена

Листинг 11.7. Фрагменты класса LightTest.java. создание освещения с помощью OpenGL ES

Начнем с описания нескольких членов класса. Член angle хранит информацию о текущем угле поворота куба вокруг оси у. Член Verti ces3 хранит вершины модели куба, которые мы скоро определим. В дополнение у нас есть экземпляры классов AmbientLight, PointLight и Di rectional Light, а также экземпляр класса Material.

Далее следует конструктор. Здесь создаются вершины модели куба, а также загружается текстура ящика. Мы также инициализируем источники освещения и материалы. Цвет подсветки – светло-зеленый. Направленный источник – красного цвета и располагается в точке (3; 3; 0) нашего мира. Направленный источник света имеет синий рассеянный цвет, свет падает слева. Для материала используем значения по умолчанию (несколько фоновый, белый для рассеянной составляющей и черный для зеркальной).

В методе resume убеждаемся, что наша текстура (пере)загружается, если контекст будет потерян.

Метод createCube практически не изменился с предыдущих примеров. Однако в этот раз мы добавляем нормали для каждой вершины, что показано на рис. 11.5. Помимо этого все остается прежним.

В методе update просто увеличиваем угол поворота куба.

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

Далее мы устанавливаем матрицу проекций равной перспективной матрице проекций с помощью метода gl uPerspecti ve, а также используем метод gl uLookAt для модельно-видовой матрицы, благодаря чему камера работает так же, как на рис. 11.6.

Затем разрешаем использование освещения. К этому моменту еще не определен ни один источник света, поэтому мы задаем их в следующих нескольких строках с помощью вызова метода для источников света и материалов.

Как обычно, также разрешаем текстурирование и привязываем текстуру ящика. Наконец, вызываем метод gl RotatefC) для поворота куба и затем отрисовываем его вершины с помощью удачно размещенных вызовов экземпляра класса Verti ces3.

В конце метода мы отключаем точечные и направленные источники освещения (помните, подсветка – это глобальное состояние), а также текстурирование и тестирование глубины. Это все, что касается освещения в OpenGL ES.

Остальная часть класса пуста; нам не нужно производить какие-либо действия в случае паузы. На рис. 11.7 показан результат работы программы.

Рис. 11.7. Сцена, изображенная на рис. 11.6, отрисованная с помощью OpenGL ES

Несколько примечаний к освещению в OpenGL ES

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

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

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

При использовании метода gl Seal ef  для изменения размера модели ее нормали также будут масштабированы. Это плохо, поскольку OpenGL ES ожидает, что нормали будут иметь параметры в заданных единицах измерения. Чтобы обойти эту проблему, вы можете использовать команду glEnable(GL10.GL  NORMALIZE) или при некоторых обстоятельствах gl Enable(GL10 .GL RESCALE N0RMAL). Полагаю, следует использовать первую команду, поскольку применение второй имеет ограничения и подводные камни. Проблема заключается в том, что нормализация или повторное масштабирование нормалей требует большой вычислительной мощности. Лучшее решение с точки зрения производительности – не масштабировать освещенные объекты.

Источник: Mario Zechner / Марио Цехнер, «Программирование игр под Android», пер. Егор Сидорович, Евгений Зазноба, Издательство «Питер»

По теме:

  • Комментарии