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

RedBook

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

void glCallList (GLuint list);

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

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

7.4.4 Иерархические списки отображения

Вы можете создать иерархический список отображения, то есть список отображения, который исполняет другой список отображения, вызывая glCallList() между парой glNewList() и glEndList(). Иерархический список отображения полезен для рисования объектов, состоящих из компонентов, особенно если эти компоненты используются более одного раза. Например, следующий список отображения для велосипеда исполняет списки отображения для рисования его частей:

glNewList(listIndex, GL_COMPILE); glCallList(handlebars); glCallList(frame); glTranslatef(1.0,0.0,0.0); glCallList(wheel); glTranslatef(3.0,0.0,0.0); glCallList(wheel);

glEndList();

Во избежание бесконечной рекурсии существует ограничение на уровень вложенности списков отображение; максимум уровня вложенности не может быть меньше 64, но может быть больше в зависимости от реализации. Чтобы выяснить максимальный уровень вложенности в вашей реализации OpenGL, используйте:

glGetIntegerv(GL_MAX_LIST_NESTING, GLint *data);

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

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

Пример 7-3. Иерархический список отображения

glNewList(1, GL_COMPILE); glVertex3fv(v1);

glEndList();

glNewList(2, GL_COMPILE);

glVertex3fv(v2);

glEndList();

glNewList(3, GL_COMPILE); glVertex3fv(v3);

glEndList();

glNewList(4, GL_COMPILE); glBegin(GL_POLYGON); glCallList(1); glCallList(2); glCallList(3);

glEnd();

glEndList();

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

7.4.5 Управление индексами списков отображения

До сих пор мы рекомендовали использовать glGenLists() для получения неиспользуемых индексов. Если вы хотите избежать использования glGenLists(), обязательно используйте glIsList() для выяснения того, находится ли конкретный индекс в использовании.

GLboolean glIsList (GLuint list);

Возвращает GL_TRUE если list уже используется в качестве идентификатора существующего списка отображения и GL_FALSE в противном случае.

Вы можете удалить конкретный список отображения или их непрерывный диапазон командой glDeleteLists(). После применения glDeleteLists(), индексы удаленных списков становятся доступны для дальнейшего использования.

void glDeleteLists (GLuint list, GLsizei range);

Удаляет последовательные (по индексам) списки отображения количеством range, начиная с индекса list. Попытки удалить несуществующие списки отображения игнорируются.

7.5 Исполнение нескольких списков отображения

OpenGL предоставляет эффективный механизм для последовательного исполнения нескольких списков отображения. Этот механизм требует, чтобы вы поместили индексы нужных списков в массив и вызвали glCallLists(). Этот механизм обычно используется тогда, когда индексы списков отображения имеют осмысленные значения. Например, если вы создаете шрифт, индекс каждого списка может соответствовать ASCII коду символа в шрифте. Чтобы создать несколько таких шрифтов, вам необходимо заранее

установить разные начальные значения для индексов списков отображения каждого шрифта. Вы можете задать начальный индекс, вызвав glListBase() до вызова glCallLists().

void glListBase (GLuint base);

Задает смещение, которое добавляется к индексам списков в glCallLists() для получения финальных индексов. По умолчанию базовое смещение равно 0. База не оказывает влияние ни на команду glNewList(), ни на glCallList(), исполняющую единственный список отображения.

void glCallLists (GLsizei n, GLenum type, const GLvoid *lists);

Исполняет nсписков отображения. Индексы исполняемых списков отображения вычисляются путем сложения текущей базы списков (заданной с помощью glListBase()) с знаковыми целыми значениями в массиве, на который указывает lists.

Аргумент type задаеттипвеличинв lists иможет принимать значения GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT или

GL_FLOAT, указывая на то, что массив lists должен интерпретироваться как массив байт, беззнаковых байт, коротких целых, беззнаковых коротких целых, целых, беззнаковых целых или чисел с плавающей точкой, соответственно. Type также может принимать значения GL_2_BYTES, GL_3_BYTESили GL_4_BYTES; в этом случае из массива считываются по 2, 3 или 4 байта, сдвигаются и складываются вместе, байт за байтом, для вычисления смещения списка. Используется следующий алгоритм (byte[0]это начало последовательности байтов):

/* b=2, 3 или 4; байты в массиве нумеруются 0, 1, 2, 3 и так далее */ offset=0;

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

{

offset=offset << 8; offset+=byte[i];

}

index=offset+listbase;

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

