Повышение точности аппроксимации сферической поверхности
12-ти гранная аппроксимация сферы не слишком точна и ее изображение напоминает сферу только при небольшом размере. Есть простой путь для увеличения точности аппроксимации. Допустим, имеется вписанный в сферу икосаэдр. Разобьем каждую грань икосаэдра на 4 равносторонних треугольника. Новые вершины будут лежать внутри сферы, поэтому их надо "приподнять" на поверхность (умножить на такое число, чтобы их радиус-векторы стали равны 1). Этот процесс разбиения можно продолжать до достижения требуемой точности. На рис. 4.2 показаны поверхности из 20, 80 и 320 треугольников.
Рис. 4.2. Последовательное разбиение для улучшения качества полигональной аппроксимации сферической поверхности.
Первое разбиение икосаэдра для получения 80-ти гранной аппроксимации сферы можно выполнить с помощью фрагмента 4.2а.
void draw_triangle(double* v1, double* v2, double* v3) { glBegin(GL_POLYGON); glNormal3dv(v1); glVertex3dv(v1); glNormal3dv(v2); glVertex3dv(v2); glNormal3dv(v3); glVertex3dv(v3); glEnd(); }
void subdivide(double* v1, double* v2, double* v3) { double v12[3], v23[3], v31[3];
for (int i = 0; i < 3; i++) { v12[i] = (v1[i]+v2[i])/2; v23[i] = (v2[i]+v3[i])/2; v31[i] = (v3[i]+v1[i])/2; } normalize(v12); normalize(v23); normalize(v31); draw_triangle(v1, v12, v31); draw_triangle(v2, v23, v12); draw_triangle(v3, v31, v23); draw_triangle(v12, v23, v31); }
... for (int i = 0; i < 20; i++) subdivide(&vdata[tindices[i][0]][0], &vdata[tindices[i][1]][0], &vdata[tindices[i][2]][0]);
Фрагмент программы 4.2а. Первое разбиение граней икосаэдра для аппроксимации сферы.
Немного изменив функцию разбиения из фрагмента 4.2а, можно получить функцию, выполняющую рекурсивное деление треугольников до достижения заданной глубины деления (см. фрагмент 4.2б). Разбиение прекращается при достижении глубины depth=0. После этого выполняется рисование треугольников. При depth=1, выполняется один шаг разбиения и т.д.
void subdivide(double* v1, double* v2, double* v3, long depth) { double v12[3], v23[3], v31[3];
if (depth == 0) { draw_triangle(v1, v2, v3); return; } for (int i = 0; i < 3; i++) { v12[i] = (v1[i]+v2[i])/2; v23[i] = (v2[i]+v3[i])/2; v31[i] = (v3[i]+v1[i])/2; } normalize(v12); normalize(v23); normalize(v31); subdivide(v1, v12, v31, depth-1); subdivide(v2, v23, v12, depth-1); subdivide(v3, v31, v23, depth-1); subdivide(v12, v23, v31, depth-1); } Фрагмент программы 4.2б. Рекурсивное разбиение треугольной грани полигональной сетки на сферической поверхности.
Алгоритм разбиения треугольной грани произвольной поверхности Прием рекурсивного разбиения, приведенный во фрагменте 4.2б, можно обобщить на случай произвольной гладкой поверхности. Рекурсия должна заканчиваться или при достижении определенной глубины, или если удовлетворяется некоторое условие о кривизне поверхности (части поверхности с большой кривизной лучше выглядят при более мелком разбиении). Будем полагать, что произвольная гладкая поверхность параметризована по двум параметрам u[0] и u[1]. Для описания поверхности в программе должны быть реализованы две функции: void surf(const double u[2], double vertex[3], double normal[3]); double curv(const double u[2]); Функция surf() получает параметры u[] и возвращает координаты вершины и единичный вектор нормали для точки поверхности с этими параметрами. Функция curv() вычисляет кривизну поверхности в точке, заданной параметрами u[]. Во фрагменте 4.3 приведена рекурсивная функция для разбиения треугольной грани произвольной поверхности на треугольники до тех пор, пока не будет достигнута заданная глубина или кривизна во всех вершинах грани не станет меньше некоторого граничного значения.
void subdivide(double u1[2], double u2[2], double u3[2], double cutoff, long depth, long max_depth) { double v1[3], v2[3], v3[3], n1[3], n2[3], n3[3]; double u12[2], u23[2], u31[2];
if (depth == max_depth || (curv(u1) < cutoff && curv(u2) < cutoff && curv(u3) < cutoff)) { surf(u1, v1, n1); surf(u2, v2, n2); surf(u3, v3, n3); glBegin(GL_POLYGON); glNormal3dv(n1); glVertex3dv(v1); glNormal3dv(n2); glVertex3dv(v2); glNormal3dv(n3); glVertex3dv(v3); glEnd();
return; }
for (int i = 0; i < 2; i++) { u12[i] = (u1[i] + u2[i])/2.0; u23[i] = (u2[i] + u3[i])/2.0; u31[i] = (u3[i] + u1[i])/2.0; }
subdivide(u1, u12, u31, cutoff, depth+1, max_depth); subdivide(u2, u23, u12, cutoff, depth+1, max_depth); subdivide(u3, u31, u23, cutoff, depth+1, max_depth); subdivide(u12, u23, u31, cutoff, depth+1, max_depth); } Фрагмент программы 4.3. Функция разбиения треугольной грани произвольной поверхности.
Плоскости отсечения Кроме 6-ти плоскостей отсечения, образующих видимый объем (левая, правая, нижняя, верхняя, ближняя и дальняя), OpenGL позволяет задать до 6-ти дополнительных плоскостей отсечения. Эти плоскости накладывают дополнительные ограничения на видимый объем (рис. 4.3), что может быть полезно для удаления посторонних объектов сцены, например, при построении сечения объекта. Каждая плоскость задается коэффициентами общего уравнения: . При модельных преобразованиях плоскости отсечения преобразуются автоматически. Отсеченный видимый объем принимается равным пересечению видимого объема и полупространств, задаваемых дополнительными плоскостями отсечения. При выполнении отсечения многоугольников, попадающих в видимый объем частично, OpenGL автоматически рассчитывает координаты новых вершин. Рис. 4.3. Дополнительные плоскости отсечения и видимый объем.
Для задания плоскости отсечения применяется функция: void glClipPlane(GLenum plane, const double* equation); Указатель equation указывает на массив из 4-х коэффициентов уравнения плоскости . Параметр plane равен GL_CLIP_PLANEi, где i – это целое число от 0 до 5, обозначающее одну из 6-ти возможных плоскостей отсечения. При выполнении отсечения будут оставлены только те вершины , для которых выполняется условие: , где M – это видовая матрица на момент вызова функции glClipPlane(). Каждую из плоскостей отсечения надо включить с помощью вызова: glEnable(GL_CLIP_PLANE i); Для выключения плоскости надо вызвать: glDisable(GL_CLIP_PLANE i); В программе 4.4 демонстрируется применение плоскостей отсечения для рисования каркасного сферического сегмента объемом в четверть сферы (рис. 4.4). Рис. 4.4. Сегмент каркасной сферы, построенный с помощью плоскостей отсечения.
#include <windows.h> #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h>
void CALLBACK resize(int width, int height); void CALLBACK display();
void main() { auxInitDisplayMode(AUX_SINGLE | AUX_RGBA); auxInitPosition(0, 0, 400, 400);
auxInitWindow("Лекция 4, Программа 4.4"); auxReshapeFunc(resize); auxMainLoop(display); }
void CALLBACK resize(int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (float)width/(float)height, 1.0, 20.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); }
void CALLBACK display() { double eqn1[4] = {0.0, 1.0, 0.0, 0.0}; // y < 0 double eqn2[4] = {1.0, 0.0, 0.0, 0.0}; // x < 0
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, 1.0, 1.0); glPushMatrix(); glTranslated(0.0, 0.0, -5.0);
glClipPlane(GL_CLIP_PLANE0, eqn1); glEnable(GL_CLIP_PLANE0); glClipPlane(GL_CLIP_PLANE1, eqn2); glEnable(GL_CLIP_PLANE1);
glRotated(90.0, 1.0, 0.0, 0.0); auxWireSphere(1.0); glPopMatrix(); glFlush(); } Программа 4.4. Использование плоскостей отсечения для построения каркасного сферического сегмента. Сводка результатов Произвольную гладкую поверхность можно аппроксимировать с помощью сетки из многоугольников, например, из треугольников. Чтобы полигональная аппроксимация поверхности выглядела похожей на требуемую поверхность, в OpenGL необходимо задавать нормали в вершинах полигональной сетки. Направление нормалей учитывается при вычислении количества света, отраженного поверхностью в окрестности вершины по направлению наблюдателя. В лекции приведен пример построения икосаэдральной аппроксимации сферы. Для улучшения вида аппроксимированной поверхности надо уменьшать размеры граней в областях поверхности с большой кривизной. В лекции приведен алгоритм рекурсивного разбиения треугольной сетки, аппроксимирующей произвольную параметрически заданную поверхность. В OpenGL можно наложить ограничения на форму видимого объема. Для этого используются дополнительные плоскости отсечения.
Упражнения Упражнение 1 На основе фрагментов программы 4.1 разработайте программу для рисования икосаэдра в четырех режимах (режимы циклически переключаются пробелом): 1) Все грани закрашиваются одним цветом, нормали вершин не задаются (т.е. нормали всех вершинам равны по умолчанию (0,0,1)). 2) Каждая грань закрашивается своим цветом, нормали не задаются (т.е. все нормали равны (0,0,1)). 3) Все грани закрашиваются одним цветом, нормали задаются как перпендикуляры к плоским граням икосаэдра.
4) Все грани закрашиваются одним цветом, нормали задаются как перпендикуляры к поверхности сферы, на которой лежат вершины икосаэдра. Упражнение 2 На основе фрагментов программы 4.2 напишите программу, которая будет рисовать три варианта икосаэдральной аппроксимации сферы (как на рис. 4.2). Затем попробуйте выполнить еще один шаг разбиения для получения 1280 граней. Упражнение 3 Примените общий алгоритм разбиения из п.3.3 для построения эллиптического параболоида . Параметрами поверхности считайте декартовы координаты x и y. В качестве первого приближения можете выбрать пирамиду с вершинами в точках (0, 0, 0), (-3, -3, 9), (-3, 3, 9), (3, 3, 9), (3, -3, 9). Из функции subdivide() (фрагмент программы 4.3) можно исключить параметр cutoff и не применять проверку кривизны поверхности. Упражнение 4 Выполните программу 4.4. Измените ее так, чтобы с помощью одной плоскости отсечения программа изображала нижнюю полусферу. Затем добавьте в свою программу функцию фоновой обработки и с ее помощью сделайте так, чтобы плоскость отсечения вращалась (функцией glRotated() перед glClipPlane()) и отсекала полусферу под разными углами.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|