Сегодня мы будем работать с освещением. Это очень важная тема, умение работать с основами освещения пригодится Вам в большинстве Ваших собственных проектов.
Здесь может возникнуть вопрос зачем что-то менять, если по умолчанию в OpenGL уже выставлен источник света и мы и без того видим наши объекты.
Дело в том, что выставленный по умолчанию свет нельзя редактировать: изменять его цвет, насыщенность, положение и другие параметры. Но самое главное это то, что он всего один. Если Вам, к примеру, понадобится еще свет от ламп или фонарика, то стандартное освещение уже не поможет. [spoiler] Другими словами, стандартное освещение отлично подходит для базовой настройки приложения и никуда не годится в реальных условиях.
Теперь немного теории.
В OpenGL версии 1.0, которую мы сейчас используем, на сцене могут присутствовать до восьми источников света. Включение и отключение их производится при помощи команд glEnable() и glDisable() которые принимают в качестве параметра описанный источник(GL_LIGHTi, где i - число от 0 до 7).
Допустим, мы включили источник света, но этого все еще не достаточно. Теперь нам необходимо задать его свойства. В OpenGL это делается при помощи команды glLight().
Параметр light указывает на источник света который мы хотим использовать.
Теперь нужно определиться с типом источника света. В OpenGL их существует три: точечный, направленный и прожектор.
Остановимся на каждом из них подробнее.
источник направленного света: расположен в бесконечности и имеет выделенное направление освещения.
точечный источник света: расположен в конкретной точке пространства и светит равномерно во всех направлениях. Для него можно задать эффект затухания света с расстоянием.
прожектор: является частным случаем точечного источника, но свет от него распространяется только внутри ограничивающего конуса, а не по всем направлениям.
Задание компонент излучения
Для источника света можно задать фоновую, рассеянную и зеркальную компоненты излучения.
Параметр pname команды glLightfv
Значение по умолчанию
Краткий комментарий
(0.0, 0.0, 0.0, 1.0)
цвет фонового излучения источника света
(1.0, 1.0, 1.0, 1.0) или(0.0, 0.0, 0.0, 1.0)
цвет рассеянного излучения источника света (значение по умолчанию для GL_LIGHT0 - белый, для остальных - черный)
(1.0, 1.0, 1.0, 1.0) или (0.0, 0.0, 0.0, 1.0)
цвет зеркального излучения источника света (значение по умолчанию для GL_LIGHT0 - белый, для остальных - черный)
Остальные параметры являются специфическими для каждого типа источников света, описанных ниже.
Источники направленного света
Источники света такого типа находится в бесконечности и свет от него распространяется в заданном направлении. Идеально подходит для создания равномерного освещения.
Хорошим примером источника направленного света может служить Солнце. У источника направленного света, кроме компонент излучения, можно задать только направление.
Параметр pname команды glLightfv
Значение по умолчанию
Краткий комментарий
(0.0, 0.0, 1.0, 0.0)
(x, y, z, w) направление источника направленного света
Первые три компоненты (x, y, z) задают вектор направления, а компонента w всегда равна нулю (иначе источник превратится в точечный).
Точечные источники света
Точечный источник света расположен в некоторой точке пространства и излучает во всех направлениях. Т.к. расстояние между источником и освещаемой точкой конечно, то можно задать закон убывания интенсивности излучения с расстоянием.
Стандартные средства OpenGL позволяют задавать такой закон в виде обратно-квадратичной функции от расстояния:
Т.е. для точечного источника света, кроме свойств излучения, можно задать ещё четыре параметра:
Параметр pname команды glLightfv
Значение по умолчанию
Краткий комментарий
(0.0, 0.0, 1.0, 0.0)
позиция источника света (по умолчанию источник света направленный)
1.0
постоянная k_const в функции затухания f(d)
0.0
коэффициент k_linear при линейном члене в функции затухания f(d)
0.0
коэффициент k_quadratic при квадрате расстояния в функции затухания f(d)
Как видно из таблицы, по умолчанию, интенсивность света не убывает с расстоянием.
Позиция источника света (в случае направленного источника - направление) задается в текущей модельной системе координат.
нулевой источник света будет расположен в точке (1.0, 1.0, 1.0) во внешней (мировой) системе координат.
Прожекторы
Одной из разновидностей точечного источника является прожектор. Для него применимы все параметры, что и для точечного источника, но кроме того прожектор позволяет ограничить распространение света конусом.
Для этого конуса можно задать коэффициент убывания интенсивности, в зависимости от угла между осью конуса и лучом распространения света.
Параметры, специфические для прожектора:
Параметр pname команды glLightfv
Значение по умолчанию
Краткий комментарий
(0.0, 0.0, -1.0)
(x, y, z) - направление прожектора (ось ограничивающего конуса)
180.0
угол между осью и стороной конуса (он же половина угла при вершине)
0.0
экспонента убывания интенсивности
С теорией мы на этом закончим и начнем писать код.
Для начала нам понадобится проект из прошлого урока. Мы не будем создавать новый, это нудно и не интересно создавать все сначала. Мы просто модернизируем старое. И так, что у нас есть?
А есть у нас три самописных класса: OpenGLRenderer, Object3D и Background.
На данном этапе нужно определить в каком из них нужно прописывать освещение.
Background отпадает сразу. В данный момент он не должен меняться от освещения. Object3D тоже отпадает. Хотя именно для его отображения мы работаем с освещением, но прописывать свет в этом классе пока нет смысла.
Решено. Остается OpenGLRenderer. Он управляет нашими классами, будет управлять и светом.
Открываем его и редактируем функцию onSurfaceChanged() до следующего вида:
/*http://esate.ru, Freaky_Brainstorm(Brain Freaky)*/
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//Создаем экземпляр классов Background и Object3D
background = new Background(width, height);
object3D = new Object3D();
//Переменная aspect будет хранить в себе значение соотношения сторон
aspect = (float)width / (float)height;
//Устанавливаем область отрисовки
gl.glViewport(0, 0, width, height);
//Выбираем матрицу проекции
gl.glMatrixMode(GL10.GL_PROJECTION);
//Умножаем ее на единичную
gl.glLoadIdentity();
//Выбираем матрицу вида
gl.glMatrixMode(GL10.GL_MODELVIEW);
//Ее тоже умножаем на единичную
gl.glLoadIdentity();
//Это все делается для того, чтобы при изменении размера экрана
//при повороте устройства, у нас ничего не "сломалось"
//Устанавливаем параметры света и материала
float[] matDiffuse = {1.0f, 1.0f, 1.0f, 1.0f};
float[] matSpecular = {1.0f, 1.0f, 1.0f, 1.0f};
float[] lightDiffuse = {1.0f, 1.0f, 1.0f, 1.0f};
float[] lightPosition = {0.0f, 0.0f, -10f, 1.0f};
float lightShininess = 60.0f;
gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_DIFFUSE, FloatBuffer.wrap(matDiffuse));
gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_SPECULAR, FloatBuffer.wrap(matSpecular));
gl.glMaterialf(GL10.GL_FRONT, GL10.GL_SHININESS, lightShininess);
//Указываем, что параметры необходимо применить
//к источнику света GL_LIGHT0
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, FloatBuffer.wrap(lightDiffuse));
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, FloatBuffer.wrap(lightPosition));
//Включаем источник света GL_LIGHT0
gl.glEnable(GL10.GL_LIGHT0);
//Записываем в переменные значения высоты и ширины экрана
this.width = (float) width; this.height = (float)height;
}
Параметры источника света следует указывать(в нашем случае) именно в этом методе т.к. их нужно пересчитывать только при изменении параметров устройства вывода(дисплея).
Хорошо. Наш следующий шаг - редактирование метода onDrawFrame():
/*http://esate.ru, Freaky_Brainstorm(Brain Freaky)*/
@Override
public void onDrawFrame(GL10 gl) {
//Очищаем буферы цвета и глубины
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//Выбираем матрицу проекции
gl.glMatrixMode(GL10.GL_PROJECTION);
//Умножаем на единичную
gl.glLoadIdentity();
//Устанавливаем ортогональную проекцию
gl.glOrthof(0.0f, this.width, 0.0f, this.height, -1.0f, 1.0f);
//Отключаем тест глубины
gl.glDisable(GL10.GL_DEPTH_TEST);
//Отключаем запись в буфер глубины
gl.glDepthMask(false);
//Отключаем освещение
gl.glDisable(GL10.GL_LIGHTING);
//Выбираем матрицу вида
gl.glMatrixMode(GL10.GL_MODELVIEW);
//Умножаем на единичную
gl.glLoadIdentity();
//Начало зоны 2D
//Задаем цвет фона
gl.glColor4f(.0f, .0f, .0f, 7.0f);
//Запускаем прорисовку
background.Draw(gl);
//Конец зоны 2D
//Включаем запись в буфер глубины
gl.glDepthMask(true);
//Включаем тест глубины
gl.glEnable(GL10.GL_DEPTH_TEST);
//Включаем освещение
gl.glEnable(GL10.GL_LIGHTING);
//Выбираем матрицу проекции
gl.glMatrixMode(GL10.GL_PROJECTION);
//Умножаем на единичную матрицу
gl.glLoadIdentity();
//Устанавливаем область видимости. Об этом ниже
GLU.gluPerspective(gl, 45.0f, aspect, 0.1f, 1000.0f);
//Выбираем матрицу вида
gl.glMatrixMode(GL10.GL_MODELVIEW);
//Умножаем на единичную матрицу
gl.glLoadIdentity();
//Устанавливаем точку и направление камеры
GLU.gluLookAt(gl, 0.5f, 0.5f, 5.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f);
//Вращаем объект относительно всех координат
gl.glRotatef(angle, 1.0f, 1.0f, 1.0f);
//Начало зоны 3D
//Задаем цвет объекта
gl.glColor4f(.1f, .1f, .1f, 1.0f);
//Запускаем прорисовку
object3D.Draw(gl);
//Конец зоны 3D
//Умножаем на единичную матрицу
gl.glLoadIdentity();
//Увеличиваем угол вращения
angle++;
}
Здесь мы только добавили четыре строки кода. Отключили освещение для фона и включили его для объекта. Добавили вращение объекта.
Плюс мы отредактировали цвет фона и позицию камеры. Смените эти параметры у себя.
Теперь Вы можете запустить проект и увидите, что наше освещение работает, но на плоскости оно смотрится не интересно.
Преобразуем наш класс Object3D. Для этого нужно изменить переменную vertices следующим образом:
Хорошо то, что освещение работает, но плохо то, что работает не корректно.
Мы вращаем матрицу и источник света вращается вместе с объектом, но это нам подходит сейчас. Мы сможем рассмотреть как освещается объект со всех сторон.
Самое плохое это то, что мы не видим граней, но это легко исправить с помощью нормалей.
у меня в одном месте полигоны лагают. Если выставить lightPosition={-10.0f,-5.0f,0.0f,1.0f}; , то есть источник света будет слева и немного повыше куба, то будет видно как моргает полигон. Как это исправить? и связано ли это с тем, что карты нормали у вас немного неправильны заданы?
Думаю, что это связано именно с нормалями и вершинами. Больше даже с вершинами. Лучше всего рисовать стороны куба припомощи glPushMatrix() и glPopMatrix().
В сети есть примеры, недавно натыкался. Если появится время, то напишу новый пост, используя именно такой подход.
Лучше всего рисовать стороны куба припомощи glPushMatrix() и glPopMatrix().
В сети есть примеры, недавно натыкался.
Если появится время, то напишу новый пост, используя именно такой подход.
Благодарю за фидбек.