В качестве примера использования нескольких списков отображения, рассмотрите фрагмент программы 7-4, взятый из полной программы-примера 7-5. Эта программа рисует символы линейным шрифтом (набор букв был создан из сегментов линий). Функция initStrokedFont() устанавливает индекс для каждого списка, чтобы индексы были равны значениям ASCII-кодов соответствующих символов.

Пример 7-4. Определение нескольких списков отображения

void initStrokedFont(void)

{

GLuint base;

base=glGenLists(128);

glListBase(base); glNewList(base+’A’,GL_COMPILE);

drawLetter(Adata);

glEndList();

glNewList(base+’E’,GL_COMPILE); drawLetter(Edata);

glEndList();

glNewList(base+’P’,GL_COMPILE); drawLetter(Pdata);

glEndList();

glNewList(base+’R’,GL_COMPILE); drawLetter(Rdata);

glEndList();

glNewList(base+’S’,GL_COMPILE);

drawLetter(Sdata);

glEndList();

//Символ пробела glNewList(base+’ ’,GL_COMPILE);

glTranslatef(8.0,0.0,0.0);

glEndList();

}

Команда glGenLists() выделяет 128 непрерывных индексов для списков отображения. Первый из этих индексов становится базой списков. Для каждой буквы создается свой список; индекс каждого списка представляет собой сумму базы и ASCII-кода символа. В этом примере создается только несколько символов и символ пробела.

После создания списков, может быть вызвана команда glCallLists() для их исполнения. Например, вы можете передать текстовую строку функции printStrokedString():

void printStrokedString(GLbyte *s)

{

GLint len=strlen(s); glCallLists(len, GL_BYTE, s);

}

Значение ASCII для каждого символа в строке используется в качестве смещения в индексах списков отображения. Для определения финального индекса исполняемых списков списочная база складывается с ASCII-кодом каждого символа. Вывод, производимый примером 7-5, показан на рисунке 7-1.

Рисунок 7-1. Линейный шрифт с символами A, E, P, R и S

Пример 7-5. Несколько списков отображения, определяющие линейный шрифт: файл stroke.cpp

#include <glut.h> #include <string.h>

#define PT 1 #define STROKE 2 #define END 3

typedef struct charpoint

{

GLfloat x,y; int type;

} CP;

CP Adata[]={{0,0,PT},{0,9,PT},{1,10,PT},{4,10,PT},{5,9,PT},{5,0,STROKE},{0,5,PT},{5,5,END}}; CP Edata[]={{5,0,PT},{0,0,PT},{0,10,PT},{5,10,STROKE},{0,5,PT},{4,5,END}};

CP Pdata[]={{0,0,PT},{0,10,PT},{4,10,PT},{5,9,PT},{5,6,PT},{4,5,PT},{0,5,END}};

CP Rdata[]={{0,0,PT},{0,10,PT},{4,10,PT},{5,9,PT},{5,6,PT},{4,5,PT},{0,5,STROKE},{3,5,PT}, {5,0,END}};

CP Sdata[]={{0,1,PT},{1,0,PT},{4,0,PT},{5,1,PT},{5,4,PT},{4,5,PT},{1,5,PT},{0,6,PT},{0,9,PT},{ 1,10,PT},{4,10,PT},{5,9,END}};

//Интерпретируем инструкции из массива для буквы и //визуализируем букву с помощью сегментов линий void drawLetter(CP *l)

{

glBegin(GL_LINE_STRIP); while(1)

{

switch(l->type)

{

case PT: glVertex2fv(&l->x); break;

case STROKE: glVertex2fv(&l->x); glEnd(); glBegin(GL_LINE_STRIP); break;

case END: glVertex2fv(&l->x); glEnd(); glTranslatef(8.0,0.0,0.0); return;

}

l++;

}

}

//Создаем список отображения для каждого из 6 символов (5 букв + пробел) void init()

{

glLineWidth(2.0);

base=glGenLists(128);

glListBase(base);

glNewList(base+'A',GL_COMPILE); drawLetter(Adata); glEndList(); glNewList(base+'E',GL_COMPILE); drawLetter(Edata); glEndList(); glNewList(base+'P',GL_COMPILE); drawLetter(Pdata); glEndList(); glNewList(base+'R',GL_COMPILE); drawLetter(Rdata); glEndList(); glNewList(base+'S',GL_COMPILE); drawLetter(Sdata); glEndList(); glNewList(base+' ',GL_COMPILE); glTranslatef(8.0,0.0,0.0); glEndList();

}

char *test1="A SPARE SERAPE APPEARS AS"; char *test2="APES PREPARE RARE PEPPERS";

void printStrokedString(char *s)

