Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

RedBook

.pdf
Скачиваний:
18
Добавлен:
11.06.2015
Размер:
7.43 Mб
Скачать

GL_MAX_CLIENT_ATTRIB_STACK_DEPTH команды glGetIntegerv().) Сохранение данных в полный стек или их извлечение из пустого приведет к генерации ошибки.

void glPushAttrib (GLbitfield mask); void glPopAttrib (void);

glPushAttrib() сохраняет все атрибуты, указанные битами в параметре mask, помещая их в стек атрибутов. glPopAttrib() восстанавливает значения тех переменных состояния, которые были сохранены командой glPushAttrib(). В таблице 2-6 перечислены возможные значения параметра mask команды glPushAttrib(). Каждое значения представляет определенный набор переменных состояния. (Эти значения могут комбинироваться с помощью логического ИЛИ.) Например, GL_LIGHTING_BIT объединяет все переменные, связанные с освещением. К ним относятся текущий цвет материала, все параметры света, список включенных источников света, и направления для тех из них, которым они присущи. Когда вызывается команда glPopAttrib() все эти переменные восстанавливаются в сохраненные значения.

Специальная маска GL_ALL_ATTRIB_BITS используется для сохранения и восстановления всех переменных состояния во всех группах атрибутов.

Таблица 2-6. Группы атрибутов

Битовая маска

Группа атрибутов

GL_ACCUM_BUFFER_BIT

аккумулятор

GL_ALL_ATTRIB_BITS

---

GL_COLOR_BUFFER_BIT

цветовой буфер

GL_CURRENT_BIT

текущие

GL_DEPTH_BUFFER_BIT

буфер глубины

GL_ENABLE_BIT

включенные

GL_EVAL_BIT

вычислители

GL_FOG_BIT

туман

GL_HINT_BIT

комплексные установки

GL_LIGHTING_BIT

освещение

GL_LINE_BIT

линия

GL_LIST_BIT

список

GL_PIXEL_MODE_BIT

пиксели

GL_POINT_BIT

точка

GL_POLYGON_BIT

полигон

GL_POLYGON_STIPPLE_BIT

шаблон полигона

GL_SCISSOR_BIT

отрез

GL_STENCIL_BUFFER_BIT

буфер трафарета

GL_TEXTURE_BIT

текстура

GL_TRANSFORM_BIT

трансформация

GL_VIEWPORT_BIT

порт просмотра

void glPushClientAttrib (GLbitfield mask); void glPopClientAttrib (void);

glPushClientAttrib() сохраняет все атрибуты, указанные битами в параметре mask, помещая их в стек атрибутов на клиентской стороне. glPopClientAttrib() восстанавливает значения тех переменных состояния, которые были сохранены последним вызовом glPushClientAttrib(). В таблице 2-7 перечислены все возможные битовые маски, которые могут быть совмещены в параметре mask команды glPushClientAttrib() для сохранения любой комбинации клиентских атрибутов. Маска GL_ALL_CLIENT_ATTRIB_BITS означает сохранение всех возможных групп атрибутов. Существуют две клиентские группы атрибутов (обратного режима и выбора), которые не могут быть сохранены или восстановлены с помощью механизма стека.Таблица 2-7.

Клиентские группы атрибутов

Битовая маска

Группа атрибутов

GL_CLIENT_PIXEL_STORE_BIT

режимы хранения пикселей

GL_CLIENT_VERTEX_ARRAY_BIT

вершинные массивы

не может быть сохранена или восстановлена

обратного режима

не может быть сохранена или восстановлена

выбор

 

 

2.9 Советы по построению полигональных моделей и поверхностей

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

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

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

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

Постоянство ориентации важно для отсечения нелицевых граней и двухстороннего освещения. Попытайтесь следовать этому правилу с самого начала, поскольку проблемы, связанные с его несоблюдением, очень сложно исправлять в дальнейшем. (Если вы используете glScale*() для отражения геометрии относительно какой-либо оси симметрии, вы можете изменить ориентацию с помощью glFrontFace() для сохранения ее постоянства.)

При делении поверхности отслеживайте любые нетреугольные полигоны. Три вершины треугольника гарантированно лежат в одной плоскости; любой полигон

счетырьмя и более вершинами не обязательно является плоским. При просмотре

сопределенных точек ребра такого полигона могут пересекаться, и OpenGL может неправильно его отобразить.

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

относительно плоских частей поверхности и большее количество маленьких полигонов для сильно изогнутых ее областей.

Для высококачественных изображений неплохой идеей является разделение контура поверхности на большее число фрагментов, чем разделение внутренних частей. Это сложнее сделать, если предполагается, что поверхность будет вращаться, поскольку в этом случае ее видимый контур будет изменяться. Контурные ребра можно выявить с помощью векторов нормалей в любой точке

