Скачиваний:
115
Добавлен:
23.02.2015
Размер:
702.98 Кб
Скачать
    1. Построение теней

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

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

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

Рассмотрим математические основы данного метода.

Пусть:

P– точка в трехмерном пространстве, которая отбрасывает тень.

L– положение источника света, который освещает данную точку.

S=a(L-P)-P- точка, в которую отбрасывает тень точкаP, гдеa– параметр.

Предположим, что тень падает на плоскость z=0.В этом случаеa=zp/(zl-zp). Следовательно,

xs = (xpzl -zlzp) / (zl -zp),

ys = (ypzl-ylzp) / (zl - zp),

zs = 0

Введем однородные координаты:

xs=xs'/ws'

ys=ys'/ws'

zs=0

ws'=zl-zp

Отсюда координатыSмогут быть получены с использованием умножения матриц следующим образом:

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

aP+bL, гдеaиb– скалярные параметры.

Следующая матрица задает плоскость через координаты ее нормали:

Точка, в которой луч, проведенный от источника света через данную точкуP, пересекает плоскостьG, определяется параметрамиaиb, удовлетворяющими следующему уравнению:

(aP+bL)G = 0

Отсюда получаем: a(PG) +b(LG) = 0. Этому уравнению удовлетворяют

a = (LG), b = -(PG)

Следовательно, координаты искомой точки S = (LG)P-(PG)L. Пользуясь ассоциативностью матричного произведения, получим

S =P[(LG)I -GL], гдеIединичная матрица.

Матрица (LG)I -GL используется для получения теней на произвольной плоскости.

Рассмотрим некоторые аспекты практической реализации данного метода с помощью OpenGL.

Предположим, что матрица floorShadowбыла ранее получена нами из формулы (LG)I -GL. Следующий код с ее помощью строит тени для заданной плоскости:

/* Делаем тени полупрозрачными с использованием смешения цветов(blending) */

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

glColor4f(0.0, 0.0, 0.0, 0.5);

glPushMatrix();

/* Проецируем тень */

glMultMatrixf((GLfloat *) floorShadow);

/* Визуализируем сцену в проекции */

RenderGeometry();

glPopMatrix();

glEnable(GL_LIGHTING);

glDisable(GL_BLEND);

/* Визуализируем сцену в обычном режиме */

RenderGeometry();

Матрица floorShadowможет быть получена из уравнения (*) с помощью следующей функции:

/* параметры: plane – коэффициенты уравнения плоскости

lightpos – координаты источника света

возвращает: matrix – результирующая матрица

*/

void shadowmatrix(GLfloat matrix[4][4], GLfloat plane[4], GLfloat lightpos[4])

{

GLfloat dot;

dot = plane[0] * lightpos[0] +

plane[1] * lightpos[1] +

plane[2] * lightpos[2] +

plane[3] * lightpos[3];

matrix[0][0] = dot - lightpos[0] * plane[0];

matrix[1][0] = 0.f - lightpos[0] * plane[1];

matrix[2][0] = 0.f - lightpos[0] * plane[2];

matrix[3][0] = 0.f - lightpos[0] * plane[3];

matrix[0][1] = 0.f - lightpos[1] * plane[0];

matrix[1][1] = dot - lightpos[1] * plane[1];

matrix[2][1] = 0.f - lightpos[1] * plane[2];

matrix[3][1] = 0.f - lightpos[1] * plane[3];

matrix[0][2] = 0.f - lightpos[2] * plane[0];

matrix[1][2] = 0.f - lightpos[2] * plane[1];

matrix[2][2] = dot - lightpos[2] * plane[2];

matrix[3][2] = 0.f - lightpos[2] * plane[3];

matrix[0][3] = 0.f - lightpos[3] * plane[0];

matrix[1][3] = 0.f - lightpos[3] * plane[1];

matrix[2][3] = 0.f - lightpos[3] * plane[2];

matrix[3][3] = dot - lightpos[3] * plane[3];

}

Заметим, что тени, построенные таким образом, имеют ряд недостатков.

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

  • В некоторых местах тени может наблюдаться эффект "двойного смешения" (reblending), т.е. темные пятна в тех участках, где спроецированные треугольники перекрывают друг друга.

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

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

Имеется решение первой и второй проблемы. Для этого используется буфер маски (см. п. 6.3)

Итак, задача – отсечь вывод геометрии (тени, в данном случае) по границе некоторой произвольной области и избежать "двойного смешения". Общий алгоритм решения с использованием буфера маски таков:

  1. Очищаем буфер маски значением 0

  2. Отображаем заданную область отсечения, устанавливая значения в буфере маски в 1

  3. Рисуем тени в тех областях, где в буфере маски установлены значения 1. Если тест проходит, устанавливаем в эти области значение 2.

Теперь рассмотрим эти этапы более подробно.

1.

/* очищаем буфер маски*/

glClearStencil(0x0);

/* включаем тест */

glEnable(GL_STENCIL_TEST);

2.

/* условие всегда выполнено и значение в буфере будет равно 1*/

glStencilFunc (GL_ALWAYS, 0x1, 0xffffffff);

/* в любом случае заменяем значение в буфере маски*/

glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);

/* выводим геометрию, по которой затем будет отсечена тень*/

RenderPlane();

3.

/* условие выполнено и тест дает истину только если значение в буфере маски равно 1 */

glStencilFunc (GL_EQUAL, 0x1, 0xffffffff);

/* значение в буфере равно 2,если тень уже выведена */

glStencilOp (GL_KEEP, GL_KEEP, GL_INCR);

/* выводим тени */

RenderShadow();

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

Соседние файлы в папке Программирование графических приложений