ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.06.2020
Просмотров: 1711
Скачиваний: 4
66
Глава
7
Приемы
работы
с
OpenGL
В
этой
главе
мы
рассмотрим
,
как
с
помощью
OpenGL
создавать
некоторые
интересные
визуальные
эффекты
,
непосредственная
поддержка
которых
отсутствует
в
стандарте
библиотеки
.
7.1.
Устранение
ступенчатости
Начнем
с
задачи
устранения
ступенчатости
(
antialiasing
).
Эффект
ступенчатости
(
aliasing
)
возникает
в
результате
погрешностей
растеризации
примитивов
в
буфере
кадра
из
-
за
конечного
(
и
,
как
,
правило
,
небольшого
)
разрешения
буфера
.
Есть
несколько
подходов
к
решению
данной
проблемы
.
Например
,
можно
применять
фильтрацию
полученного
изображения
.
Также
этот
эффект
можно
устранять
на
этапе
растеризации
,
сглаживая
образ
каждого
примитива
.
Здесь
мы
рассмотрим
прием
,
позволяющий
устранять
подобные
артефакты
для
всей
сцены
целиком
.
Для
каждого
кадра
необходимо
нарисовать
сцену
несколько
раз
,
на
каждом
проходе
немного
смещая
камеру
относительно
начального
положения
.
Положения
камер
,
например
,
могут
образовывать
окружность
.
Если
сдвиг
камеры
относительно
мал
,
то
погрешности
дискретизации
проявятся
по
-
разному
,
и
,
усредняя
полученные
изображения
,
мы
получим
сглаженное
изображение
.
Проще
всего
сдвигать
положение
наблюдателя
,
но
перед
этим
нужно
вычислить
размер
сдвига
так
,
чтобы
приведенное
к
координатам
экрана
значение
не
превышало
,
скажем
,
половины
размера
пикселя
.
Все
полученные
изображения
сохраняем
в
буфере
-
накопителе
с
коэффициентом
1/n,
где
n –
число
проходов
для
каждого
кадра
.
Чем
больше
таких
проходов
–
тем
ниже
производительность
,
но
лучше
результат
.
for(i=0;i<samples_count;++i)
/*
обычно
samples_count
лежит
в
пределах
от
5
до
10 */
{
ShiftCamera(i);
/*
сдвигаем
камеру
*/
67
RenderScene();
if (i==0)
/*
на
первой
итерации
загружаем
изображение
*/
glAccum(GL_LOAD,1/(float)samples_count);
else
/*
добавляем
к
уже
существующему
*/
glAccum(GL_ACCUM,1/(float)samples_count);
}
/*
Пишем
то
,
что
получилось
,
назад
в
исходный
буфер
*/
glAccum(GL_RETURN,1.0);
Следует
отметить
,
что
устранение
ступенчатости
сразу
для
всей
сцены
,
как
правило
,
связано
с
серьезным
падением
производительности
визуализации
,
так
как
вся
сцена
рисуется
несколько
раз
.
Современные
ускорители
обычно
аппаратно
реализуют
другие
методы
,
основанные
на
так
называемом
ресамплинге
изображений
.
7.2.
Построение
теней
В
OpenGL
нет
встроенной
поддержки
построения
теней
на
уровне
базовых
команд
.
В
значительной
степени
это
объясняется
тем
,
что
существует
множество
алгоритмов
их
построения
,
которые
могут
быть
реализованы
через
функции
OpenGL.
Присутствие
теней
сильно
влияет
на
реалистичность
трехмерного
изображения
,
поэтому
рассмотрим
один
из
подходов
к
их
построению
.
Большинство
алгоритмов
,
предназначенных
для
построения
теней
,
используют
модифицированные
принципы
перспективной
проекции
.
Здесь
рассматривается
один
из
самых
простых
методов
.
С
его
помощью
можно
получать
тени
,
отбрасываемые
трехмерным
объектом
на
плоскость
.
Общий
подход
таков
:
для
всех
точек
объекта
находится
их
проекция
параллельно
вектору
,
соединяющему
данную
точку
и
точку
,
в
которой
находится
источник
света
,
на
некую
заданную
плоскость
.
Тем
самым
получаем
новый
объект
,
целиком
лежащий
в
заданной
плоскости
.
Этот
объект
и
является
тенью
исходного
.
Рассмотрим
математические
основы
данного
метода
.
Пусть
:
P
–
точка
в
трехмерном
пространстве
,
которая
отбрасывает
тень
.
L
–
положение
источника
света
,
который
освещает
данную
точку
.
68
S
=a(
L
-
P
)-
P
-
точка
,
в
которую
отбрасывает
тень
точка
P,
где
a –
параметр
.
Предположим
,
что
тень
падает
на
плоскость
z=0.
В
этом
случае
a=z
p
/(z
l
-z
p
).
Следовательно
,
x
s
= (x
p
z
l
- z
l
z
p
) / (z
l
- z
p
),
y
s
= (y
p
z
l
-y
l
z
p
) / (z
l
- z
p
),
z
s
= 0
Введем
однородные
координаты
:
x
s
=x
s
'/w
s
'
y
s
=y
s
'/w
s
'
z
s
=0
w
s
'=z
l
-z
p
Отсюда
координаты
S
могут
быть
получены
с
использованием
умножения
матриц
следующим
образом
:
Для
того
,
чтобы
алгоритм
мог
рассчитывать
тень
,
падающую
на
произвольную
плоскость
,
рассмотрим
произвольную
точку
на
линии
между
S
и
P
,
представленную
в
однородных
координатах
:
a
P
+b
L,
где
a
и
b –
скалярные
параметры
.
Следующая
матрица
задает
плоскость
через
координаты
ее
нормали
:
=
d
z
y
x
G
n
n
n
−
−
−
=
′
′
′
l
l
l
l
l
s
s
s
s
s
s
z
y
x
z
z
z
y
x
w
y
x
0
0
0
1
0
0
0
0
0
0
0
]
1
[
]
0
[
69
Точка
,
в
которой
луч
,
проведенный
от
источника
света
через
данную
точку
P,
пересекает
плоскость
G,
определяется
параметрами
a
и
b,
удовлетворяющими
следующему
уравнению
:
(a
P
+b
L)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
–
координаты
источника
света
70
возвращает
: 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),
т
.
е
.
темные
пятна
в
тех
участках
,
где
спроецированные
треугольники
перекрывают
друг
друга
.
С
увеличением
числа
поверхностей
сложность
алгоритма
резко
увеличивается
,
т
.
к
.
для
каждой
поверхности
нужно
заново