такого ребра вектор нормали перпендикулярен вектору из этой точки к точке обзора, то есть их скалярное произведение равно 0. Ваш алгоритм разделения

поверхности может быть написан с учетом возможности выбора более частого дробления там, где произведение этих вектором приближается к 0.

Попытайтесь избежать T-пересечений в ваших моделях (рисунок 2-17). Нет гарантии, что сегменты AB и BC будут отображены на тех же пикселях, что и

сегмент AC. Иногда это так, а иногда нет, в зависимости от применяемых трансформаций и ориентации поверхности. Это может привести к появлению видимых «трещин» в поверхности.

Рисунок 2-17. Проблема T-пересечения и ее решение

Если вы строите замкнутую поверхность, при замыкании границы убедитесь, что используете точно те же значения координат для точек ее начала и конца, в противном случае, вы можете обнаружить видимые трещины в поверхности из-за арифметических округлений. Далее приводится неверных пример кода для рисования круга:

//Не используйте этот код

#define PI 3.14159265

//Количество отрезков, на которое мы подразделяем //гладкую границу круга

#define EDGES 30

glBegin(GL_LINE_STRIP); for(i=0;i<=EDGES;i++)

glVertex2f(cos((2*PI*i)/EDGES), sin((2*PI*i)/EDGES)); glEnd();

В действительности граница круга замкнется только в том случае, если ваш компьютер сможет при расчете синуса и косинуса от 0 и (2*PI*EDGES/EDGES) получить совершенно одинаковые величины. В противном случае начало и конец окружности, ограничивающей круг, могут не совпасть по координатам и позже по рисуемым пикселям и фигура не будет замкнутой. Однако на подобную точность расчетов с плавающей точкой рассчитывать не стоит. Для исправления кода убедитесь, что когда i==EDGES, вы используете для вычисления синуса и косинуса не (2*PI*EDGES/EDGES), а 0. (Существует более простой способ используйте GL_LINE_LOOP вместо GL_LINE_STRIP и измените условие завершения цикла на i<EDGES).

2.9.1 Пример: Построение Икосаэдра

Для иллюстрации некоторых вопросов, возникающих при аппроксимации поверхностей, рассмотрим простой пример кода. Этот код создает обычный икосаэдр (который является Платоновым телом, состоящим из 12 вершин и 20 граней, каждая из которых является треугольником). Икосаэдр можно считать грубым приближением сферы. Пример 2-13 задает вершины и треугольники, образующие икосаэдр и затем рисует икосаэдр.

Пример 2-13. Рисование икосаэдра

#define X .525731112119133606

#define Z .850650808352039932

GLfloat vdata[12][3] = { {-X,0.0,Z},{X,0.0,Z},{-X,0.0,-Z},{X,0.0,-Z}, {0.0,Z,X},{0.0,Z,-X},{0.0,-Z,X},{0.0,-Z,-X}, {Z,X,0.0},{-Z,X,0.0},{Z,-X,0.0},{-Z,-X,0.0}

};

GLuint tindices[20][3] = { {1,4,0},{4,9,0},{4,5,9},{8,5,4},{1,8,4}, {1,10,8},{10,3,8},{8,3,5},{3,2,5},{3,7,2}, {3,10,7},{10,6,7},{6,11,7},{6,0,11},{6,1,0}, {10,1,6},{11,0,9},{2,11,9},{5,2,9},{11,2,7}

};

int i;

glBegin(GL_TRIANGLES); for(i=0;i<20;i++)

{

//Здесь помещается информация о цвете

glVertex3fv(&vdata[tindices[i][0]][0]);

glVertex3fv(&vdata[tindices[i][1]][0]);

glVertex3fv(&vdata[tindices[i][2]][0]);

}

glEnd();

Странные числа X и Z выбраны таким образом, чтобы расстояние от точки начала координат до любой из вершин было равно 1.0. Координаты 12-ти вершин задаются в массиве vdata, где координаты нулевой вершины {-X,0.0,Z}, координаты первой {X,0.0,Z} и так далее. Массив tindices определяет, как нужно соединять вершины для получения нужных треугольников. Например, вершинами первого треугольника являются нулевая, четвертая и первая вершины из массива vdata. Если вершины для каждого треугольника будут выбираться согласно указанному порядку, все треугольники будут иметь одинаковую ориентацию.

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

Замечание: Во всех примерах данного раздела, если поверхность должна быть нарисована более одного раза, вероятно, стоит сохранить вычисленные координаты вершин и нормалей, чтобы не производить вычисления каждый раз при рисовании поверхности. Вы можете сделать это посредством собственных структур данных или списков отображения.

2.9.1.1 Расчет нормалей для поверхности