{

GLsizei len=strlen(s); glCallLists(len,GL_BYTE,(GLbyte *) s);

}

void display()

{

glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0,1.0,0.0); glPushMatrix();

glScalef(2.0,2.0,2.0);

glTranslatef(10.0,30.0,0.0);

printStrokedString(test1);

glPopMatrix();

glPushMatrix();

glScalef(2.0,2.0,2.0);

glTranslatef(10.0,13.0,0.0);

printStrokedString(test2);

glPopMatrix();

glFlush();

}

void reshape(int w,int h)

{

glViewport(0,0,w,h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0,440,0.0,120);

}

void keyboard(unsigned char key,int x,int y)

{

switch(key)

{

case ' ': glutPostRedisplay(); break;

case 27: exit(0); break;

}

}

int main(int argc, char** argv)

{

glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutInitWindowSize(440,120);

glutCreateWindow("Multiple Display Lists to Define a Stroked Font"); init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutKeyboardFunc(keyboard);

glutMainLoop(); return 0;

}

7.6 Управление переменными состояния с помощью списков отображения

Список отображения может содержать команды, изменяющие значения переменных состояния OpenGL. Величины изменяются при исполнении списка отображения, как если бы команды выполнялись в непосредственном режиме. Изменения сохраняются и после завершения исполнения списка. Как было видно в примере 7-2 и будет показано в примере 7-6, изменения текущего цвета и текущей матрицы, сделанные в процессе исполнения списка отображения, остаются в действии и после того, как он был выполнен.

Пример 7-6. Постоянство изменения состояния после исполнения списка отображения

glNewList(listIndex, GL_COMPILE); glColor3f(1.0,0.0,0.0); glBegin(GL_POLYGON);

glVertex2f(0.0,0.0);

glVertex2f(1.0,0.0);

glVertex2f(0.0,1.0);

glEnd();

glTranslatef(1.5,0.0,0.0);

glEndList();

Таким образом, если вы выполните следующую последовательность кода, линию, нарисованная после того, как список отображения рисовал красным цветом и выполнил смещение на (1.5, 0.0, 0.0), будет красной:

glCallList(listIndex); glBegin(GL_LINES);

glVertex2f(2.0,-1.0); glVertex2f(1.0,0.0);

glEnd();

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

необходимо сохранить значения переменных до исполнения списка отображения и затем восстановить их после него. Помните, что в списках вы не можете использовать команды glGet*(), так что понадобится иной способ запроса и сохранения значений переменных состояния.

Вы можете использовать glPushAttrib() для сохранения групп переменных состояния и glPopAttrib() для их последующего восстановления. Чтобы сохранять и восстанавливать текущую матрицу, используйте glPushMatrix() и glPopMatrix(). Эти команды могут быть вполне законно кэшированы в списках отображения. Чтобы восстановить переменные состояния в примере 7-6, можно использовать код примера

7-7.

Пример 7-7. Восстановление переменных состояния внутри списка отображения

glNewList(listIndex, GL_COMPILE); glPushMatrix();

glPushAttrib(GL_CURRENT_BIT); glColor3f(1.0,0.0,0.0); glBegin(GL_POLYGON);

glVertex2f(0.0,0.0);

glVertex2f(1.0,0.0);

glVertex2f(0.0,1.0);

glEnd();

glTranslatef(1.5,0.0,0.0);

glPopAttrib();

glPopMatrix();

glEndList();

Если вы используете список отображения из примера 7-7, код в примере 7-8 нарисует зеленую, неперенесенную линию. При использовании списка отображения из примера 7-6 (которые не сохраняет и не восстанавливает состояние), линия будет нарисована красным сдвинутой 10 раз на (1.5, 0.0, 0.0).

Пример 7-8. Список отображение может влиять или не влиять на drawLine()

void display(void)

{

GLint i; glClear(GL_COLOR_BUFFER_BIT);

glColor3f(0.0,1.0,0.0); //устанавливаем текущий цвет в зеленый for(i=0;i<10;i++)

glCallList(listIndex); //список исполняется 10 раз drawLine();

glFlush();

}

7.6.1 Инкапсуляция изменений режима

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

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

Пример 7-9 показывает, как использовать списки отображения для переключения между тремя различными шаблонами линий. Сначала вызывается glGenLists(), чтобы

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

Пример 7-9. Списки отображения, используемые для смены режимов

GLuint offset;

offset=glGenLists(3); glNewList(offset,GL_COMPILE);

glDisable(GL_LINE_STIPPLE); glEndList();

