3 апреля 2012 г.

OpenGL ES 1. Освещение и материалы

Освещение очень важно для правильного отображения трёхмерных объектов. Например, без освещения сфера будет выглядеть как круг, а цилиндр как прямоугольник. Чтобы использовать освещение нужно обязательно включить его командой glEnable(GL10.GL_LIGHTING), иначе все источники света будут игнорированы. В OpenGL ES допускается одновременно использовать восемь источников света, которые пронумерованы специальными параметрами состояния GL10.GL_LIGHT0, GL10.GL_LIGHT1, GL10.GL_LIGHT2, и.т.д. до GL10.GL_LIGHT7. Чтобы включить источник света нужно вызвать команду glEnable(номер источника света). Например, чтобы включить первый источник света вводим glEnable(GL10.GL_LIGHT1). По умолчанию включен источник света GL10.GL_LIGHT0. Аналогично можно выключить источник света командой glDisable(номер источника света). Каждый источник света имеет свои атрибуты, которые настраиваются независимо от других источников.

Важнейшим из атрибутов источника являются его координаты в пространстве, которые устанавливаются следующей командой:
gl.glLightfv(номер источника, GL10.GL_POSITION, буфер координат источника);
Продемонстрируем это на примере. Например, нам требуется установить источник c номером  GL10.GL_LIGHT0 в точку с координатами X=1, Y=1, Z=1. Сначала подготовим буфер для координат источника света длиной в 16 байт:
ByteBuffer bb = ByteBuffer.allocateDirect(16);
bb.order(ByteOrder.nativeOrder());
positionlightBuffer = bb.asFloatBuffer();

Почему 16 байт ? Для источника света достаточно трёх координат, каждая из которых занимает 4 байта. Однако, у источника света есть четвертая координата W, которую мы рассматривать не будем и примем равной нулю. Далее создаём массив с координатами и записываем его в буфер:
float [] positionlightArray={1,1,1};
positionlightBuffer.position(0);
positionlightBuffer.put(positionlightArray);
positionlightBuffer.position(0);
Буфер координат источника света передаем в OpenGL:
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, positionlightBuffer);

Свет, излучаемый источником, содержит в себе три составляющие-фоновую (ambient), рассеянную (diffuse), зеркальную (specular). 
Фоновая составляющая освещает трёхмерные объекты одинаково со всех сторон. Фоновый свет отражается во все стороны с одинаковой интенсивностью. Яркость фонового излучения не зависит от ориентации освещаемой поверхности и не зависит от положения глаза наблюдателя и как следствие, одинакова для всех поверхностей.
Рассеянный свет, падая на поверхность, отражается от неё во всех направлениях. Яркость отраженного рассеянного света зависит от ориентации поверхности по отношению к источнику света. В зависимости от направления нормали поверхность может казаться более или менее яркой. Однако, яркость рассеянного света не зависит от положения глаза наблюдателя, т.к. отражение рассеянной составляющей происходит с одинаковой интенсивностью во все стороны.
Зеркальная составляющая отражается от поверхности преимущественно в одном направлении, которое определяется законом отражения. Поэтому её яркость зависит как от ориентации поверхности по отношению к источнику света, так и от положения глаза наблюдателя. 

Для каждой составляющей света задаются отдельно четыре компоненты: красная, зелёная, синяя и альфа. Каждая компонента спектра может иметь собственную яркость. Спектральные компоненты источника света передаются в OpenGL при помощи команды:
gl.glLightfv(номер источника света, составляющая света, буфер цветов);
Приведу пример:
// Создадим массивы и буферы для хранения цветов источника света
// Запишем спектральные компоненты в буферы


// цвета фоновой составляющей света

float [] ambientlightArray={0.5f, 0.5f, 0.5f, 1};
FloatBuffer ambientlightBuffer;
ByteBuffer b1 = ByteBuffer.allocateDirect(16);
b1.order(ByteOrder.nativeOrder());
ambientlightBuffer = b1.asFloatBuffer();
ambientlightBuffer.position(0);
ambientlightBuffer.put(ambientlightArray);
ambientlightBuffer.position(0);

// цвета рассеянной составляющей света
float [] diffuselightArray={0.8f, 0.8f, 0.8f, 1};
FloatBuffer diffuselightBuffer;
ByteBuffer b2 = ByteBuffer.allocateDirect(16);
b2.order(ByteOrder.nativeOrder());
diffuselightBuffer = b2.asFloatBuffer();
diffuselightBuffer.position(0);
diffuselightBuffer.put(diffuselightArray);
diffuselightBuffer.position(0);

// цвета зеркальной составляющей света
float [] specularlightArray={0.8f, 0.8f, 0.8f, 1};
FloatBuffer specularlightBuffer; 
ByteBuffer b3 = ByteBuffer.allocateDirect(4 * 4);
b3.order(ByteOrder.nativeOrder());
specularlightBuffer = b3.asFloatBuffer();
specularlightBuffer.position(0);
specularlightBuffer.put(specularlightArray);
specularlightBuffer.position(0); 

Теперь передадим цвета в OpenGL:
// цвета фоновой составляющей света
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientlightBuffer);
// цвета рассеянной составляющей света 
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuselightBuffer);
// цвета зеркальной составляющей света 
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularlightBuffer);