Если предполагается, что поверхность будет освещена, вам необходимо определить вектора нормалей к поверхности. Вектор нормали можно получить, вычислив нормализованное векторное произведение двух векторов на этой поверхности. Для каждой плоской части икосаэдра (то есть для каждого треугольника) все три вершины имеют одинаковый вектор нормали. Таким образом, нормаль должна быть определена единожды для каждого набора из трех вершин. Комментарий в предыдущем примере рисования икосаэдра может быть заменен на код из примера 2-14.

Пример 2-14. Генерируем вектора нормалей к поверхности

GLfloat d1[3], d2[3], norm[3]; for(j=0;j<3;j++)

{

d1[j]=vdata[tindices[i][0]][j]-vdata[tindices[i][1]][j]; d2[j]=vdata[tindices[i][1]][j]-vdata[tindices[i][2]][j];

}

normcrossprod(d1,d2,norm);

glNormal3fv(norm);

Функция normcrossprod() вычисляет нормализованное векторное произведение двух векторов, как показано в примере 2-15.

Пример 2-15. Вычисление нормализованного векторного произведения двух векторов

void normalize(float v[3])

{

GLfloat d=sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); if(d==0.0)

{

error(“Длина вектора равна 0”); return;

}

v[0]/=d;

v[1]/=d;

v[2]/=d;

}

void normcrossprod(float v1[3], float v2[3], float out[3])

{

out[0]=v1[1]*v2[2]- v1[2]*v2[1]; out[1]=v1[2]*v2[0]- v1[0]*v2[2]; out[2]=v1[0]*v2[1]- v1[1]*v2[0]; normalize(out);

}

Если вы используете икосаэдр в качестве приближения равномерно закрашенной сферы, вам, возможно, потребуется использовать нормали, которые перпендикулярны истинной поверхности сферы, а не граням икосаэдра. Вектора нормалей для сферы вычисляются элементарно, поскольку в каждой точке вектор нормали совпадает с вектором из центра сферы в эту точку. Поскольку в приведенном примере икосаэдр имеет радиус равный 1, данные для нормалей и данные координат вершин совпадают. Далее приводится код, рисующий двадцатистороннюю аппроксимацию (икосаэдр) равномерно закрашенной сферы (в коде предполагается, что кроме прочего включено освещение):

glBegin(GL_TRIANGLES); for(i=0;i<20;i++)

{

glNormal3fv(&vdata[tindices[i][0]][0]);

glVertex3fv(&vdata[tindices[i][0]][0]);

glNormal3fv(&vdata[tindices[i][1]][0]);

glVertex3fv(&vdata[tindices[i][1]][0]);

glNormal3fv(&vdata[tindices[i][2]][0]);

glVertex3fv(&vdata[tindices[i][2]][0]);

}

glEnd();

2.9.1.2 Улучшаем модель

20-сторонняя аппроксимация сферы выглядит не лучшим образом, если только она не находится далеко от наблюдателя, однако существует простой способ улучшить качество изображения. Представьте себе, что икосаэдр вписан в сферу (то есть все вершины икосаэдра лежат на поверхности сферы) и разбейте каждый из его треугольников на 4, как показано на рисунке 2-18 (заметьте, что в правой части рисунка AD=DB=AB/2, BE=EC=DC/2 и AF=FC=AC/2).

Замечание: Вообще говоря, это только один из вариантов разбиения треугольников. Например, можно разбить каждый треугольник не на 4 новых, а на 3, добавив новую вершину в точке пересечения биссектрис исходного треугольника или в центре его тяжести.

Появившиеся в результате новые вершины находятся внутри сферы, и мы должны притянуть их к поверхности сферы путем нормализации (делением координат вершин на некоторый коэффициент, в результате чего вектор из центра сферы к этой вершине будет иметь длину равную 1). Подобный процесс разбиения может быть повторен несколько раз для получения приближения требуемой точности. Объекты на рисунке 2-19 состоят из 20, 80 и 320 треугольников соответственно.

Пример 2-16 производит одно дополнительное разбиение, создавая 80-стороннюю сферическую аппроксимацию.

Рисунок 2-18. Разбиение треугольников

Рисунок 2-19. Разбиение увеличивает гладкость полигональной модели

Пример 2-16. Единичное разбиение

void drawTriangle(float *v1, float *v2, float *v3)

{

glBegin(GL_TRIANGLES); glNormal3fv(v1); glVertex3fv(v1); glNormal3fv(v2); glVertex3fv(v2); glNormal3fv(v3); glVertex3fv(v3);

glEnd();

}

void subdivine(float *v1, float *v2, float *v3)

{

GLfloat v12[3], v23[3], v31[3]; GLint i;

for (i=0;i<3;i++)

{

v12[i]=(v1[i]+v2[i])/2.0;

v23[i]=(v2[i]+v3[i])/2.0;

v31[i]=(v3[i]+v1[i])/2.0;

}

normalize(v12);

normalize(v23);

normalize(v31);

drawTriangle(v1,v12,v31);

drawTriangle(v2,v23,v12);

drawTriangle(v3,v31,v23);

drawTriangle(v12,v23,v31);

}

