В этом разделе мы рассмотрим алгоритм построения отражений от плоских объектов. Такие отражения придают бóльшую достоверность построенному изображению и их относительно легко реализовать.
Алгоритм использует интуитивное представление полной сцены с зеркалом как составленной из двух: «настоящей» и «виртуальной» – находящейся за зеркалом. Следовательно, процесс рисования отражений состоит из двух частей: 1) визуализации обычной сцены и 2) построения и визуализации виртуальной. Для каждого объекта «настоящей» сцены строится его отраженный двойник, который наблюдатель и увидит в зеркале.
Рис. 9 Зеркальное отражение
Для иллюстрации рассмотрим комнату с зеркалом на стене. Комната и объекты, находящиеся в ней, выглядят в зеркале так, как если бы зеркало было окном, а за ним была бы еще одна такая же комната с тем же объектами, но симметрично отраженными относительно плоскости, проведенной через поверхность зеркала.
Упрощенный вариант алгоритма создания плоского отражения состоит из следующих шагов:
-
Рисуем сцену как обычно, но без объектов-зеркал.
-
Используя буфер маски, ограничиваем дальнейший вывод проекцией зеркала на экран.
-
Визуализируем сцену, отраженную относительно плоскости зеркала. При этом буфер маски позволит ограничить вывод формой проекции объекта-зеркала.
Эта последовательность действий позволит получить убедительный эффект отражения.
Рассмотрим этапы более подробно:
Сначала необходимо нарисовать сцену как обычно. Не будем останавливаться на этом этапе подробно. Заметим только, что, очищая буферы OpenGL непосредственно перед рисованием, нужно не забыть очистить буфер маски:
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|
GL_STENCIL_BUFFER_BIT);
Во время визуализации сцены лучше не рисовать объекты, которые затем станут зеркальными.
На втором этапе необходимо ограничить дальнейший вывод проекцией зеркального объекта на экран.
Для этого настраиваем буфер маски и рисуем зеркало
glEnable(GL_STENCIL_TEST);
/* условие всегда выполнено и значение в буфере будет равно 1*/
glStencilFunc(GL_ALWAYS, 1, 0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
RenderMirrorObject();
В результате мы получили:
-
в буфере кадра – корректно нарисованная сцена, за исключением области зеркала;
-
в области зеркала (там, где мы хотим видеть отражение) значение буфера маски равно 1.
На третьем этапе нужно нарисовать сцену, отраженную относительно плоскости зеркального объекта.
Сначала настраиваем матрицу отражения. Матрица отражения должна зеркально отражать всю геометрию относительно плоскости, в которой лежит объект-зеркало. Ее можно получить, например, с помощью такой функции (попробуйте получить эту матрицу самостоятельно в качестве упражнения):
void
reflectionmatrix(GLfloat reflection_matrix[4][4],
GLfloat plane_point[3],
Glfloat plane_normal[3])
{
GLfloat* p;
GLfloat* v;
float pv;
GLfloat* p = (Glfloat*)plane_point;
Glfloat* v = (Glfloat*)plane_normal;
float pv = p[0]*v[0]+p[1]*v[1]+p[2]*v[2];
reflection_matrix[0][0] = 1 - 2 * v[0] * v[0];
reflection_matrix[1][0] = - 2 * v[0] * v[1];
reflection_matrix[2][0] = - 2 * v[0] * v[2];
reflection_matrix[3][0] = 2 * pv * v[0];
reflection_matrix[0][1] = - 2 * v[0] * v[1];
reflection_matrix[1][1] = 1- 2 * v[1] * v[1];
reflection_matrix[2][1] = - 2 * v[1] * v[2];
reflection_matrix[3][1] = 2 * pv * v[1];
reflection_matrix[0][2] = - 2 * v[0] * v[2];
reflection_matrix[1][2] = - 2 * v[1] * v[2];
reflection_matrix[2][2] = 1 - 2 * v[2] * v[2];
reflection_matrix[3][2] = 2 * pv * v[2];
reflection_matrix[0][3] = 0;
reflection_matrix[1][3] = 0;
reflection_matrix[2][3] = 0;
reflection_matrix[3][3] = 1;
}
и рисуем сцену еще раз (без зеркальных объектов)
glPushMatrix();
glMultMatrixf((float *)reflection_matrix);
RenderScene();
GlPopMatrix();
Наконец, отключаем маскирование
glDisable(GL_STENCIL_TEST);
После этого можно опционально еще раз вывести зеркальный объект, например, с альфа-смешением – для создания эффекта замутнения зеркала и т.д.
Существует несколько модификаций этого алгоритма, отличающиеся последовательностью действий, ограничениями на геометрию и т.д.
Глава 8.Оптимизация программ 8.1.Организация приложения
На первый взгляд может показаться, что производительность приложений, основанных на OpenGL, определяется в первую очередь производительностью реализации самой библиотеки OpenGL. Это верно, однако организация всего приложения также очень важна.
8.1.1.Высокоуровневая оптимизация
Обычно от программы под OpenGL требуется визуализация высокого качества на интерактивных скоростях. Но, как правило, и то и другое сразу получить не удается. Следовательно, необходим поиск компромисса между качеством и производительностью. Существует множество различных подходов, но их подробное описание выходит за пределы этого пособия. Приведем лишь несколько примеров.
-
Можно отображать геометрию сцены с низким качеством во время анимации, а в моменты остановок показывать ее с наилучшим качеством. Во время интерактивного вращения (например, при нажатой клавише мыши) визуализировать модель с уменьшенным количеством примитивов. При рисовании статичного изображения отображать модель полностью.
-
Аналогично, объекты, которые располагаются далеко от наблюдателя, могут быть представлены моделями пониженной сложности. Это значительно снизит нагрузку на все ступени конвейера OpenGL. Объекты, которые находятся полностью вне поля видимости, могут быть эффективно отсечены без передачи на конвейер OpenGL с помощью проверки попадания ограничивающих их простых объемов (сфер или кубов) в пирамиду зрения.
-
Во время анимации можно отключить псевдотонирование (dithering), плавную заливку, текстуры. Опять-таки включать все это во время демонстрации статичных изображений. Этот подход особенно эффективен для систем без аппаратной поддержки OpenGL.
8.1.2.Низкоуровневая оптимизация
Объекты, отображаемые с помощью OpenGL, хранятся в некоторых структурах данных. Одни типы таких структур более эффективны в использовании, чем другие, что определяет скорость визуализации.
Желательно, чтобы использовались структуры данных, которые могут быть быстро и эффективно переданы на конвейер OpenGL. Например, если мы хотим отобразить массив треугольников, то использование указателя на этот массив значительно более эффективно, чем передача его OpenGL поэлементно.
Пример:
Предположим, что мы пишем приложение, которое реализует рисование карты местности. Один из компонентов базы данных – список городов с их шириной, долготой и названием. Соответствующая структура данных может быть такой:
struct city
{
float latitute, longitude; /* положение города */
char *name; /* название */
int large_flag; /* 0 = маленький, 1 = большой */
};
Список городов может храниться как массив таких структур. Допустим, мы пишем функцию, которая рисует города на карте в виде точек разного размера с подписями:
void draw_cities( int n, struct city citylist[] )
{
int i;
for (i=0; i < n; i++)
{
if (citylist[i].large_flag)
glPointSize( 4.0 );
else
glPointSize( 2.0 );
glBegin( GL_POINTS );
glVertex2f( citylist[i].longitude,
citylist[i].latitude );
glEnd();
/* рисуем название города */
DrawText(citylist[i].longitude,
citylist[i].latitude,
citylist[i].name);
}
}
Это реализация неудачна по следующим причинам:
-
glPointSize вызывается для каждой итерации цикла.
-
между glBegin и glEnd рисуется только одна точка.
-
вершины определяются в неоптимальном формате.
Ниже приведено более рациональное решение:
void draw_cities( int n, struct city citylist[] )
{
int i;
/* сначала рисуем маленькие точки */
glPointSize( 2.0 );
glBegin( GL_POINTS );
for (i=0; i < n ;i++)
{
if (citylist[i].large_flag==0) {
glVertex2f( citylist[i].longitude,
citylist[i].latitude );
}
}
glEnd();
/* большие точки рисуем во вторую очередь */
glPointSize( 4.0 );
glBegin( GL_POINTS );
for (i=0; i < n ;i++)
{
if (citylist[i].large_flag==1)
{
glVertex2f( citylist[i].longitude,
citylist[i].latitude );
}
}
glEnd();
/* затем рисуем названия городов */
for (i=0; i < n ;i++)
{
DrawText(citylist[i].longitude,
citylist[i].latitude,
citylist[i].name);
}
}
В такой реализации мы вызываем glPointSize дважды и увеличиваем число вершин между glBegin и glEnd.
Однако остаются еще пути для оптимизации. Если мы поменяем наши структуры данных, то можем еще повысить эффективность рисования точек. Например:
struct city_list
{
int num_cities; /* число городов в списке */
float *position;/* координаты города */
char **name; /* указатель на названия городов */
float size; /* размер точки, обозначающей город */
};
Теперь города разных размеров хранятся в разных списках. Положения точек хранятся отдельно в динамическом массиве. После реорганизации мы исключаем необходимость в условном операторе внутри glBegin/glEnd и получаем возможность использовать массивы вершин для оптимизации. В результате наша функция выглядит следующим образом:
void draw_cities( struct city_list *list )
{
int i;
/* рисуем точки */
glPointSize( list->size );
glVertexPointer( 2, GL_FLOAT, 0,
list->num_cities,
list->position );
glDrawArrays( GL_POINTS, 0, list->num_cities );
/* рисуем название города */
for (i=0; i < list->num_cities ;i++)
{
DrawText(citylist[i].longitude,
citylist[i].latitude
citylist[i].name);
}
}
Достарыңызбен бөлісу: |