Цвета источника света установлены. Их нужно рассматривать как способность источника света излучать фоновую, рассеянную и зеркальную составляющую света какой-либо цветовой гаммы. Способность отражать ту или иную составляющую света относится к свойствам материала, из которого сделана поверхность. Способность излучать и способность отражать разные свойства. Например, источник света может излучать рассеянную составляющую зеленого цвета с максимальной яркостью, равной 1. Однако, поверхность может быть сделана из такого материала, который вообще не отражает зелёный цвет. В этом случае, зелёный цвет не дойдёт до нашего глаза. Поэтому в OpenGL вводится понятие материала и задаются ряд его свойств связанных с отражением света, которые аналогичны свойствам источника света. Задаются они командой glMaterialfv. Формат команды такой:
gl.glMaterialfv(освещаемая поверхность, составляющая света, буфер);
Первый параметр  принимает следующие значения:
GL10.GL_FRONT_AND_BACK - команда применяется для обратной и лицевой стороны поверхности,
GL10.GL_FRONT - команда применяется только для лицевой стороны поверхности,
GL10.GL_BACK -  команда применяется только для обратной стороны поверхности.
Второй параметр определяет составляющую света:
GL10.GL_AMBIENT - команда применяется для отраженного поверхностью фонового света,
GL10.GL_DIFFUSE - команда применяется для отраженного поверхностью рассеянного света,
GL10.GL_SPECULAR - команда применяется для отраженного поверхностью зеркального света.
Третий параметр - это буфер в котором содержатся компоненты цветовой гаммы, т.е.яркость красной, зелёной и синей компоненты отраженного света, а также альфа-компонента, т.е степень непрозрачности материала.  Применение команды glMaterialfv покажу на примере:
// массивы для хранения цветов материала
// цвета фонового освещения

float [] ambientmaterial={0.2f, 0.2f, 0.2f, 1f};
// цвета рассеянного света
float [] diffusematerial={0.8f, 0.8f, 0.8f, 1f};
// цвета зеркального света
float [] specularmaterial={0.5f, 0.5f, 0.5f,1f};
// соответствующие им буферы цветов материала
private FloatBuffer ambientmaterialBuffer,diffusematerialBuffer,specularmaterialBuffer;

ByteBuffer b1 = ByteBuffer.allocateDirect(16);
b1.order(ByteOrder.nativeOrder());
ambientmaterialBuffer = b1.asFloatBuffer();
ambientmaterialBuffer.put(ambientmaterial);
ambientmaterialBuffer.position(0);

ByteBuffer b2 = ByteBuffer.allocateDirect(16);
b2.order(ByteOrder.nativeOrder());
diffusematerialBuffer = b2.asFloatBuffer();
diffusematerialBuffer.put(diffusematerial);
diffusematerialBuffer.position(0);

ByteBuffer b3 = ByteBuffer.allocateDirect(16);
b3.order(ByteOrder.nativeOrder());
specularmaterialBuffer = b3.asFloatBuffer();
specularmaterialBuffer.put(specularmaterial);
specularmaterialBuffer.position(0);

// передаем данные о цветах материала в OpenGL
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientmaterialBuffer);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffusematerialBuffer);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specularmaterialBuffer);


С зеркальной составляющей тесно связано понятие блеска. Чем больше блеск, тем меньше размер отраженного "зайчика" и тем больше его яркость. Показатель блеска может меняться от 0 до 128. Блеск действует только на зеркальную составляющую и управляется командой:
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, shine);
где shine - число от 1 до 128.

Если включено освещение собственные цвета вершин игнорируются. Чтобы в режиме освещения включить цвета вершин нужно выполнить команду:
gl.glEnable(GL10.GL_COLOR_MATERIAL);
Однако, при этом исчезнет эффект освещения. Совмещение собственных цветов вершин и цветов отраженного материалом света в OpenGL ES не выполняется. Если нужно вернуться от отображения цветов вершин в режим освещения выполняем команду:
gl.glDisable(GL10.GL_COLOR_MATERIAL);
Мы можем выбрать модель освещения, при которой освещена будет только лицевая сторона полигона, либо другую модель, в которой освещены обе стороны - лицевая и обратная. Установка модели производится командами:
// освещаются обе стороны полигона
gl.glLightModelx(GL10.GL_LIGHT_MODEL_TWO_SIDE, GL10.GL_TRUE);
// освещается только лицевая сторона
gl.glLightModelx(GL10.GL_LIGHT_MODEL_TWO_SIDE, GL10.GL_FALSE);

4 комментария:

  1. Спасибо, очень полезная статья. Скажите пожалуйста, возможно ли организовать взаимодействие цвета и света? То есть возможно ли задать цвета вершин, чтобы при этом они реагировали на освещение?

    ОтветитьУдалить
    Ответы
    1. В OpenGL ES 1.0 совместить собственные цвета вершин и освещение невозможно. В OpenGL ES 2.0 это делается в шейдерах.

      Удалить
  2. Если при включенном освещении цвета вершин игнорируются, то как же задать цвет объекта?

    ОтветитьУдалить
    Ответы
    1. В этом случае цвет объекта можно задать, наложив на него текстуру. OpenGL ES 1.0 устарел. Лучше переходите на изучение OpenGL ES 2.0 последовательно по урокам. В шейдерах комбинировать цвета можно как угодно.

      Удалить