Ю.М. Баяковский, А.В. Игнатенко - Начальный курс OpenGL (DOC) (1124366), страница 11
Текст из файла (страница 11)
/* Делаем тени полупрозрачными с использованием
смешивания цветов(blending) */ glEnable (GL_BLEND) ;
glBlendFunc (GL_SRC_ALPHA,GL_ONE_MNUS_SRC_ALPHA); glDisable (GL_LIGHTING); glColor4f(0.0, 0.0, 0.0, 0.5); glPushMatrix ();
/* Проецируем тень */
glMultMatrixf ((GLfloat *) floorShadow);
/* Визуализируем сцену в проекции */
106 Глава 8. Графические алгоритмы на основе OFENGL
RenderGeometry (); glPopMatrix (); glEnable (GL_LIGHTING); glDisable(GL_BLEND);
Матрица floorShadow может быть получена из уравнения 8.1 с помощью следующей функции:
/* параметры:
plane — коэффициенты уравнения плоскости lightpos — координаты источника света возвращает: matrix — результирующая матрица
*/
void shadowmatrix( GLfloat matrix [4] [4] ,
GLfloat plane [4] , GLfloat lightpos [4])
{
GLfloat dot ;
dot = plane [0] * lightpos [0] +
plane[l] * lightpos [1] +
plane[2] * lightpos [2] +
plane [3] * lightpos [3];
atrix | ol [ |
atrix | 11 [ |
atrix | 21 [ |
atrix | 3] [ |
atrix | ol f |
atrix | 11 [ |
atrix | 21 [ |
atrix | 3] [ |
atrix | ol f |
atrix | 11 [ |
atrix | 21 [ |
atrix | 31 f |
0] = dot
0] = O.f
0] = O.f
01 = O.f
] = O.f dot O.f O.f
O.f O.f dot O.f
lightpos lightpos lightpos lightpos
lightpos lightpos lightpos lightpos
lightpos lightpos lightpos lightpos
* | plane | 0] |
* | plane | lj |
* | plane | 2J |
* | plane | 3J |
* | plane | o] |
* | plane | lj |
* | plane | 2J |
* | plane | 3J |
* | plane | o] |
* | plane | lj |
* | plane | 2J |
* | plane | 3| |
8.2. Построение теней
107
matrix [0][3] = O.f - lightpos [3] * plane[0]
matrix[l][3] = O.f - lightpos [3] * plane [1]
matrix [2][3] = O.f - lightpos [3] * plane[2]
}
matrix [3][3] = dot - lightpos [3] * plane [3]
Заметим, что тени, построенные таким образом, имеют ряд недостатков:
-
Описанный алгоритм предполагает, что плоскости бесконечны, и не отрезает тени по границе. Например, если некоторый объект отбрасывает тень на стол, она не будет отсекаться по границе, и, тем более, «заворачиваться» на боковую поверхность стола.
-
В некоторых местах теней может наблюдаться эффект «двойного смешивания» (rcblcnding), т.е. темные пятна в тех участках, где спроецированные треугольники перекрывают друг друга.
-
С увеличением числа поверхностей сложность алгоритма резко увеличивается, т.к. для каждой поверхности нужно заново строить всю сцену, даже если проблема отсечения теней по границе будет решена.
-
Тени обычно имеют размытые границы, а в приведенном алгоритме они всегда имеют резкие края. Частично избежать этого позволяет расчет теней из нескольких источников света, расположенных рядом и последующее смешивание результатов.
Имеется решение первой и второй проблемы. Для этого используется буфер маски (см. п. 7.2).
Итак, задача — отсечь вывод геометрии (тени) по границе некоторой произвольной области и избежать «двойного смеши-
108 Глава 8. Графические алгоритмы на основе OFENGL
вания». Общий алгоритм решения с использованием буфера маски таков:
-
очищаем буфер маски значением 0;
-
отображаем заданную область отсечения, устанавливая значения в буфере маски в 1;
-
рисуем тени в тех областях, где в буфере маски установлены значения; если тест проходит, устанавливаем в эти области значение 2.
Теперь рассмотрим эти этапы более подробно. 1.
/* очищаем буфер маски*/
glClearStencil(OxO)
glClear (GL_STENCIL_BUFFER_BIT);
/* включаем тест */
gl En able (GL_STENCIL_TEST);
2.
/* условие всегда выполнено и
значение в буфере будет равно 1*/ glStencilFunc (GL_ALWAYS, Oxl, Oxffffffff);
/* в любом случае заменяем значение в буфере, маски*/ glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
/* выводим геометрию, по которой
затем будет отсечена тень*/ RenderPlane();
3.
/* условие выполнено и тест дает истину только если значение в буфере маски равно 1 */
8.3. Зеркальные отражения
109
glStencilFunc (GL_EQUAL, 0x1, Oxffffffff);
/* значение в буфере равно 2, если тень уже выведена */ glStencilOp (GL_KEEP, GL_KEEP, GL_INCR);
/* выводим тени */ RenderShadow ();
Строго говоря, даже при применении маскирования остаются некоторые проблемы, связанные с работой z-буфера. В частности, некоторые участки теней могут стать невидимыми. Для решения этой проблемы можно немного приподнять тени над плоскостью с помощью модификации уравнения, описывающего плоскость. Описание других методов выходит за рамки данного пособия.
8.3. Зеркальные отражения
В этом разделе мы рассмотрим алгоритм построения отражений от плоских объектов. Такие отражения придают большую достоверность построенному изображению и их относительно легко реализовать.
Алгоритм использует интуитивное представление полной сцены с зеркалом как составленной из двух: «настоящей» и «виртуальной»— находящейся за зеркалом. Следовательно, процесс рисования отражений состоит из двух частей: 1) визуализации обычной сцены и 2) построения и визуализации виртуальной. Для каждого объекта «настоящей» сцены строится его отраженный двойник, который наблюдатель и увидит в зеркале.
Для иллюстрации рассмотрим комнату с зеркалом на стене. Комната и объекты, находящиеся в ней, выглядят в зеркале так, как если бы зеркало было окном, а за ним была бы еще одна такая же комната с тем же объектами, но симметрично отраженными относительно плоскости, проведенной через поверхность
110 Глава 8. Графические алгоритмы на основе OFENGL