for(i=0;i<20;i++)

{

subdivine(&vdata[tindices[i][0]][0],

&vdata[tindices[i][1]][0],

&vdata[tindices[i][2]][0]);

}

Пример 2-17это модификация примера 2-16, осуществляющая рекурсивное разбиение треугольников до заданной глубины. Если глубина (параметр depth) равна 0, разбиение не производится, и треугольники рисуются «как есть». Если задана глубина равная 1, разбиение производится 1 раз и так далее.

Пример 2-17. Рекурсивное разбиение

void subdivine(float *v1, float *v2, float *v3, long depth)

{

GLfloat v12[3], v23[3], v31[3]; GLint i;

if(depth==0)

{

drawTriangle(v1,v2,v3);

return;

}

for (i=0;i<3;i++)

{

v12[i]=(v1[i]+v2[i])/2.0;

v23[i]=(v2[i]+v3[i])/2.0;

v31[i]=(v3[i]+v1[i])/2.0;

}

normalize(v12);

normalize(v23);

normalize(v31); subdivine(v1,v12,v31,depth-1); subdivine(v2,v23,v12,depth-1); subdivine(v3,v31,v23,depth-1); subdivine(v12,v23,v31,depth-1);

}

2.9.1.3 Обобщенное разбиение

Рекурсивное разбиение (например, то, которое приведено в примере 2-17) обычно заканчивается, если достигнут заданный уровень глубины рекурсии, или если степень

кривизны поверхности удовлетворяет некоторому критерию (особенно кривые части поверхностей выглядят лучше при большем числе разбиений).

Рассмотрим более обобщенное решение проблемы разбиения, считая, что некоторая поверхность параметризирована двумя переменными u[0] и u[1]. Предположим, что существует две функции:

void surf(GLfloat u[2],GLfloat vertex[3],GLfloat normal[3]); float curv(GLfloat u[2]);

При передаче u[] функции surf(), возвращаются соответствующие трехмерные координаты вершины и вектора нормали единичной длины. Если передать u[] в функцию curv() вычисляется и возвращается кривизна поверхности в данной точке (за

дополнительными сведениями об измерении кривизны поверхности обратитесь к литературе по аналитической геометрии).

В примере 2-18 представлена рекурсивная функция, которая разбивает треугольники до достижения максимального уровня вложенности или до тех пор, пока максимальная

кривизна в трех вершинах треугольника не станет меньше некоторого заданного значения.

Пример 2-18. Обобщенное разбиение

void subdivine(float u1[2],float u2[2],float u3[2],float cutoff,long depth)

{

GLfloat v1[3],v2[3],v3[3],n1[3],n2[3],n3[3]; GLfloat u12[2],u23[2],u31[2];

GLint i;

if(depth==maxdepth || (curv(u1)<cutoff))

{

surf(u1,v1,n1);

surf(u2,v2,n2);

surf(u3,v3,n3); glBegin(GL_POLYGON);

glNormal3fv(n1); glVertex3fv(v1); glNormal3fv(n2); glVertex3fv(v2); glNormal3fv(n3); glVertex3fv(v3);

glEnd();

return;

}

for(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;

}

subdivine(u1,u12,u31,cutoff,depth+1);

subdivine(u2,u23,u12,cutoff,depth+1);

subdivine(u3,u31,u23,cutoff,depth+1);

subdivine(u12,u23,u31,cutoff,depth+1);

}

Глава 3. Вид

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

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

Трехмерные координаты объекта преобразуются в позиции пикселей на экране серией из трех операций.

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

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

графике отсечение производится путем отбрасывания объектов с одной стороны отсекающей плоскости.

Наконец, должно быть установлено соответствие между преобразованными координатами и экранными пикселями. Этот процесс известен, как трансформация порта просмотра.

3.1Аналогия с фотокамерой

Процесс трансформаций, используемых для создания сцены, аналогичен получению фотографии с помощью камеры. Как показано на рисунке 3-1 этапы для получения фотографии (или сцены на экране монитора) должны быть следующими.

1.Установить треногу и направить камеру на сцену (видовые трансформации).

2.Расположить фотографируемые объекты нужным образом (модельные трансформации).

3.Выбрать линзы для камеры и масштаб (проекционные трансформации).

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

5.После произведения этих шагов фотография может быть отпечатана, а изображение сцены нарисовано.

Рисунок 3-1. Аналогия с фотокамерой

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

В коде программы видовые трансформации должны быть определены до модельных,

однако проекцию и порт просмотра вы можете задавать в любой момент до самого

Соседние файлы в предмете Компьютерная Графика