В 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)
Итак, задача – отсечь вывод геометрии (тени, в данном случае) по границе некоторой произвольной области и избежать "двойного смешения". Общий алгоритм решения с использованием буфера маски таков:
-
Очищаем буфер маски значением 0
-
Отображаем заданную область отсечения, устанавливая значения в буфере маски в 1
-
Рисуем тени в тех областях, где в буфере маски установлены значения 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 помощью модификации уравнения, описывающего плоскость. Описание других методов выходит за рамки данного пособия.
Достарыңызбен бөлісу: |