Рис. 8.1. Зеркальное отражение
зеркала.
Упрощенный вариант алгоритма создания плоского отражения состоит из следующих шагов:
-
Рисуем сцену как обычно, но без объектов-зеркал.
-
Используя буфер маски, ограничиваем дальнейший вывод проекцией зеркала на экран.
-
Визуализируем сцену, отраженную относительно плоскости зеркала. При этом буфер маски позволит ограничить вывод формой проекции объекта-зеркала.
Эта последовательность действий позволит получить убедительный эффект отражения.
Рассмотрим этапы более подробно.
Сначала необходимо нарисовать сцену как обычно. Не будем останавливаться на этом этапе подробно. Заметим только, что, очищая буферы OpenGL непосредственно перед рисованием, нужно не забыть очистить буфер маски:
8.3. Зеркальные отражения
111
g 1С1 е а г (GL_COLOR_BUFFER_BEr | GL_DEPTH_BUFFTER_BrT | GL_STENCIL_BUFFER_Brr);
Во время визуализации сцены лучше не рисовать объекты, которые затем станут зеркальными.
На втором этапе необходимо ограничить дальнейший вывод проекцией зеркального объекта на экран.
Для этого настраиваем буфер маски и рисуем зеркало
glEnable (GL_STENCIL_TEST) ;
/* условие всегда выполнено и значение в буфере
будет равно 1*/ glStencilFunc(GL_ALWAYS, 1, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
Render Mirror Object ();
В результате мы получили:
-
в буфере кадра — корректно нарисованная сцена, за исключением области зеркала;
-
в области зеркала (там, где мы хотим видеть отражение) значение буфера маски равно 1.
На третьем этапе нужно нарисовать сцену, отраженную относительно плоскости зеркального объекта.
Сначала настраиваем матрицу отражения. Матрица отражения должна зеркально отражать всю геометрию относительно плоскости, в которой лежит объект-зеркало. Ее можно получить, например, с помощью такой функции (попробуйте получить эту матрицу самостоятельно в качестве упражнения):
void reflectionmatrix ( GLfloat reflection_matrix [4] [4] ,
GLfloat plane_point [3] , Glfloat plane_normal [3])
{
GLfloat* p;
112 Глава 8. Графические алгоритмы на основе OFENGL
GLfloat* v; float pv;
GLfloat* p = ( Glfloat *) plane_point ; Glfloat* v = ( Glfloat *) plane_normal; float pv = p[0]*v[0] + p[l]*v[l] + p[2]*v[2];
reflect reflect reflect reflect
reflect reflect reflect reflect
reflect reflect reflect reflect
on _mat r on _mat r on _mat r on _mat r
on _mat r on _mat r on _mat r on _mat r
on _mat r on _mat r on _mat r on matr
= 1 -= - 2
= - 2 * v[0
2 * v[0] * v[0] * v[0] * v[l];
* v
:2];
= 2 * pv * v [0] ;
= - 2 * v[0] * v[l];
= 1- 2 * v[l] * v[l];
= - 2 * v[l] * v[2];
= 2 * pv * v [ 1 ] ;
= - 2 * v[0] * v[2];
= - 2 * v[l] * v[2];
= 1 - 2 * v[2] * v[2] ;
= 2 * pv * v [ 2 ] ;
reflection _ matrix reflection _ matrix reflection _ matrix reflection matrix
= 0
= 0
= 0
= 1
}
Настраиваем буфер маски на рисование только в областях, где значение буфера равно 1:
/* условие выполнено и тест дает истину
только если значение в буфере маски равно 1 */ glStencilFunc (GL_EQUAL, Oxl, Oxffffffff);
/* ничего не. меняем в буфере */ glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
и рисуем сцену еще раз (без зеркальных объектов)
8Jh. Контрольные вопросы
113
glPushMatrix ();
glMultMatrixf (( float *) reflection_matrix ) ;
RenderScene ();
glPopMatrix () ;
Наконец, отключаем маскирование: g 1D i s a b 1 e (GL_STENCIL_TEST);
После этого можно опционально еще раз вывести зеркальный объект, например, с альфа-смешением — для создания эффекта замутнения зеркала и т.д.
Обратите внимание, что описанный метод корректно работает, только если за зеркальным объектом нет других объектов сцены. Поэтому существует несколько модификаций этого алгоритма, отличающихся последовательностью действий и имеющих разные ограничения на геометрию.
8.4. Контрольные вопросы
-
В результате чего возникает эффект ступенчатости изображения? Опишите алгоритм устранения ступенчатости.
-
Почему в OpenGL нет встроенной поддержки построения теней?
-
Кратко опишите предложенный метод визуализации зеркальных объектов. Почему он не работает, если за зеркалом находятся другие объекты сцены? Что будет отражаться в этом случае? Подумайте, как обойти это ограничение?
Глава 9.
Оптимизация программ
9.1. Организация приложения
На первый взгляд может показаться, что производительность графических приложений, основанных на OpenGL, определяется в первую очередь производительностью реализации самой библиотеки. Это верно, однако организация всего приложения также очень важна.