glNewList(offset+1,GL_COMPILE); glEnable(GL_LINE_STIPPLE); glLineStipple(1,0x0F0F);

glEndList();

glNewList(offset+2,GL_COMPILE); glEnable(GL_LINE_STIPPLE); glLineStipple(1,0x1111);

glEndList();

#define drawOneLine(x1, y1, x2, y2) glBegin(GL_LINES); glVertex2f((x1),(y1)); glVertex2f((x2),(y2)); glEnd();

glCallList(offset);

drawOneLine(50.0,125.0,350.0,125.0);

glCallList(offset+1);

drawOneLine(50.0,100.0,350.0,100.0);

glCallList(offset+2);

drawOneLine(50.0,75.0,350.0,75.0);

Глава 8. Отображение пикселей, битовых карт, шрифтов и изображений

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

Битовые карты, обычно используемые для символов шрифта

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

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

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

В этой главе описывается, как рисовать пиксельные данные (битовые карты и изображения) из памяти в буфер кадра и как считывать из буфера кадра обратно. Также описывается, как копировать пиксельные данные из одной позиции в другие, то есть из одного буфера в другой, или из буфера в него же.

Замечание: OpenGL не поддерживает ни считывание пикселей из графических файлов, ни сохранение пикселей в них.

8.1 Битовые карты и шрифты

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

OpenGL предоставляет только низкоуровневую поддержку для рисования строк символов и манипуляций со шрифтами. Команды glRasterPos*() и glBitmap() позиционируют и рисуют на экране одну битовую карту. Кроме того, с помощью

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

Рассмотрим пример 8-1, который трижды рисует на экране символ F. На рисунке 8-1 символ F показан в виде битовой карты и соответствующих ей данных.

Рисунок 8-1. Битовая карта символа F и ее данные

Пример 8-1. Рисование битовой карты символа: файл drawf.cpp

#include <glut.h>

GLubyte rasters[24]= { 0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x00, 0xC0,0x00,0xFF,0x00,0xFF,0x00,0xC0,0x00, 0xC0,0x00,0xC0,0x00,0xFF,0xC0,0xFF,0xC0 };

void init(void)

{

glPixelStorei(GL_UNPACK_ALIGNMENT,1); glClearColor(0.0,0.0,0.0,0.0);

}

void display(void)

{

glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,1.0,1.0);

glRasterPos2i(20,20);

glBitmap(10,12,0.0,0.0,11.0,0.0,rasters);

glBitmap(10,12,0.0,0.0,11.0,0.0,rasters);

glBitmap(10,12,0.0,0.0,11.0,0.0,rasters);

glFlush();

}

void reshape(int w, int h)

{

glViewport(0,0,(GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0,w,0,h,-1.0,1.0); glMatrixMode(GL_MODELVIEW);

}

void keyboard(unsigned char key,int x,int y)

{

switch(key)

{

case 27: exit(0); break;

}

}

int main(int argc, char **argv)

{

glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutInitWindowSize(300,100); glutCreateWindow("Drawing a Bitmapped Character"); init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutKeyboardFunc(keyboard);

glutMainLoop(); return 0;

}

На рисунке 8-1 обратите внимание, что видимая часть символа Fзанимает 10 бит в ширину. Битовые карты всегда хранятся кусками, ширина которых кратна 8, но реальная ширина битовой карты не обязана быть кратной 8. Биты, задающие битовую карту, выводятся, начиная с левого нижнего угла: сначала рисуется нижний ряд, затем ряд над ним и так далее. Как вы можете видеть в коде, битовая карта хранится в памяти именно в этом порядке массив начинается с чисел 0xc0, 0x00, 0xc0, 0x00, задающих два нижних ряда символа F и заканчивается числами 0xff, 0xc0, 0xff, 0xc0, задающими два верхних ряда.

В приведенном примере особый интерес представляют команды glRasterPos2i() и glBitmap(); они подробно рассматриваются в следующих разделах. Пока мы проигнорируем команду glPixelStorei(); она указывает на то, как данные битовой карты хранятся в памяти компьютера.

8.1.1 Текущая позиция растра

Текущая позиция растра это некоторое положение на экране, где будет нарисована следующая битовая карта (или изображение). В примере с символом F текущая позиция растра была установлена командой glRasterPos*() с координатами (20, 20) в качестве параметров. Это то место, где был помещен нижний левый угол символа F:

glRasterPos2i(20, 20);

void glRasterPos[234]{sifd} (TYPE x, TYPE y, TYPE z, TYPE w); void glRasterPos[234]{sifd}v (TYPE *coords);

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