RedBook
.pdf12.1.1 Одномерные вычислители
В данном разделе представлен пример использования одномерного вычислителя для рисования кривой. Далее описаны команды и уравнения, контролирующие вычислитель.
12.1.1.1 Одномерный пример: простая кривая Безье
Программа, представленная в примере 12-1 рисует кубическую кривую Безье с использованием 4 контрольных точек, как показано на рисунке 12-1.
Рисунок 12-1. Кривая Безье
Пример 12-1. Кривая Безье с 4 контрольными точками: файл bezcurve.cpp
#include
GLfloat ctrlpoints[4][3]={ {-4.0,-4.0,0.0}, {-2.0,4.0,0.0}, {2.0,-4.0,0.0}, {4.0,4.0,0.0}
};
void init()
{
glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT);
glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4,&ctrlpoints[0][0]); glEnable(GL_MAP1_VERTEX_3);
}
void display()
{
int i;
glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0,1.0,0.0); glBegin(GL_LINE_STRIP);
for(i=0;i<=30;i++)
glEvalCoord1f((GLfloat)i/30.0);
glEnd();
//Контрольные точки glPointSize(5.0); glColor3f(1.0,1.0,0.0); glBegin(GL_POINTS);
for(i=0;i<4;i++)
glVertex3fv(&ctrlpoints[i][0]);
glEnd();
glFlush();
}
void reshape(int w, int h)
{
glViewport(0,0,(GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity();
if (w<=h) glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)w/(GLfloat)h, 5.0*(GLfloat)w/(GLfloat)h,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW); glLoadIdentity();
}
int main(int argc, char** argv)
{
glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutInitWindowSize(500,500); glutInitWindowPosition(100,100);
glutCreateWindow("Bezier Curve with Four Control Points"); init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop(); return 0;
}
Кубическая кривая Безье описывается 4 точками, которые фигурируют в примере в виде массива ctrlpoints[][]. Этот массив является одним из аргументов для команды glMap1f(). Полный список аргументов для этой команды следующий:
GL_MAP1_VERTEX3 Предоставлены трехмерные точки и должны быть сгенерированы трехмерные вершины
0.0Нижняя граница для u
1.0Верхняя граница для u
3Число величин с плавающей точкой между началом данных для одной контрольной точки и началом данных для другой в массиве
4Порядок сплайна, равный его степени плюс 1. В данном случае степень равна 3 (поскольку это кубический сплайн)
&ctrlpoints[][] Указатель на данные первой контрольной точки
Обратите внимание на то, что второй и третий аргументы команды управляют параметризацией кривой – в то время как uменяется от 0.0 до 1.0, кривая проходит от своего начала до своего конца. Вызов команды glEnable() активизирует одномерный вычислитель для трехмерных вершин.
Сама кривая рисуется в функции display() между вызовами glBegin() и glEnd(). поскольку вычислитель активизирован, вызов команды glEvalCoord1f() аналогичен выполнению команды glVertex() с координатами вершины на кривой, соответствующими заданному параметру u.
12.1.1.2 Определение и вычисление одномерного вычислителя
Многочлен Бернштейна степени n(или порядка n+1) вычисляется по формуле
Если представляет набор контрольных точек (одно-, двух-, трех- или даже четырехмерных), то уравнение
представляет кривую Безье в процессе изменения uот 0.0 до 1.0. Чтобы представить ту же кривую, позволяя u меняться от до , а не между 0.0 и 1.0, вычислите
.
Команда glMap1() определяет одномерный вычислитель, использующий данные уравнения.
void glMap1{fd} (GLenum target, TYPE u1, TYPE u2, GLint stride, GLint order, const TYPE *points);
Определяет одномерный вычислитель. Аргумент target определяет, что именно задается контрольными точками (смотрите таблицу 12-1) и, как следствие, сколько величин должно быть задано в аргументе points. Точки могут представлять вершины, цветовые данные RGBA, вектора нормалей или координаты текстуры. Например, если задать для target значение GL_MAP1_COLOR_4, вычислитель генерирует цветовые данные в четырехмерном RGBA пространстве вдоль кривой. Величины параметров, перечисленные в таблице 12-1, также используются для активизации конкретного вычислителя до его вызова. Чтобы активизировать или деактивировать нужный вычислитель, передайте соответствующий аргумент командам glEnable() или glDisable() соответственно.
Следующие два параметра glMap1*() – u1 и u2 задают диапазон изменения переменной u. Аргумент stride– это количество величин с плавающей точкой одинарной или двойной точности в каждом блоке хранилища, то есть это величина смещения между началом предыдущей контрольной точки и началом следующей.
Аргумент order – это степень кривой плюс 1, и это число должно согласовываться с количеством поставляемых контрольных точек. Аргумент points должен указывать на первую координату первой контрольной точки.
Таблица 12-1. Типы контрольных точек для glMap1*()
Параметр |
Смысл параметра |
GL_MAP1_VERTEX_3 |
координаты вершины x, yи z |
GL_MAP1_VERTEX_4 |
координаты вершины x, y, z и w |
GL_MAP1_INDEX |
цветовой индекс |
GL_MAP1_COLOR_4 |
R, G, B, A |
GL_MAP1_NORMAL |
координаты нормали |
GL_MAP1_TEXTURE_COORD_1 |
координата текстуры s |
GL_MAP1_TEXTURE_COORD_2 |
координаты текстуры s и t |
GL_MAP1_TEXTURE_COORD_3 |
координаты текстуры s, tи r |
GL_MAP1_TEXTURE_COORD_4 |
координаты текстуры s, t, r и q |
|
|
Одновременно может быть включено более одного вычислителя. Если у вас определены, например, два вычислителя GL_MAP1_VERTEX_3 и GL_MAP1_COLOR_4, то вызов команды glEvalCoord1() сгенерирует и позицию и цвет. Одновременно может быть активизирован только один вершинный вычислитель, даже если определены оба. Также одновременно может быть включен только один текстурный вычислитель. В других случаях, однако, вычислители могут использоваться для генерирования любой комбинации вершин, нормалей, цветов и координат текстуры. Если вы определите и активизируете два или более вычислителей одного и того же типа, будет использован тот из них, в котором наибольшее число измерений.
Для вычисления заданной и активизированной одномерной таблицы используйте команду glEvalCoord1*().
void glEvalCoord1{fd} (TYPE u); void glEvalCoord1{fd}v (TYPE* u);
Вызывает к исполнению процесс вычисления одномерных таблиц. Аргумент uзадает одномерную координату на кривой.
Обращения к glEvalCoord*() не используют текущие величины цвета, цветового индекса, вектора нормали и координат текстуры. glEvalCoord*() оставляет эти величины неизмененными.
12.1.1.3 Определение одномерных доменных координат на кривой с равными промежутками
Вы можете использовать glEvalCoord1() с любыми значениями u, но наиболее частой
практикой является использование сетки величин с равными промежутками между ними, как показано ранее в примере 12-1. Чтобы получить такие величины, определите сетку командой glMapGrid1*() и примените ее, используя glEvalMesh1().
void glMapGrid1{fd} (GLint n, TYPE u1, TYPE u2);
Определяет сетку величин изменяющихся от u1 до u2 за n равных шагов.
void glEvalMesh1 (GLenum mode, GLint p1, GLint p2);
Применяет определенную в текущий момент одномерную сетку величин ко всем активизированным вычислителям. Аргумент mode может принимать значения GL_POINT или GL_LINE в зависимости от того, как вы хотите изобразить кривую – точками вдоль нее или соединяющимися линиями. Вызов данной команды полностью эквивалентен
вызовам glEvalCoord1() для каждого шага от p1 до p2 включительно, где 0<=p1, p2<=n. С точки зрения кода, это эквивалентно следующему фрагменту:
glBegin(GL_POINTS); /* или glBegin(GL_LINES); */ for(i=p1;i<=p2;i++)
glEvalCoord1(u1+i*(u2-u1)/n);
glEnd();
за исключением того, что если i=0 или i=n, glEvalCoord1() вызывается непосредственно с параметрами u1 или u2.
12.1.2 Двумерные вычислители
Двумерный случай практически идентичен одномерному за тем исключением, что все команды должны принимать в расчет 2 параметра u и v. Точки, цвета, нормали и координаты текстуры должны поставляться по поверхности, а не по кривой.
Математически описание поверхности Безье задается в виде
где величины представляют собой набор из m*n контрольных точек, а функции B – это те же многочлены Бернштейна, что и в одномерном случае. Как и раньше величины
могут являться вершинами, нормалями, цветами или координатами текстуры.
Процедура использования двумерного вычислителя идентична одномерному случаю.
1.Определите вычислитель (или вычислители) с помощью glMap2*().
2.Активизируйте их, передав нужную величину (или величины) команде glEnable().
3.Вызовите их к исполнению либо с помощью команд glEvalCoord2() между glBegin() и glEnd(), либо определив и применив сетку величин с помощью команд glMapGrid2() и glEvalMesh2().
12.1.2.1 Определение и вычисление двумерного вычислителя
Используйте glMap2*() и glEvalCoord2*() для определения и выполнения двумерного вычислителя.
void glMap2{fd} (GLenum target, TYPE u1, TYPE u2, GLint ustride, GLint uorder, TYPE v1, TYPE v2,
GLint vstride, GLint vorder, TYPE* points);
Параметр target может принимать любые значения из таблицы 12-1, но в данном случае MAP1 нужно изменить на MAP2 в именах всех параметров. Как и раньше, эти же
значения используются в командах glEnable() и glDisable() для активизации или деактивации нужных вычислителей. Минимальное и максимальное значения аргументов u и v, задаются в виде аргументов u1, u2, v1 и v2, соответственно. Аргументы ustride и vstride задают количество чисел однократной или двойной точности между независимыми установками величин u и v, позволяя пользователю выбирать подрегион контрольных точек из намного большего по размеру массива. Например,
если данные заданы в форме
GLfloat ctrlpoints[100][100][3];
и вы хотите использовать только подмножество точек размером 4x4, начинающееся с точки ctrlpoints[20][30], установите ustrideв 100*3, а vstride в 3. Аргумент points в этом случае должен быть задан как &ctrlpoints[20][30][0]. Наконец, аргументы, определяющие порядок – uorder и vorder, могут иметь разные значения, позволяя создавать, например, поверхности квадратные в одном направлении и кубические в другом.
void glEvalCoord2{fd} (TYPE u, TYPE v); void glEvalCoord2{fd}v (TYPE* u, TYPE* v);
Вызывает к исполнению заданные и активизированные двумерные вычислители. Аргументы uи v являются величинами (или указателями на величины в случае векторной версии команды) доменных координат (координат на поверхности или кривой). Если активизирован один из вершинных вычислителей (GL_MAP2_VERTEX_3 или GL_MAP2_VERTEX_4), то координаты нормалей к поверхности вычисляются автоматически. Если активизирован режим автоматической генерации нормалей (с помощью аргумента GL_AUTO_NORMAL в команде glEnable()), эта нормаль ассоциируется с вычисленной вершиной. Если этот режим выключен, для вычисления нормали используется текущий активизированный вычислитель. Если же таковой отсутствует, используется текущий вектор нормали.
12.1.2.2 Двумерный пример: поверхность Безье
Пример 12-2 отображает каркасную поверхность Безье, показанную на рисунке 12-2, с использованием вычислителя. В этом примере поверхность рисуется в виде 9 изогнутых линий в каждом направлении. Каждая линия состоит из 30 сегментов. Для получения цельной программы, добавьте функции reshape() и main() из примера 12-1.
Рисунок 12-2. Поверхность Безье
Пример 12-2. Поверхность Безье: файл bezsurf.cpp
GLfloat ctrlpoints[4][4][3]={ {{-1.5,-1.5,4.0},{-0.5,-1.5,2.0},{0.5,-1.5,-1.0},{1.5,-
1.5,2.0}}, {{-1.5,-0.5,1.0},{-0.5,-0.5,3.0},{0.5,-0.5,0.0},{1.5,-0.5,-
1.0}}, {{-1.5,0.5,4.0},{-0.5,0.5,0.0},{0.5,0.5,3.0},{1.5,0.5,4.0}},
{{-1.5,1.5,-2.0},{-0.5,1.5,-2.0},{0.5,1.5,0.0},{1.5,1.5,-1.0}}
};
void display()
{
int i,j;
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glColor3f(0.0,0.0,0.0);
glLoadIdentity();
glRotatef(85.0,1.0,1.0,1.0);
for(j=0;j<=8;j++)
{
glBegin(GL_LINE_STRIP); for(i=0;i<=30;i++)
glEvalCoord2f((GLfloat)i/30.0,(GLfloat)j/8.0);
glEnd(); glBegin(GL_LINE_STRIP);
for(i=0;i<=30;i++)
glEvalCoord2f((GLfloat)j/8.0,(GLfloat)i/30.0);
glEnd();
}
glFlush();
}
void init()
{
glClearColor(1.0,1.0,1.0,0.0);
glLineWidth(2.0);
glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&ctrlpoints[0][0][0]); glEnable(GL_MAP2_VERTEX_3); glMapGrid2f(20,0.0,1.0,20,0.0,1.0); glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}
12.1.2.3 Определение двумерных доменных координат на поверхности с равными промежутками
В двух измерениях команды glMapGrid2*() и glEvalMesh2() используются так же как их одномерные версии, за тем исключением, что должна быть задана информация и о u, и о v.
void glMapGrid2{fd} (GLint nu, TYPE u1, TYPE u2, GLint vn, TYPE v1, TYPE v2); void glEvalMesh2 (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2);
Определяют двумерную сетку величин, проходящих от u1 до u2 за nu шагов с равными промежутками и от v1 до v2 за nv с равными промежутками (glMapGrid2*()) и затем применяют эту сетку ко всем активизированным вычислителям (glEvalMesh2()). Единственное существенное отличие от одномерного случая заключается в том, что аргумент mode команды glEvalMesh2() помимо GL_POINT и GL_LINE может принимать и значение GL_FILL. GL_FILL генерирует закрашенные полигоны с помощью четырехугольников. Если говорить точно, вызов glEvalMesh2() практически эквивалентен одному из трех следующих блоков кода. («Практически» потому, что, когда i=nu или j=nv, параметры равны u2 или v2, а не u1+nu*(u2-u1)/nu или v1+nv*(v2-v1)/nv– эти числа могут различаться из-за ошибок округления.)
glBegin(GL_POINTS); |
/* режим GL_POINT */ |
for(i=nu1;i<=nu2;i++) |
|
for(j=nv1;j<=nv2;j++)
glEvalCoord2(u1+i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
или
for(i=nu1;i<=nu2;i++) /* режим GL_LINE */
{
glBegin(GL_LINES); for(j=nv1;j<=nv2;j++)
glEvalCoord2(u1+i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
}
for(j=nv1;j<=nv2;j++)
{
glBegin(GL_LINES); for(i=nu1;i<=nu2;i++)
glEvalCoord2(u1+i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
}
или
for(i=nu1;i<=nu2;i++) /* режим GL_FILL */
{
glBegin(GL_QUAD_STRIP); for(j=nv1;j<=nv2;j++)
glEvalCoord2(u1+i*(u2-u1)/nu, v1+j*(v2-v1)/nv); glEvalCoord2(u1+(i+1)*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
}
Пример 12-3 показывает отличия, которые нужно сделать в примере 12-2, чтобы нарисовать ту же поверхность, но с применением glMapGrid2() и glEvalMesh2() для разделения квадрата доменных координат на сетку величин размерностью 8x8. Пример 12-3 также добавляет освещение и закраску, как показано на рисунке 12-3.
Рисунок 12-3. Освещенная и закрашенная поверхность Безье, нарисованная по сетке
доменных координат
Пример 12-3. Освещенная и закрашенная поверхность Безье, нарисованная по сетке доменных координат: файл bezmesh.cpp
void display()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glLoadIdentity();
glRotatef(85.0,1.0,1.0,1.0); glEvalMesh2(GL_FILL,0,20,0,20); glFlush();
}
void init()
{
glClearColor(1.0,1.0,1.0,0.0);
glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&ctrlpoints[0][0][0]); glEnable(GL_MAP2_VERTEX_3); glMapGrid2f(20,0.0,1.0,20,0.0,1.0); glEnable(GL_DEPTH_TEST);
glEnable(GL_AUTO_NORMAL);
//Источники света
GLfloat ambient[]={0.2,0.2,0.2,1.0}; GLfloat position[]={0.0,0.0,2.0,1.0}; GLfloat mat_diffuse[]={0.6,0.6,0.6,1.0}; GLfloat mat_specular[]={1.0,1.0,1.0,1.0}; GLfloat mat_shininess[]={50.0};
glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0,GL_AMBIENT,ambient); glLightfv(GL_LIGHT0,GL_POSITION,position);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse); glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular); glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
}
12.1.3 Использование вычислителей для текстур
Пример 12-3 активизирует одновременно 2 вычислителя: первый генерирует трехмерные точки на той же поверхности Безье, что и пример 12-3, а второй генерирует координаты текстуры. В данном случае в качестве координат текстуры выступают те же значения uи v, которые используются для вычисления вершин, но для того, чтобы применить их к поверхности, требуется определить отдельный вычислитель.
Второй вычислитель определяется на квадрате с углами в точках (0,0), (0, 1), (1,0) и (1,1); он генерирует (0,0) в углу (0,0), (0,1) в углу (0,1) и так далее. Поскольку он имеет порядок 2 (являясь линейным – первая степень плюс 1), вычисление этой текстуры в точке (u,v) дает координаты текстуры (s,t). Он активизируется в то же время, что и вершинный вычислитель, таким образом, на рисуемую поверхность воздействуют оба, что можно увидеть на рисунке 12-4. Если вы хотите, чтобы текстура повторилась 3 раза, измените каждую 1.0 в массиве texpts[ ][ ][ ].
Рисунок 12-4. Текстурированная поверхность Безье
Пример 12-4. Использование вычислителей для текстур: файл texturesurf.cpp
#include
#include
GLfloat ctrlpoints[4][4][3]={ {{-1.5,-1.5,4.0},{-0.5,-1.5,2.0},{0.5,-1.5,-1.0},{1.5,-
1.5,2.0}}, {{-1.5,-0.5,1.0},{-0.5,-0.5,3.0},{0.5,-0.5,0.0},{1.5,-0.5,-
1.0}}, {{-1.5,0.5,4.0},{-0.5,0.5,0.0},{0.5,0.5,3.0},{1.5,0.5,4.0}},
{{-1.5,1.5,-2.0},{-0.5,1.5,-2.0},{0.5,1.5,0.0},{1.5,1.5,-1.0}}
};
GLfloat texpts[2][2][2]={{{0.0,0.0},{0.0,1.0}},{{1.0,0.0},{1.0,1.0}}};
void display()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glColor3f(1.0,1.0,1.0); glEvalMesh2(GL_FILL,0,20,0,20);
glFlush();
}
#define imageWidth 64 #define imageHeight 64
GLubyte image[3*imageWidth*imageHeight];
void makeImage()
{
int i,j; float ti,tj;
for(i=0;i<="h)" glOrtho(-4.0,4.0,-4.0*(GLfloat)h (GLfloat)w, 4.0*(GLfloat)h (GLfloat)w,-4.0,4.0); else glOrtho(-4.0*(GLfloat)w (GLfloat)h, 4.0*(GLfloat)w (GLfloat)h,-4.0,4.0,-4.0,4.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(85.0,1.0,1.0,1.0); } int main(int argc, char** argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH); glutInitWindowSize(500,500); glutInitWindowPosition(100,100);