Буфер накопления
Этот буфер аналогичен буферу цвета, но при выводе в него образы накапливаются и накладываются друг на друга. Используется буфер для получения эффектов нерезкости и сглаживания. Посмотрим на примере проекта из подкаталога Ех59, как осуществляется работа с буфером накопления.
Буфер трафарета
Многие специальные эффекты, самым простым из которых является вырезка объекта, основаны на использовании буфера трафарета (шаблона).
Замечание
Напомню, что одним из полей в формате пиксела является размер буфера трафарета, который надо задавать в соответствии с характеристиками вашей графической платы
Выполнение теста трафарета разрешается командой glEnable с аргументом GL_STENCIL_TEST
Команда glstencilFunc отвечает за сравнение, а команда glstencilop позволяет определить действия, базирующиеся на результате проверки трафарета Другие команды, связанные с буфером трафарета, рассмотрим по ходу изучения примеров, первым из которых станет проект из подкаталога Ех32, результат работы которого представлен на рис 4.21
Часто разработчики нуждаются в нескольких экранах
Следующий пример не имеет отношения к функциям работы с пикселами, но подготавливает к следующим примерам и сам по себе является очень полезным. В проекте из подкаталога Ех55 в окне приложения присутствует несколько экранов, в каждом из которых одна и та же сцена видна из различных точек зрения (Рисунок 4.34).
Для разбиения окна на несколько экранов пользуемся знакомым нам приемом, основанным на использовании команд glviewport и glscissor. Напомню, что вторая из них нам необходима для того, чтобы при очистке буфера кадра не закрашивался весь экран.
Для сокращения кода видовые параметры задаются одинаковыми для всех трех экранов:
procedure TfrmGL.FormResize(Sender: TObject);
begin
glMatrixMode(GL_PROJECTION);
glLoadldentity;
gluPerspective (60.0, ClientWidth / ClientHeight, 5.0, 70.0);
glMatrixMode (GL_MODELVIEW);
glLoadldentity;
InvalidateRect(Handle, nil, False);
end;
Конечно же, для каждого экрана можно эти параметры задавать индивидуально, тогда соответствующие строки необходимо вставить перед заданием установок, например, перед очередным glViewport. Для каждого экрана меняем положение точки зрения командой gluLookAt:
glPushMatrix;
// первый экран - левая половина окна
glviewport(0,0,round(ClientWidth/2), ClientHeight);
glScissor(0,0,round(ClientWidth/2), ClientHeight); //вырезка
glClearColor(0. 55, 0. 9, 0. 4, 0. 0);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glPushMatrix;
gluLookAt(25. 0, 25. 0, 50. 0, 25. 0, 25. 0, 20. 0, 0. 0, 1. 0, 0. 0);
glTranslatef(25. 0, 25. 0, 10. 0);
glRotatef (Angle, 1. 0, 0. 0, 0. 0);
glCallList(Torus);
glPopMatrix;
// второй экран - правый верхний угол окна
// единица - для получения разделительной линии
glViewport(round(ClientWidth/2) + l, round(ClientHeight/2) +1,
round(ClientWidth/2), round(ClientHeight/2));
glScissor(round(ClientWidth/2) + l, round(ClientHeight/2) +1,
round(ClientWidth/2), round(ClientHeight/2));
glClearColor(0. 7, 0. 7, 0. 9, 0. 0);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glPushMatrix;
gluLookAt(25. 0, 50. 0, 50. 0, 25. 0, 25. 0, 20. 0, 0. 0, 1. 0, 0. 0);
glTranslatef(25. 0, 25. 0, 10. 0); glRotatef (Angle, 1. 0, 0. 0, 0. 0);
glCallList(Torus);
glPopMatrix;
// третий экран - левый нижний угол окна
glViewport(round(ClientWidth/2) +l, 0, round(ClientWidth/2),
round(ClientHeight/2));
glScissor(round(ClientWidth/2)+l, , round(ClientWidth/2), round(ClientHeight/2));
glClearColor(0. 0, 0. 6, 0. 7, 0. 0);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glPushMatrix;
gluLookAt(0. 0, 25. 0, 50. 0, 25. 0, 25. 0, 20. 0, 0. 0, 1. 0, 0. 0);
glTranslatef(25. 0, 25. 0, 10. 0);
glRotatef (Angle, 1. 0, 0. 0, 0. 0);
glCallList(Torus);
glPopMatrix;
glPopMatrix;
Стоит напомнить, что без следующей строки ничего работать не будет.
glEnable(GL_SCISSOR_TEST); // включаем режим вырезки
Два рассмотренных примера подготовили нас к тому, чтобы познакомиться со следующим проектом из подкаталога Ex56, где визуализируется содержимое буфера глубины (рис 4.35).
Для построения поверхности используется
Теперь вы можете подготавливать модели с помощью любого профессионального редактора - формат dxf является открытым и стандартным.
Замечание
Саму поверхность не обязательно строить по отдельным треугольникам, главное, чтобы используемый редактор мог разбивать поверхность на такие треугольники при экспорте в dxf-формат.
Я соглашусь с вами, что некоторые примеры, хоть и являются очень интересными, годятся только для того, чтобы пару раз ими полюбоваться. Этот же пример является действительно полезным и достоин подробного разбора.
Программа является универсальной, в частности, в ней не ограничивается число точек поверхности. В таких случаях можно использовать динамические массивы или, как в этом Примере, списки (в терминах Delphi).
Списки введены в программе для хранения точек модели и нормалей к каждому треугольнику:
Model, Normals : TList;
Следующий тип введен для хранения координат отдельной точки:
type
Vector = record
х, у, z : GLfloat;
end;
Одной из ключевых процедур примера является процедура чтения данных из файла формата dxf:
I$WARNINGS OFF} // отключить предупреждения компилятора о возможной
// неинициализации переменных
procedure TfrmGL.LoadDXF (st : String);
var
f : TextFile;
wrkString : String;
group, err : GLint;
x1, x2, y1, y2, z1, z2, x3, y3, z3 : GLfloat;
// вспомогательная процедура добавления вектора в список
Model procedure AddToList (х, у, z : GLfloat);
var
wrkVector : Vector; // рабочая переменная, вектор
pwrkVector : PVector; // указатель на вектор begin
wrkVector.х := х; // заполняем поля вектора
wrkVector.у := у; wrkVector.z := z;
New (pwrkVector); // выделение памяти для нового элемента списка
pwrkVectorA := wrkVector; // задаем указатель
Model.Add (pwrkVector); // собственно добавление вектора в список
end;
begin
AssignFile(f,st); // открываем файл
Reset(f);
repeat // пропускаем файл до секции объектов "ENTITIES"
ReadLn(f, wrkString);
until (wrkString = 'ENTITIES') or eof(f);
While not eof (f) do begin
ReadLn (f, group); // маркер
ReadLn (f, wrkString); // идентификатор либо координата
case group of
0: begin // начался следующий объект
AddToList (х3, у3, z3); // добавляем в список треугольник
AddToList (x2, у2, 2.2); AddToList (x1, y1, z1);
end;
10: val(wrkString, x1, err) // считываем вершины треугольника
20: val(wrkString, y1, err) 30: val(wrkString, z1, err)
11: val(wrkString, x2, err) 21: val(wrkString, y2, err)
31: val(wrkString, z2, err) 12: val(wrkString, x3, err)
22: val(wrkString, y3, err) 32: val(wrkString, z3, err)
end;
end;
CloseFile(f);
end;
{$WARNINGS ON}
После того как считаны координаты вершин треугольников, образующих поверхность, необходимо рассчитать векторы нормалей к каждому треугольнику:
($HINTS OFF) // отключаем замечания компилятора о неиспользовании
// переменной pwrkVector procedure TfrmGL.CalcNormals;
var
i : Integer;
wrk1, vx1, vy1, vz1, vx2, vy2, vz2 : GLfloat;
nx, ny, nz : GLfloat;
wrkVector : Vector;
pwrkVector : ^Vector;
wrkVector1, wrkVector2, wrkVectorS : Vector;
pwrkVector1, pwrkVector2, pwrkVector3 : ^Vector; begin
New (pwrkVector1); // выделение памяти под указатели
New (pwrkVector2);
New (pwrkVector3);
For i := 0 to round (Model.Count / 3) - 1 do begin
pwrkVector1 := Model [i * 3]; // считываем по три вершины из списка
wrkVector1 := pwrkVector1^; // модели
pwrkVector2 := Model [i * 3 -t- 1] ;
wrkVector2 := pwrkVector2^;
pwrkVector3 := Model [i * 3 + 2];
wrkVector3 := pwrkVectorS";
// приращения координат вершин по осям
vx1 := wrkVector1.х - wrkVector2.x;
vy1 := wrkVector1.y - wrkVector2.у;
vz1 := wrkVectorl.z - wrkVector2.z; 1
vx2 := wrkVector2.x - wrkVector3.x; 1
vy2 := wrkVector2.y - wrkVector3.y;
vz2 := wrkVector2.z - wrkVector3.z;
// вектор-перпендикуляр к центру треугольника
nx := vy1 * vz2 - vz1 * vy2;
ny := vz1 * vx2 - vx1 * vz2;
nz := vx1 * vy2 - vy1 * vx2; // получаем унитарный вектор единичной длины
wrk1 := sqrt (nx * nx + ny * ny + nz * nz);
If wrk1 = 0 then wrk1 := 1; // для предотвращения деления на ноль
wrkVector.x := nx / wrk1;
wrkVector.y := ny / wrk1;
wrkVector.z := nz / wrk1;
New (pwrkVector); // указатель на очередную нормаль
pwrkVector4 := wrkVector;
Normals.Add (pwrkVector); // добавляем нормаль в список Normals
end;
end;
{$HINTS ON}
Собственно поверхность описывается в дисплейном списке:
Model := TList.Create; // создаем список модели
Normals := TList.Create; // создаем список нормалей
LoadDxf ('Dolphin.dxf'); // считываем модель
CalcNormals; // расчет нормалей
glNewList (SURFACE, GL_COMPILE); // поверхность хранится в списке
For i := 0 to round (Model.Count / 3) - 1 do begin // по три вершины
glBegin(GL_TRIANGLES);
glNormal3fv(Normals.Items [i]); // задаем текущую нормаль
glvertex3fv(Model.Items [i * 3]); // вершины треугольника
glvertex3fv(Model.Items [i * 3 + 1]); glvertex3fv(Model.Items [i * 3 + 2]);
glEnd;
end;
glEndList;
Model.Free; // списки больше не нужны, удаляем их
Normals.Free;
Замечание
Я не мог привести этот пример в предыдущей главе, поскольку в этом примере необходимо задать модель освещения с расчетом обеих сторон многоугольников у нас нет гарантии, что все треугольники поверхности повернуты к наблюдателю лицевой стороной
В программе все треугольники раскрашены одним цветом, можно добавить еще один список, аналогичный списку нормалей, но хранящий данные о цвете каждого отдельного треугольника.
Замечание
Обратите внимание, что в этом примере я меняю скорость поворота модели в зависимости от того, как быстро пользователь перемещает указатель при нажатой кнопке мыши.
Для построения поверхности используется
Программа рисует бесформенный полый объект по точкам, считываемым из текстового файла. Каждая строка файла содержит координаты вершины и нормали к ней.
Поверхность строится как единая группа связанных треугольников:
procedure DrawSurface;
var
i: GLuint;
begin
glBeginf GL_TRIANGLE_STRIP);
For i := 0 to numverts - 1 do begin glNormal3fv( @norms[i]);
glVertex3fv( @verts[i]);
end;
glEnd;
end;
Клавишами управления курсором можно вращать фигуру, есть режим тестирования скорости воспроизведения, активизируемый нажатием клавиши 'Т'' фигура делает полный оборот по оси X.
Программа, несмотря на обширность кода, проста и не должна вызвать особых затруднений при разборе. Стоит только обратить внимание, что на сцене присутствует два источника света, различающиеся позицией.
Этот пример демонстрирует, что, в принципе, для воспроизведения поверхностей сложной формы не обязательно пользоваться сплайнами, а можно разбить поверхность на множество простых примитивов.
Следующий пример окажется очень полезным для тех, кто настроен на профессиональную работу с OpenGL.
В проекте из подкаталога Ех24 координаты вершин треугольников, образующих поверхность, считываются из файла формата dxf (Рисунок 4.17).
Для прямоугольника, расположенного слева, источник света находится в бесконечности
Позиция источника света, освещающего первый четырехугольник, задана точно такой же, что и для второго квадрата, но значение четвертого компонента равно нулю, источник света расположен где-то в бесконечности, и только направление на него влияет на оттенок серого, которым равномерно окрашивается плоскость
Далее в нашей программе стоит проект из подкаталога Ех09. Этот пример, представляющий двенадцать сфер из разного материала, обычно сопровождает любой курс по OpenGL (Рисунок 4 5)
Для простых задач тень можно рисовать самому, по многоугольникам
В программе описана пользовательская процедура для рисования тени с учетом того, что все грани куба параллельны координатным плоскостям. Тень рисуется в виде шести отдельных серых многоугольников, для каждой грани объекта:
procedure TfrmGL.Shadow;
// подсчет координат точки тени для одной вершины
procedure OneShadow (х, у, z, h : GLfloat; var x1, y1 : GLfloat);
begin
x1 := x * LightPosition [2] / (LightPosition [2] - (z + h) ) ;
If LightPosition [0] < x
then begin If x1 > 0 then x1 := LightPosition [0] + x1 end
else begin If x1 > 0 then x1 := LightPosition [0] - x1 end;
y1 := у * LightPosition [2] / (LightPosition [2] - (z + h));
If LightPosition [1] < у
then begin If y1 > 0 then y1 := LightPosition [1] + y1 end
else begin If y1 > 0 then yl := LightPosition [1] - y1 end;
If x1 < 0 then x1 := 0 else
If x1 > SquareLength then x1 := SquareLength;
If y1 < 0 then y1 := 0 else
If y1 > SquareLength then y1 := SquareLength;
end;
var
x1, y1, x2, y2, хЗ, уЗ, х4, y4 : GLfloat;
wrkx1, wrky1, wrkx2, wrky2, wrkx3, wrky3, wrkx4, wrky4 : GLfloat;
begin
OneShadow (cubeX + cubeL, cubeY + cubeH, cubeZ, cubeW, x1, y1);
OneShadow (cubeX, cubeY + cubeH, cubeZ, cubeW, x2, y2);
OneShadow (cubeX, cubeY, cubeZ, cubeW, хЗ, уЗ);
OneShadow (cubeX + cubeL, cubeY, cubeZ, cubeW, x4, y4);
// пол на уровне -1 по оси Z, тень рисуется над полом, -0.99 по оси Z
If cubeZ + cubeW >= 0 then begin
glBegin (GL_QUADS); // тень от верхней грани объекта
glVertex3f (x1, y1, -0.99);
glVertex3f (x2, y2, -0.99);
glVertex3f (хЗ, уЗ, -0.99);
glVertexSf (x4, y4, -0.99);
glEnd;
end;
If cubeZ >= 0 then begin wrkx1 := x1;
wrky1 := y1;
wrkx2 := x2;
wrky2 := y2;
wrkx3 := хЗ;
wrky3 := уЗ;
wrkx4 := х4;
wrky4 := у4;
OneShadow (cubeX + cubeL, cubeY + cubeH, cubeZ, 0, x1, y1) ;
OneShadow (cubeX, cubeY + cubeH, cubeZ, 0, x2, y2) ;
OneShadow (cubeX, cubeY, cubeZ, 0, хЗ, уЗ) ;
OneShadow (cubeX + cubeL, cubeY, cubeZ, 0, x4, y4) ;
glBegin (GL_QUADS);
glVertex3f (x1, y1, -0.99) // нижняя грань
glVertexSf (x2, y2, -0.99)
glVertexSf (хЗ, уЗ, -0.99)
glVertex3f (x4, y4, -0.99)
glVertex3f (wrkx2, wrky2, -0.99); // боковые грани
glVertex3f (x2, y2, -0.99)
glVertexSf (хЗ, уЗ, -0.99)
glVertex3f (wrkx3, wrky3, -0.99);
glVertex3f (wrkx1, wrky1, -0.99);
glVertex3f (wrkx4, wrky4, -0.99);
glVertexSf (x4, y4, -0.99);
glVertex3f (x1, y1, -0.99);
glVertexSf (wrkx1, wrky1, -0.99);
glVertex3f (x1, y1, -0.99);
glVertex3f (x2, y2, -0.99);
glVertexSf (wrkx2, wrky2, -0.99);
glVertexSf (wrkx3, wrky3, -0.99);
glVertexSf (хЗ, уЗ, -0.99);
glVertexSf (x4, y4, -0.99);
glVertexSf (wrkx4, wrky4, -0.99);
glEnd;
end;
end;
Этот пример не универсальный, край пола я намеренно убрал за грани:, экрана, чтобы скрыть ошибку, возникающую при приближении тени к этому краю.
Перейдем к следующему примеру, проекту из подкаталога Ех70, результат работы которого представлен на Рисунок 4.44.
Фонтан точек, точки полупрозрачны
Окно снабжено всплывающим меню, один из пунктов которого позволяет отключить режим смешения. Можете сравнить результат
Режим смешения часто используется для сглаживания краев объектов. Проект из подкаталога Ех45 демонстрирует эту возможность на примере точек, которые выводятся со сглаженными, нерезкими, краями.
Помимо собственно режима сглаживания точек, в программе также включается режим смешения цветов:
glEnable (GL_POINT_SMOOTH);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glHint (GL_POINT_SMOOTH_HINT, GL_DONT_CARE);
glPointSize (3.0);
Замечание
Команда glHint используется для уточнения режимов отработки некоторых операций, например, если нужно выполнить операцию максимально быстро или добиться наилучшего результата. В данном случае не задается определенного предпочтения.
В качестве упражнения можете удалить включение режима смешения, чтобы увидеть, что же все-таки добавляется с его включением.
Проект из подкаталога Ех46 продолжает нашу тему, но здесь выводятся линии со сглаженными краями. Перед началом работы приложение выводит информацию о степени детализации линий и диапазоне возможной толщины линий:
glGetFloatv (GL_LINE_WIDTH_GRANULARITY, Svalues);
ShowMessage(Format ('GL_LINE_WIDTH_GRANULARITY value is %3.lf,
[values[0]]));
glGetFloatv (GL_LINE_WIDTH_RANGE, @values);
ShowMessage(Format ('GL_LINE_WIDTH_RANGE values are %3.1f %3.lf,
[values[0], values[1]])); glEnable (GL_LINE_SMOOTH);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
glLineWidth (1.5);
Теперь решим несколько несложных задач по теме альфа-смешивания. Рисунок 4. 30 показывает картинку работы приложения, полученного после компиляции проекта из подкаталога Ex48, где полупрозрачная сфера вращается вокруг непрозрачного конуса.
Визуальные эффекты
Эта глава посвящена тому, как повысить качество получаемых образов и как получить некоторые специальные эффекты Приступать к ней стоит только после того, как материал предыдущих глав основательно изучен Примеры к главе помещены на дискете в каталоге Chapter4.
Искажения образа текстуры позволяют добиться разнообразных эффектов
Для движения узлов поверхности необходимо менять значения элементов массива контрольных точек, после чего вычислитель также необходимо "перезаряжать".
Для нанесения текстуры на поверхность требуется вспомогательный массив с координатами текстуры, а также включение специального режима.
В программе рисование объектов сводится к одной строке:
glEvalMesh2(GL_FILL, 0, 20, 0, 20); // вывод поверхности
В обработчике таймера пересчитываются координаты опорных точек поверхности, а также меняются координаты текстуры (для искажения образа):
A := А + 0.5; // увеличиваем управляющую переменную
init_surface; // пересчитываем координаты опорных точек
// ! - зарядить вычислитель новыми данными
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 17, 0, 1, 51, 17, @ctrlpoints);
// двигаем точки координат текстуры
texpts [O][0][0] :=texpts [O][0][0] -step;
texpts [0][0][1] :=texpts [0][0][1] -step;
// ограничиваем искажения некоторыми пределами
If (texpts [O][0][0] <-4.0) or (texpts [O][0][0] > 1.0)
then step := - step;
// принять во внимание измененные значения координат
glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1, 4, 2, @texpts);
При инициализации должна присутствовать строка с включением режима использования текстуры для поверхностей:
glEnable(GL_MAP2_TEXTURE_COORD_2);
Использование искажения координат текстуры открывает перед нами столь широкие возможности, что единственным ограничением является только наша фантазия.
Я бы мог привести еще массу примеров на использование текстуры в OpenGL, однако чувствую потребность остановиться, чтобы дать вам возможность самим попробовать свои силы.
Нет, все-таки не удержусь и приведу еще один (последний) пример, проект из подкаталога Ex96, иллюстрирующий, как подготовить текстуру на основе образа, считанного прямо с экрана, чтобы получить эффект увеличения (Рисунок 4.70).
Использование буфера трафарета для получения узоров на плоскости
Пример является иллюстрацией того, как можно использовать буфер трафарета для нанесения узоров на плоскость, одним из таких узоров является тень объекта.
На сцене присутствует модель самолета, земля, взлетная полоса, разметка взлетной полосы и тень от самолета, навигация в пространстве сцены осуществляется с помощью мыши.
По выбору пользователя на экран выводится содержимое буфера кадра, буфера трафарета или буфера глубины. В последних двух случаях для удобства восприятия выводится содержимое буферов не действительное, а преобразованное.
Нажимая на цифровые клавиши или выбирая пункт всплывающего меню, Можно останавливать воспроизведение сцены на каком-либо этапе.
Для упрощения программы я запретил изменение размеров окна, чтобы не Приходилось менять размеры массивов, предназначенных для хранения содержимого буферов.
В этом примере мы впервые встречаем команду glDrawBuffer, директивно задающую, в какой буфер будет осуществляться вывод. Хотя в этой программе можно вполне обойтись и без использования этой команды, воспользуемся случаем, чтобы с ней познакомиться Аргументом процедуры копирования содержимого буфера глубины является символическая константа, задающая буфер вывода Перед непосредственным выводом в рабочую переменную заносится имя текущего буфера вывода, чтобы затем по этому значению восстановить первоначальные установки
Как мы видели в одном из предыдущих примеров, при передаче содержимого буфера глубины фон залит белым цветом, объекты сцены как бы погружены в туман
В этом примере содержимое буфера глубины, вернее, массива, соответствующего этому содержимому, инвертируется, отчего восприятие картинки улучшается:
procedure TfrmGL.copyDepthToColor(whichColorBuffer : GLenum);
var
x, у : GLint; max, nun : GLfloat;
previousColorBuffer : GLint;
begin
// заполняем массив depthSave содержимым буфера глубины
glReadPixels(0, 0, winWidth, winHeight, GL_DEPTH_COMPONENT, GL_FLOAT,
@depthSave);
// инвертирование содержимого массива
depthSave max := 0;
nun := 1;
For у := 0 to winHeight - 1 do
For x := 0 to winWidth - 1 do begin
If (depthSave[winWidth * у + x] < nun)
then mm := depthSave[winWidth * у + x] ;
If (depthSave[winWidth * у + x] > max) and (depthSave[winWidth *
у + x] < 0.999)
then max := depthSave[winWidth * у + x] ;
end;
For у := 0 to winHeight - 1 do
For x := 0 to winWidth - 1 do If (depthSave[winWidth * у + x] <= max)
then depthSave[winWidth * у + x] := 1 - (depthSave[winWidth *
у + x] - mm) / (max - mm) else depthSave[winWidth * у + x] := 0;
// меняем проекцию, чтобы удобнее задавать позицию вывода растра
pushOrthoView(0, 1, 0, 1, 0, 1) ;
glRasterPos3f(0, 0, -0.5); // можно
glRasterPos2f(0, 0)
glDisable(GL_DEPTH_TEST); // вывод только в буфер кадра
glDisable(GL_STENCIL_TEST);
glColorMask(TRUE, TRUE, TRUE, TRUE);
// запоминаем текущий буфер вывода
glGetIntegerv(GL_DRAW_BUFFER, @previousColorBuffer);
glDrawBuffer(whichColorBuffer); // задаем требуемый буфер вывода
// выводим оттенками серого содержимое массива
glDrawPixels(winWidth, winHeight, GL_LUMINANCE, GL_FLOAT, @depthSave);
// восстанавливаем первоначальные установки glDrawBuffer(previousColorBuffer);
glEnable(GL_DEPTH_TEST);
popView; // восстанавливаем видовые параметры
end;
В буфере трафарета будут присутствовать целые значения в пределах от нуля до шести для объектов и значение $FF для фона, при выводе содержимого этого буфера на экране такие оттенки будут трудноразличимыми Поэтому для вывода используется вспомогательный массив, служащий для перевода цветовой палитры.
const // вспомогательный массив для передачи содержимого буфера трафарета
colors : Array [0..6, 0..2] of Byte = ( (255, 0, 0), // красный
(255, 218, 0), // желтый
(72, 255, 0), // желтовато-зеленый
(0, 255, 145), // голубоватый циан
(0, 145, 255), // цианово-синий
(72, 0, 255), // синий с пурпурным оттенком
(255, 0, 218) // красноватый пурпурный );
// процедура передачи буфера трафарета цветами
procedure TfrmGL.copyStencilToColor(whichColorBuffer : GLenum);
var
x, у : GLint;
previousColorBuffer : GLint;
stencilValue : GLint;
begin
// считываем в массиве stencilSave содержимое нужного буфера
glReadPixels(0,0,winWidth,winHeight,GL_STENCIL_INDEX,GLJJNSIGNED BYTE,
@ stencilSave);
// перевод значений в свою палитру
For у := 0 to winHeight - 1 do
For x := 0 to winWidth - 1 do begin
stencilValue := stencilSave (winWidth * у + x);
colorSavef[(winWidth * у + x)*3+0] := colors[stencilValue mod [7][0];
colorSave[(winWidth * у + x)*3+1] := colors[stencilValue mod 7][1];
colorSave[(winWidth * у + x)*3+2] := colors[stencilValue mod 7][2];
end;
// меняем матрицу проекций для задания позиции вывода растра
pushOrthoView(0, 1, 0, 1, 0, 1);
glRasterPos3f(0, 0, -0.5);
glDisable(GL_DEPTH_TEST); // вывод только в буфер кадра
glDisable (GL_STENCIL__TEST) ;
glColorMask(TRUE, TROE, TRUE, TRUE);
glGetIntegerv(GL_DRAW_BUFFER, glpreviousColorBuffer);
glDrawBuffer(whichColorBuffer); // передаем содержимое буфера трафарета в цвете
glDrawPixels(winWidth, winHeight, GL_RGB, GL_UNSIGNED_BYTE,ScolorSave) ;
// восстанавливаем первоначальные установки
glDrawBuffer(previousColorBuffer);
glEnable(GL_DEPTH_TEST);
popView;
end;
Базовая площадка, земля аэродрома, рисуется всегда, но менее удаленные объекты должны загораживать ее:
procedure TfrmGL.setupBasePolygonState(maxDecal : GLint);
begin
glEnable(GL_DEPTH_TEST);
If useStencil then begin
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, maxDecal + 1, $ff); // рисуется всегда
glStencilOp(GL_KEEP, GL_REPLACE, GL_ZERO);
end
end;
Все остальные объекты сцены в своих пикселах увеличивают значение буфера трафарета, загораживая более удаленные объекты
procedure TfrmGL.setupDecalState(decalNum : GLint);
begin
If useStencil then begin
glDisable(GL_DEPTH_TEST);
glDepthMask(FALSE);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_GREATER, decalNum, $ff) ;
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
end
end;
Воспроизведение объектов сцены не содержит для нас ничего нового, поэтому рассмотрим подробно только итоговый код воспроизведения кадра:
procedure TfrmGL.WMPaint(var Msg: TWMPamt) ;
var
ps : TPaintStruct; label // метка для передачи управления
doneWithFrame;
begin
BeginPaint(Handle, ps);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
// буфер трафарета очищается только при включенном режиме
If dataChoice = STENCIL
then glClear(GL_STENCIL_BUFFER_BIT);
glPushMatrix;
glScalef(0.5, 0.5, 0.5);
If stage = 1 then goto doneWithFrame; // выбран этап 1
setupLight;
setupNormalDrawingState; // без записи в буфер трафарета
glPushMatrix;
glTranslatef (0, 1, 4);
glRotatef(135, 0, 1, 0);
drawAirplane; // рисуем самолет
glPopMatrix;
If stage = 2 then goto doneWithFrame; // выбран этап 2
setupBasePolygonState(3) ; // 3 - для трех картинок на земле
drawGround; // рисуем землю
If stage = 3 then goto doneWithFrame; // выбран этап 3
setupDecalState(1); // наклейка 1 - асфальт взлетной полосы
drawAsphalt; // рисуем асфальт
If stage = 4 then goto doneWithFrame; // выбран этап 4
setupDecalState(2); // наклейка 2 - желтые полосы на асфальте
drawStripes; // рисуем полосы
If stage = 5 then goto doneWithFrame; // выбран этап 5
setupDecalState(3); // наклейка 3 - тень от самолета
glDisable(GL_LIGHTING); // тень рисуется без источника света
glEnable(GL_BLEND); // обязательно включить смешение цвета
glPushMatrix;
glColor4f(0, 0, 0, 0.5); // цвет тени - черный, альфа < 1.0
glTranslatef(О, О, 4); // сдвигаем систему координат под землю
glRotatef(135, О, 1, 0); // подгоняем систему координат для тени
glScalef(1, О, 1);
drawAirplane; // рисуем копию самолета - тень
glPopMatrix;
glDisable(GL_BLEND);
glEnable(GL_LIGHTING); (label) doneWithFrame:
setupNormalDrawingState; // восстановить нормальные установки
glPopMatrix;
// если выбран вывод буферов, все предыдущее затирается
If dataChoice = STENCIL then copyStencilToColor(GL_BACK);
If dataChoice = DEPTH then copyDepthToColor(GL_BACK);
SwapBuffers(DC);
EndPaint(Handle, ps);
end;
Надеюсь, этот пример помог вам лучше уяснить многие вопросы, связанные с буферами. Эффект отражения плоским зеркалом реализуется в OpenGL способом, похожим на то, что мы использовали для тени - объекты рисуются дважды, над и под поверхностью отражения. Для такого эффекта достаточно одного смешения цветов, буфер трафарета используется здесь для того, чтобы фальшивая система не выглядывала за пределами зеркальной поверхности.
На Рисунок 4. 47 показан один из моментов работы следующего примера, располагающегося в подкаталоге Ex73.
Использование патчей
Одним из главных достоинств сплайнов является то, что поверхность задается небольшим количеством опорных точек - весьма экономный подход
Замечание
Возможно, к достоинствам можно отнести и то, что нет необходимости самостоятельно рассчитывать нормали к поверхности, как это делалось в предыдущем примере
Вспомним пример предыдущей главы с холмообразной поверхностью Для хранения этой поверхности достаточно запомнить шестнадцать точек, если ее разбивать по треугольникам, то шестнадцати вершин окажется явно не достаточно для сохранения гладкости.
Подкаталог Ех29 содержит модификацию примера предыдущей главы на построение NURBS-поверхности. Отличает этот проект то, что в нем задаются свойства материала, так что поверхность выглядит гораздо эффектнее, также здесь добавилось то, что среди опорных точек одна выделяется и Рисуется красным цветом. Такую точку можно перемещать в пространстве нажимая на клавиши 'X', Т, 'Z', а также на эти клавиши совместно с <Shift>. Клавишами управления курсором можно задавать, какая из опорных точек является выделенной. Создавать кусочки поверхностей с помощью сплайнов - дело несложное, если попытаться подобрать опорные точки для того, чтобы нарисовать одним сплайном поверхности подобные чайнику, то это окажется занятием крайне трудоемким. Если же поверхность имеет негладкие участки, как, например, область соприкосновения носика чайника с его корпусом, то традиционными сплайнами это нарисовать окажется вообще невозможно. Имеется стандартный способ рисования поверхностей, сочетающий в себе достоинства использования сплайнов и лишенный его недостатков: поверхность разбивается на отдельные гладкие участки, для каждого из которых строится отдельный сплайн. Такие кусочки называются патчами (patch, заплатка).
В частности, модель чайника строится именно таким способом, по отдельным патчам, вы можете заглянуть в модуль для того, чтобы убедиться в этом. Следующий пример, проект из подкаталога Ex25, строит поверхность на основе патчей, опорные точки для которых считываются из текстового файла (Рисунок 4. 19).
Эффект полупрозрачности сквозь куб просматривается сфера заднего плана
Первоначально сфера находится на переднем плане, после нажатия клавиши А' сфера и куб меняются местами
При задании оптических свойств материала для сферы альфа-составляющая Устанавливается единичной, для куба - 06, поэтому куб выглядит полупрозрачным, а сфера - сплошной 204
В проекте из подкаталога Ех42 рисуются полупрозрачный цилиндр и непрозрачный тор, при щелчке кнопкой мыши меняется точка зрения. Принцип, используемый в этом проекте для получения эффекта полупрозрачности, ничем не отличается от приема предыдущего примера.
Следующий пример, располагающийся в подкаталоге Ех43, продолжает тему. На экране располагаются две сферы, красная вложена внутрь совершенно прозрачной, наружная сфера со временем мутнеет.
Пример очень простой: в обработчике таймера увеличивается значение переменной transparent, режим смешения цветов включается один раз в самом начале работы, а при воспроизведении кадра режим не переключается'
(красная сфера внутри}
glColorSf(1.0, 0.0, 0.0); // по умолчанию альфа =1.0
gluSpheref(qobj, 0.75, 20, 20);
{наружная сфера}
glColor4f(1.0, 1.0, 1.0, transparent);
gluSphere (qObj, 1.0, 20, 20);
Дальше нам следует разобрать проект из подкаталога Ех44, очень похожий на один из ранее разобранных примеров, в котором рисовался фонтан точек. Теперь точки стали полупрозрачными (Рисунок 4.29).
Эффект зеркального отражения можно распространять и на поверхности, покрытые текстурой
glPushMatrix;
gluLookAt(eyex, eyey, eyez, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
// рисуем стол в плоскости отражения
glEnable(GL_STENCIL_TEST); // буфер трафарета заполняем 1 там, где стол
glStencilFunc(GL_ALWAYS, 1, 1); // рисовать всегда, шаблон задаем = 1
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
// стол не рисуется в буфере кадра
glColorMask(FALSE, FALSE, FALSE, FALSE);
glCallList(table); // рисуем стол
glColorMask(TRUE, TRUE, TRUE, TRUE);
// точка отражения
If eyey > 0.0 then Begin
glPushMatrix;
glStencilFunc(GL_EQUAL, 1, 1); // рисуем при 1, только там, где стол
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glScalef(1.0, -1.0, 1.0);
draw_objects; // объекты сцены
glPopMatrix; end;
glDisable(GL_STENCIL_TEST);
glEnable(GL_BLEND); // на стол накладывается отражение
glCallList(table);
glDisable(GL_BLEND); // смотрим сверху
glPushMatrix;
draw_objects; // объекты рисуются обычным образом
glPopMatrix;
glPopMatrix;
Настала пора узнать, как в OpenGL можно использовать текстуру в качестве фона, и здесь нам поможет пример из подкаталога Ех94 (Рисунок 4.67).
Эта композиция станет тестовой для дополнительного примера на эффект фокуса
В проекте из подкаталога Ех64 нарисованы те же пять чайников, но в фокусе находится только второй из них, золотой, остальные объекты размыты.
Замечание
Для подготовки каждого кадра может потребоваться несколько секунд
Используются все те же самые процедуры AccFrustum и AccPerspective. Еще приведу несколько примеров на эту тему.
В проекте из подкаталога Ех65 мы не встретим ничего принципиально нового. Рисуется шестнадцать раз чайник с различных точек зрения, только не используется отдельный модуль, а массивы пикселных смещений описаны здесь же. Среди этих массивов вы можете найти дополнительные наборы данных, например, массив на 90 точек.
На Рисунок 4.40 приведен результат работы программы из подкаталога Ех66, также приводимой только для закрепления темы.
Эта композиция станет тестовой для примеров на буфер накопления
Здесь нет особых приемов, работа с буфером накопления отсутствует, но эта программа послужит основой для следующих примеров
Вот в проекте из подкаталога Ех61 выводится та же самая картина, но нерезкой, размытой
Замечание
Возможно, в примере эффект мало заметен, но после того, как разберем программу, вы сможете его усилить.
Чтобы смазать изображение, в буфере накопления одна и та же сцена рисуется несколько раз, каждый раз с немного измененной точкой зрения. Например, чтобы изображение двоилось, надо нарисовать сцену два раза под двумя точками зрения Чем больше мы возьмем таких точек зрения, тем более ровным окажется размытость, но такие эффекты, конечно, затормаживают воспроизведение
Можно пожертвовать качеством изображения, но повысить скорость взять небольшое количество точек зрения, но увеличить расстояние между ними в пространстве. В примере используется модуль jitter, переложение на Delphi известного модуля jitter h Модуль содержит в виде констант набор массивов, хранящих координаты точек зрения, а точнее - смещений точки зрения, разбросанных вокруг нуля по нормальному (Гаусса) закону. Массивов всего семь, массив с именем j2 содержит данные для двух точек зрения, j66 содержит j66 таких точек. Тип элементов массивов следующий:
type
jitter_point = record
х, у : GLfloat;
end;
В нашем примере введена константа, задающая, сколько "кадров" будет смешиваться в буфере накопления:
const
ACSIZE = 8;
В примере установлена ортографическая проекция:
procedure TfrmGL.FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadldentity;
If ClientWidth <= ClientHeight
then glOrtho (-2.25, 2.25, -2 25*ClientHeight/ClientWidth,
2.25*ClientHeight/ClientWidth, -10.0, 10.0)
else glOrtho (-2.25*ClientWidth/ClientHeight, 2.25*ClientWidth/ClientHeight, -2.25, 2.25, -10.0, 10.0);
glMatrixMode(GLJMODELVIEW);
InvalidateRect(Handle, nil, False);
end;
Из параметров команды glOrtho видно, что расстояния между левой и правой, верхней и нижней плоскостями отсечения одинаковы и равны 4 5 Это число мы будем использовать в качестве базового при кодировании эффекта размытости, как масштаб для перевода в мировые координаты:
Procedure TfrmGL.DrawScene;
var
viewport : Array[0..3] of GLint;
jitter of GLint, 222
begin
glGetlntegerv (GL_VIEWPORT, viewport) ;
// viewport[2] = ClientWidth
// viewport[3] = ClientHeight
glClear(GL_ACCUM_BUFFER_BIT);
For Jitter := 0 to ACSIZE - 1 do begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glPushMatrix;
// Эта формула преобразовывает дробное перемещение пиксела
//в мировые координаты
glTranslatef (38[jitter].x*4.5/viewport[2],
38 [jitter] .yM.5/viewport[3] , 0.0) ;
displayObjects; // рисуем сцену
glPopMatrix;
glAccum(GL_ACCUM, 1.0/ACSIZE); // в буфер накопления
end;
glAccum (GL_RETURN, 1.0); // буфер накопления - в буфер кадра
SwapBuffers(DC); // вывод кадра на экран
end;
Обратите внимание, что коэффициент яркости при заполнении буфера накопления задается таким образом, что его итоговое значение будет равно единице, т. е результирующие цвета не будут искажены.
Замечание
В примере используется массив из восьми точек, если на вашем компьютере этот пример работает слишком медленно, можете уменьшить это число до трех, а базовое число 4 5 в формулах преобразования увеличить раза в три.
Если для вас важно качество изображения, но пауза перед воспроизведением кадра недопустима, можете отказаться от двойной буферизации, а команду SwapBuffers заменить на glFlush.
Думаю, теперь этот пример для вас понятен, и мы можем перейти к следующему, где буфер накопления используется для получения эффекта фокуса Проект из подкаталога Ех62 содержит программу, рисующую всё ту же композицию. Первоначально изображение ничем не отличается от картинки из предыдущего примера.
Для получения эффекта необходимо задать базовую точку в пространстве, находящуюся в фокусе, или расстояние от наблюдателя до этой точки Точка зрения немного переносится в пространстве, перспектива немного усиливается Чем сильнее усиливается коэффициент перспективы, тем менее узкая область пространства не теряет резкость Сцена воспроизводится в искаженной перспективе, и процесс повторяется несколько раз с разных точек зрения. Для облегчения кодирования в программе написаны две вспомогательные процедуры, основная из которых AccFrustum. Первые шесть аргументов процедуры идентичны аргументам команды glFrustum.
Аргументы pixdx и pixdy задают смещение в пикселах для нерезкости Оба устанавливаются в нуль при отсутствии эффекта размытости Параметры eyedx и eyedy задают глубину области, в пределах которой сцена не размывается, если оба они равны нулю, то на сцене не будет присутствовать область, находящаяся в фокусе. Аргумент focus задает расстояние от глаза наблюдателя до плоскости, находящейся в фокусе Этот параметр должен быть больше нуля (не равен).
Поскольку процедура использует команду переноса системы координат, до ее вызова видовые параметры должны быть заданы с учетом будущего переноса:
procedure AccFrustum(left, right, bottom, top: GLdouble;
anear, afar, pixdx, pixdy, eyedx, eyedy: GLdouble; focus: GLdouble);
var
xwsize, ywsize : GLdouble; // размеры окна
dx, dy : GLdouble;
// для хранения параметров области вывода
viewport : array[0. 3] of GLint;
begin
glGetlntegerv (GL_VTEWPORT, @viewport); // получаем размеры окна
xwsize := right - left; // дистанция в пространстве по горизонтали
ywsize := top - bottom; // дистанция в пространстве по вертикали
// приращения для искажения перспективы
dx := -(pixdx*xwsize/viewport[2] + eyedx*anear/focus);
dy := -(pixdy*ywsize/viewport[3] + eyedy*anear/focus);
glMatrixMode(GL_PROJECTION);
glLoadldentity();
// меняем перспективу на чуть искаженную
glFrustum (left + dx, right + dx, bottom + dy, top + dy, anear, afar) ;
glMatrixMode(GLJMODELVIEW); glLoadldentity();
// переносим центр сцены
glTranslatef {-eyedx, -eyedy, 0.0);
end,
В принципе, этой процедуры достаточно, но для тех, кто привык пользоваться командой gluPerspective для задания перспективы, написана процедура AccPerspective, первые четыре аргумента которой идентичны аргументам gluPerspective Смысл остальных параметров мы разобрали выше:
procedure AccPerspective(fovy, aspect, anear, afar, pixdx, pixdy,
eyedx, eyedy, focus: GLdouble);
var
fov2,left,right,bottom,top : GLdouble;
begin
// половина угла перспективы, переведенного в радианы
fov2 := "fovy*Pi) / 180.0) / 2.0;
// рассчитываем плоскости отсечения
top := anear / (cos(fov2) / sin(fov2));
bottom := -top;
right := top * aspect;
left := -right;
AccFrustum (left, right, bottom, top, anear, afar, pixdx, pixdy, eyedx, eyedy, focus);
end;
Эта процедура вызывается перед каждым воспроизведением системы объектов. Для получения эффекта фокуса можете переписать вызов, например, так:
accPerspective (50.0, viewport[2]/viewport[3],
1.0, 15.0, ;j8 [jitter] .x, j8[jitter].у, 0.33*j8[jitter] .x, 0.33*;)8 [jitter] .у, 4.0);
Последний аргумент можете варьировать для удаления плоскости фокуса от глаза наблюдателя. Следующие два примера отличаются от предыдущих только наполнением сцены. В проекте из подкаталога Ех62 пока отсутствуют какие-либо эффекты, просто нарисовано пять чайников из различного материала, каждый из которых располагается на различном расстоянии от глаза наблюдателя (Рисунок 4.39).
Это один из самых интересных примеров
Программа демонстрирует, как использовать буфер трафарета для реализации логических операций с твердыми телами, на примере трех базовых фигур: куб, сфера и конус. Рисование фигур вынесено в отдельные процедуры, для манипуляций с ними введен процедурный тип:
type
proctype = procedure;
var
а: proctype = procCube;
b: proctype = procSphere;
Логическая операция OR заключается в том, что воспроизводятся обе фигуры
procedure procOR (a, b: proctype);
begin
glPushAttrib(GL_ALL_ATTRIB_BITS);
glEnable(GL_DEPTH_TEST);
а;
b;
glPopAttrib;
end;
Для получения части фигуры А, находящейся внутри В, предназначена следующая процедура:
procedure inside(a, b: proctype; face, test: GLenum);
begin
// рисуем А в буфере глубины, но не в буфере кадра
glEnable(GL_DEPTH_TEST);
glColorMask(FALSE, FALSE, FALSE, FALSE};
glCullFace(face);
а;
// буфер трафарета используется для нахождения части А, находящейся
// внутри В. Во-первых, увеличиваем буфер трафарета для передней
// поверхности В.
glDepthMask{FALSE);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 0, 0);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glCullFace(GL_BACK); // отсекаем заднюю часть В
b;
// затем уменьшаем буфер трафарета для задней поверхности
glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
glCullFace(GL_FRONT); // отсекаем переднюю часть В
b;
// теперь рисуем часть фигуры А, находящуюся внутри В
glDepthMask(TRUE);
glColorMask(TRUE, TRUE, TRUE, TRUE);
glStencilFunc(test, 0, 1);
glDisable(GL_DEPTH_TEST);
glCullFace(face);
glDisable(GL_STENCIL_TEST); // отключаем буфер трафарета
end;
Вспомогательная процедура fixup предназначена для регулирования содержимого буфера глубины, фигура выводится только в этот буфер:
procedure fixup (а: proctype);
begin
glColorMask(FALSE, FALSE, FALSE, FALSE); // отключаем вывод в кадр
glEnable(GL_DEPTH_TEST); // включаем тестирование глубины
glDisable(GL_STENCIL_TEST); // отключаем операции с трафаретом
glDepthFunc(GL_ALWAYS); // все точки фигуры - в буфер глубины а;
// рисуем фигуру только в буфер глубины
glDepthFunc(GL_LESS); // отключаем буфер глубины
end;
Логическая операция AND состоит в нахождении пересечения двух фигур: находим часть А, находящуюся внутри В, затем находим часть В, находящуюся внутри А:
procedure procAND (a, b: proctype);
begin
inside(a, b, GL_BACK, GL_NOTEQUAL);
fixup(b); // рисуем фигуру В в буфер глубины
inside(b, a, GL_BACK, GL_NOTEQUAL);
end;
Вычитание фигур реализовано так: находим часть А, находящуюся внутри В, затем находим ту часть задней поверхности В, что не находится в А.
procedure sub (a, b: proctype);
begin
inside(a, b, GL_FRONT, GL_NOTEQUAL);
fixup(b);
inside(b, a, GL_BACK, GL_EQUAL);
end;
Клавиши управления курсором предназначены для перемещения фигур в пространстве, регистровые клавиши <Shift> и <Alt> используются для выбора перемещаемой фигуры. Клавиша 'Z' ответственна за приближение точки зрения, набор фигур меняется при нажатии 'C' Здесь обратите внимание на то, как анализируется значение переменных процедурного типа:
If (@A = @procCube) and (@B = @procSphere) then begin
A: = procSphere;
В: = procCone;
end
else begin If (@A = SprocSphere) and (@B = @procCone) then begin
A := procCone;
В .= procCube,
end
else begin
A o= procCube;
В := procSphere;
end
end;
Думаю, этот пример окажется очень полезным для многих читателей
Рис 4.26 демонстрирует результат работы еще одного примера на буфер трафарета, проекта из подкаталога Ех38, в котором красиво переливается поверхность додекаэдра с приклеенной буквой "Т".
Этот пример посвящается всем девушкам-программисткам
В примере используются патчи из 25 точек каждый, первые двенадцать "заплаток" предназначены для построения лепестков розы, четырнадцать следующих - для стебля цветка. При описании дисплейного списка последовательно задаем нужный цвет:
glNewList (ROZA, GL_COMPILE);
glPushMatrix;
glScalef (0.5, 0.5, 0.5);
For i := 0 to 11 do begin // первые 12 патчей - лепестки
glColorSf (1.0, 0.0, 0.0); // задаем цвет красным
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 5, О, 1, 15, 5, Model.Items[i]);
glEvalMesh2(GL_FILL, 0, 20, 0, 20);
end,
For i := 12 to Model.Count - 1 do begin // стебель цветка
glColor3f (0.0, 1.0, 0.0); // цвет - зеленый
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 5, 0, I, 15, 5, Model.Items[i]);
glEvalMesh2(GL_FILL, 0, 20, 0, 20);
end;
glPopMatrix;
glEndList;
В последней главе книги будет еще один пример, где используются патчи, так что мы не прощаемся с этой темой насовсем.
Классический пример, иллюстрирующий свойства материала
Стоит сразу же обратить внимание, что при отключенном режиме GL_COLOR_ MATERIAL цветовой фильтр никак не влияет на освещенность. Для регулирования ее гаммы используется команда задания модели освещения со вторым аргументом, равным GL_LIGHT_MODEL_AMBIENT, при этом последний аргумент, массив четырех вещественных чисел, задает цветовую палитру:
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, @lmodel_ambient);
Сфера в первом ряду и первой колонке нарисована с отключенными фоновой и зеркальной составляющими материала Первый ряд, вторая колонка - диффузное и зеркальное освещение, следующая сфера более блестящая. Последняя сфера первого ряда - включены диффузия и эмиссия. Фоновая и диффузная составляющие включены для первой сферы второй линии, нет зеркальной составляющей Вторая и третья сферы - все включено, кроме эмиссии, различаются размером блика. У последней сферы второго ряда отсутствует зеркальная составляющая.
Сферы последней линии отличаются усиленностью цветовой насыщенности вставляющих свойств материала.
Еще один классический пример, проект из подкаталога Ех10 двадцать чайников из различного материала (Рисунок 4.6).
Металлические детали покрывайте текстурой для повышения зрелищности
Значения всех параметров текстуры задаются такими, которые обеспечивают максимальное качество изображения:
// текстуру накладывать медленно, но точно
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// смешивать накладывающиеся цвета
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
// карта координат подобна сфере
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable(GL_TEXTURE_2D);
// иначе будет нарисована цилиндрическая Земля
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
Обратите внимание, что в этом примере перемещается не объект, а точка зрения наблюдателя:
glPushMatrix;
gluLookAt(lO*cos(spin), 5*cos(spin)*sin(spin), 15*sin(spin),
0.0, 0.0, 0.0, 1.0, 0.0, 0.0); glCallList(CyList);
glPopMatrix;
Не забывайте об этом примере, когда будете рисовать модели, где присутствуют цилиндрические детали, сделанные из металла.
Поверхности, покрытые текстурой, вполне пригодны для создания специальных эффектов. В проекте из подкаталога Ex93 на такой поверхности видно отражение объектов, располагающихся над ней (Рисунок 4.66). Само создание эффекта традиционно и заключается в том, что объекты сцены рисуются дважды, а для того чтобы скрыть от наблюдателя эту хитрость, используется буфер трафарета:
Наши астрономические модели становятся все более совершенными
Источник света располагается внутри звезды, чем объясняется эффектность освещения планеты и спутника, тени и блики на их поверхностях в точности соответствуют положению в пространстве относительно звезды. Не пропустите важный момент: для сферы, моделирующей звезду, задается свойство материала, соответствующее излучающей составляющей материала:
const
sColor: array [0..3] of GLfloat = (1, 0.75, 0, 1);
black: array [0..3] of GLfloat = (0, 0, 0, 1);
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, @sColor);//излучение света
glutSolidSphere(0.8, 32, 16); // солнце
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, @black);// отключить
Благодаря этому наша звезда действительно светится.
На Рисунок 4.10 представлен результат работы следующего примера, проекта из подкаталога Ех17.
Объекты можно рисовать стеклянными
Команда glclear вызывается сразу же после того, как заполнен массив Поскольку процедура SwapBuffers не вызывалась, все предыдущие манипуляции остаются для пользователя незамеченными.
Можете в двух командах glclear манипулировать аргументами, не стирая каждый раз оба буфера, возможно, вам пригодятся получающиеся эффекты.
Объекты сцены имеют различные свойства материала
В программе заданы два массива, определяющие различные цветовые гаммы. Перед воспроизведением элемента задаются свойства материала:
const
MaterialCyan : Array[0..3] of GLfloat = (0.0, 1.0, 1.0, 1.0);
MaterialYellow : Array[0..3] of GLfloat = (1.0, 1.0, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @MaterialCyan);
glutSolidCube (2.0); // зеленоватый кубик
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @MaterialYellow);
// три желтых цилиндра
glTranslatef (0.0, 0.0, -2.0);
gluCylinder (qObj, 0.2, 0.2, 4.0, 10, 10);
glRotatef (90, 1.0, 0.0, 0.0);
glTranslatef (0.0, 2.0, -2.0);
gluCylinder (qObj, 0.2, 0.2, 4.0, 10, 10);
glTranslatef (-2.0, 0.0, 2.0);
glRotatef (90, 0.0, 1.0, 0.0);
gluCylinder (qObj, 0.2, 0.2, 4.0, 10, 10);
Теперь нам необходимо вернуться немного назад и обсудить положение источника света, задаваемое массивом четырех вещественных чисел. Если последнее число равно нулю, то, согласно документации, свет рассматривается как направленный источник, а диффузное и зеркальное освещение рассчитываются в зависимости от направления на источник, но не от его действительного положения, и ослабление заблокировано.
Посмотрим на практике, что это значит примером будет служить проект из подкаталога Ех08: на экране рисуется два четырехугольника, один покрыт равномерно серым, на поверхности второго видны блики (Рисунок 4. 4)
Объекты сцены освещаются источниками различной фоновой интенсивности
Пример посвящен фоновой интенсивности света. Во-первых, задается полная интенсивность, как свойство источника света:
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, @global_ambient);
Перед воспроизведением каждого чайника задается свое, локальное значение фоновой составляющей материала. Это значение минимально для верхнего объекта и максимально для нижнего.
В примере встречаем новую для нас команду:
glFrontFace (GL_CW);
В данном случае она никак не влияет на работу приложения, но дает нам повод подробнее изучить то, как задаются передняя и задняя стороны многоугольников (в предыдущих главах мы уже об этом немного говорили).
По умолчанию обход вершин против часовой стрелки задает лицевую сторону многоугольника. Вызов команды с аргументом GL_CW меняет порядок обхода на противоположный.
В проекте из подкаталога Ex18 с течением времени вращается квадрат, передняя сторона которого окрашена красным, задняя - синим. Если вершины перечислять в обратном порядке, квадрат развернется к наблюдателю задней стороной. To же самое произойдет, если нажать третью цифровую клавишу, в обработчике нажатия которой вызывается команда glFrontFace с аргументом GL_cw:
If Key = 49 then glEnable (GL_CULL_FACE); // нажата '1'
If Key = 50 then glDisable (GL_CULL_FACE); // нажата '2'
If Key = 51 then glFrontFace (GL_CCW); // нажата '3'
If Key = 52 then glFrontFace (GL_CW}; // нажата '4'
If Key = 53 then glCullFace (GL_FRONT); // нажата '5'
If Key = 54 then glCullFace (GL_BACK); // нажата '6'
При нажатии клавиши '1' включается режим отсечения, задние стороны многоугольников не рисуются. Клавиша '5' меняет правило отсечения, при включенном режиме отсечения не будут рисоваться передние стороны полигонов. Остальные клавиши позволяют вернуть режимы в значения, принятые по умолчанию.
Обратите внимание, что при переворотах квадрата нормаль необходимо разворачивать самому, иначе квадрат выглядит чересчур тускло. Код для этого можно скорректировать так:
If Key = 51 then begin
glFrontFace (GL_CCW); glNormal3f (0. 0, 0. 0, 1. 0);
end;
If Key = 52 then begin
glFrontFace (GL_CW);
glNorroal3f (0. 0, 0. 0, -1. 0);
end;
Переходим к следующему примеру, проекту из подкаталога Ex19. Здесь мы помимо того, что закрепим тему этого раздела, вспомним, как производить отсечение в пространстве.
На экране нарисованы три чайника, у каждого из них небольшая часть отсечена так, что можно заглянуть внутрь объекта (Рисунок 4. 11).
Объекты сцены в примере рисуются нерезкими
Здесь эффект нерезкости используется в анимационном приложении, нажатием на клавишу 'А' можно включать/выключать этот эффект.
Обычно эффект дымки используется для передачи глубины пространства
Пример действительно простой: в легкой дымке рисуется правильный многогранник. В таких картинках наблюдатель обычно путается с определением того, какие ребра фигуры более удалены, при использовании же дымки никакой неопределенности не возникает. Обратите внимание, что в этом примере мы снова встречаемся с функцией glHint; пожелание к системе OpenGL состоит в том, чтобы туман эмулировался с наилучшим качеством:
Procedure myinit;
const
fogColor : Array [0..3] of GLFloat = (0.0, 0.0, 0.0, 1.0); // цвет тумана
begin
glEnable(GL_FOG); // включаем туман
glFogi(GL_FOG_MODE, GL_LINEAR); // линейный закон распространения
glHint (GL_FOG_HINT, GL_NICEST); // пожелания к передаче тумана
glFogf (GL_FOG_START, 3.0); // передняя плоскость тумана
glFogf (GL_FOG_END, 5.0); // задняя плоскость тумана
glFogfv (GL_FOG_COLOR, @fogColor); // задаем цвет тумана
glClearColor(0.0, 0.0, 0.0, 1.0);
glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
end;
Отверстие в квадрате сделано с помощью буфера трафарета
Замечание
В таких примерах обычно не используют буфер глубины Замечу, что если необходимо на время отключать этот буфер можно воспользоваться командой glDepthMask c аргументом False
Еще одно замечание если вы столкнетесь с резким падением частоты воспроизведения данного и последующих примеров главы, это означает, что для соответствующих операций не предусмотрена акселерация. Решение задачи тривиально, важен порядок действий, определяемый сценарием:
glClear(GL_COLOR_BUFFER_BIT or GL_STENCIL_BUFFER BIT);
glPushMatrix;
If fRot then glRotatef(theta, 1.0, I 0, 0.0); // поворот площадки
glColor3f(l 0, 1.0, 0.0); // площадка желтого цвета
glStencilFunc(GL_ALWAYS, 1, 1); // площадка рисуется всегда
// из-за GL_ALWAYS первый аргумент безразличен
// второй аргумент безразличен, поскольку не используется буфер глубины
glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
glBegin(GL_QUADS); // собственно площадка
glNormal3f(0.0, 0.0, 1.0);
glVertexSf(0.0, 0.0, 0.0);
glVertex3f(100.0, 00, 00);
glVercexSf(100.0, 100.0, 0.0);
glVertex3f(0.0, 100.0, 0.0);
glEnd;
// отверстие 9lPushMatrix;
glTranslatef(50.0, 50.0, 0.0); // в центр площадки
glStencilFunc(GL_NEVER, 1, 1); // площадка не рисуется никогда
// важен только первый аргумент, любое значение
// кроме GL_KEEP и GL_REPLACE
glStencilOp(GL_DECR, GL_REPLACE, GL_REPLACE);
gluDisk(qObj, 10. 0, 20. 0, 20, 20); // диск отверстия
glPopMatrix;
glPopMatrix;
// вернулись в первоначальную систему координат, сфера не вращается
glPushMatrix;
glColor3f(1. 0, 0. 0, 0. 0); // сфера красного цвета
glTranslatef (45. 0, 40. 0, -150. 0);
// рисовать только там, где присутствует только фон
glStencilFunc (GL_NOTEQUAL, 1, 1);
// важен только первый аргумент
glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
gluSphere (qObj, 50. 0, 20, 20); // собственно сфера
glPopMatrix;
Это самый простой способ решения задачи. При увеличении радиуса отверстия фон под диском окрашивается в цвет площадки, так что пример подходит только для узкого круга задач.
На Рисунок 4. 23 показана экранная форма, появляющаяся при работе следующего примера по этой теме, проекта из подкаталога Ex34.
Площадка с тенью перемещается в пространстве
Следующий пример является прямым потомком предыдущего в проекте из подкаталога Ех71 площадка с тенью вращается вокруг системы объектов, как будто источник света также перемещается в пространстве (рис 4.45).
Обратите внимание, что в отличие от предыдущего примера объекты сцены окрашены в разные цвета. Чтобы этого не произошло с тенями объектов и они остались бы серыми, процедуру воспроизведения сцены я снабдил параметром, задающим, включать ли источник света и надо ли использовать цвета:
procedure DrawScene (light : Boolean);
begin
glPushMatrix;
If light then begin
glEnable (GL_LIGHTING);
glEnable (GL_LIGHTO);
glColor3f(l, 0.3, 0.5);
end;
glutsolidTorus(0.1, 0.2, 16, 16);
If light then glColorSf(0.5, 0.8, 0.8);
glTranslatef(0.05, 0.08,-0.2);
glutSolidSphere(0.05, 16, 16);
glTranslatef(0.2, 0.2, 0.4);
glutsolidSphere(0.1, 16, 16);
glTranslatef(0.3, 0.3, -0.2);
If light then begin glDisable (GL_LIGHTO);
glDisable (GL_LIGHTING);
end;
glPopMatrix;
end;
При воспроизведении кадра нет никаких хитрых перемножений матриц, система координат ложной сцены перемещается вслед за площадкой и производится масштабирование:
procedure TfrmGL.WMPaint(var Msg: TWMPaint);
var
ps : TPaintStruct;
begin
BeginPaint(Handle, ps);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glPushMatrix;
glPushMatrix; // пол
glColor3f(0.8, 0.8, 1) ;
glRotatef(Angle, 0, 1, 0) ;
glBegin(GL_POLYGON);
glVertex3f(0.5, -0.5, 0.5);
glVertex3f(0.5, 0.5, 0.5);
glVertexSf(0.5, 0.5, -0.5);
glVertex3f(0.5, -0.5, -0.5);
glEnd;
glPopMatrix; glPushMatrix; // тень
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glColor4f(0, 0, 0, 0.4);
glTranslatef(0.5*c, 0, 0.5*a); // перемещение для тени
glScaled(abs(a),1,abs(с)); // отобразить изменения в расстоянии
glDisable(GL_DEPTH_TEST);
glRotatef(AngleSystem, 1, 1, 1) ;
DrawScene (False);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glPopMatrix;
glRotatef(AngleSystem, 1, 1, 1); // собственно система объектов
DrawScene (True);
glPopMatrix; SwapBuffers(DC); EndPaint(Handle, ps);
Angle := Angle +5; // угол поворота площадки
If Angle >= 360.0 then Angle := 0.0;
AngleSystem := AngleSystem + 2.5; // угол поворота системы объектов
If AngleSystem >= 360.0 then AngleSystem := 0.0;
a := -sin(Angle * Pi/180);
b := 0;
с := cos(Angle * Pi/180);
InvalidateRect(Handle, nil, False); // зацикливаем программу
end;
Замечание
Обратите внимание, что в обоих примерах тень рисуется всегда, даже тогда, когда выходит за границы пола.
Переходим к проекту из подкаталога Ех72, очень интересному примеру, результат работы которого представлен на Рисунок 4.46.
Подробнее о пиксельных операциях
В этом разделе мы рассмотрим несколько полезных примеров, имеющих отношение не только к функциям OpenGL работы с пикселами, но и к некоторым другим темам.
Начнем с проекта из подкаталога Ех54, иллюстрирующего, как можно комбинировать различные проекции В примере используются ортографическая и перспективная проекции
На экране присутствуют прямоугольная площадка и система из двух объектов - сферы и цилиндра (Рисунок 4.33).
Подробнее о поверхностях произвольной формы
В предыдущей главе уже были рассмотрены несколько примеров построения поверхности произвольной формы. В этом разделе мы продолжим этот разговор.
На Рисунок 4.16 представлен результат работы программы - проекта Ех27, моей модификации широко известной программы isosurf.c.
Подробнее об источнике света
Начнем с позиции источника света в пространстве. Раньше мы пользовались источником света со всеми характеристиками, задаваемыми по умолчанию Источник света по умолчанию располагается в пространстве в точке с координатами (0, 0, 1)
Поучимся менять позицию на примере проекта из подкаталога Ex01. На экране присутствует тор, вокруг которого вращается источник света Позиция источника света визуализируется каркасным кубиком. Игра теней на поверхности тора меняется в зависимости от текущего положения источника света.
Замечание
Напомню если режим GL_COLOR_MATERIAL не включен, то текущие цветовые установки не влияют на цвет поверхности тора, поэтому он выглядит серым хотя текущий цвет задан зеленоватым.
Переменная spin задает угол поворота системы координат, связанной с источником света по оси X Положение источника света в этой системе координат определено в массиве position
const
position : Array [0..3] of GLfloat = (0, 0, 0, 0, 1 5, 1.0);
Перерисовка кадра выглядит так:
glPushMatrix; // запомнили мировую систему координат
glRotated (spin, 1.0, 0.0, 0.0); // поворот системы координат
glLightfv (GL_LIGHTO, GL_POSITION, Sposition), // задаем новую позицию
// источника света glTranslated (0.0, 0.0, 1.5); // перемещаемся в точку, где
// располагается источник света
glDisable (GL_LIGHTING); // отключаем источник света
glutWireCube (0.1); // визуализируем источник света
glEnable (GL_LIGHTING); // включаем источник света
glPopMatrix; // возвращаемся в мировую систему координат
glutSolidTorus (0.275, 0.85, 8, 15), // рисуем тор
Требуются некоторые пояснения Кубик рисуется с отключенным источником света для того, чтобы он не получился таким же, как тор, серым. Большинство параметров источника света задается с помощью команды glLightfv Первый параметр команды - идентификатор источника, второй аргумент - символическая константа, указывающая, какой атрибут устанавливается, последним аргументом задается ссылка на структуру, содержащую задаваемые значения
Как видно из кода, для задания позиции символическая константа должна быть GL_POSITION, указываемый массив определяет позицию источника света в текущей системе координат Источник света, подобно невидимому объекту, располагается в заданной точке и не перемещается вслед за системой координат. После того как его позиция зафиксирована, трансформации системы координат не приведут к изменению положения источника.
В этом примере значения элементов массива не изменяются, а система координат перемещается с течением времени Можно и наоборот - изменять значения элементов массива, а систему координат не трогать. Это сделано в следующем примере, проекте из подкаталога Ех02, где положение источника света задается значениями элемента массива LightPos, изменяющимися с течением времени:
With frmGL do begin
LightPos[0] := LightPos[0] + Delta;
If LightPos[0] > 15.0
then Delta := -1.0
else If (LightPos[0] < -15 0) then
Delta := 1.0,
InvalidateRect(Handle, nil, False);
end,
Перерисовка кадра начинается с того, что задается текущее положение источника света:
glLightfv(GL__LIGHTO, OPPOSITION, @LightPos}; glCallList(Sphere);
В примере источник света колеблется над поверхностью сферы. Теперь настала пора разобрать параметры источника света, связанные с оптическими характеристиками окружающей среды Эти характеристики складываются из трех отражений: фоновое, диффузное и зеркальное Символьные константы GL_AMBIENT, GL_DIFFUSE и GL_SPECULAR, указанные в качестве второго аргумента команды glLight, позволяют задавать требуемые свойства окружающей среды.
Наиболее важными для нас пока являются первые две характеристики Я приведу их упрощенное толкование, которого вам будет вполне достаточно для успешного использования соответствующих команд. Конечно, эти понятия имеют физическое обоснование, и неплохо было бы разобраться в нем, но пока можно ограничиться и поверхностным представлением Вес цвета в диффузной составляющей задает, насколько сильно этот цвет отражается поверхностью при ее освещении. И наоборот, вес цвета в фоновой составляющей задает, насколько сильно этот цвет поглощается поверхностью.
По умолчанию поверхность ничего не поглощает и все отражает. Следующий пример, проект из подкаталога Ех03, является развитием предыдущего, в нем добавлена возможность задания значений для характеристик источника цвета.
Окно приложения снабжено всплывающим меню. По выбору пункта меню появляется диалог задания цвета; выбранный пользователем цвет устанавливается значением нужной характеристики источника света. Здесь я воспользовался разобранной во второй главе процедурой, которая переводит системный цвет в диапазон, обычный для OpenGL:
procedure TfrmGL.AmbientlClick(Sender: TObject);
begin
If ColorDialogl.Execute then ColorToGL (ColorDialogl.Color, Ambient [0], Ambient [I], Ambient 12);
end;
С помощью этого проекта можно подбирать свойства источника света для дальнейшего использования в других проектах. При выборе пункта Info появляется окно, в котором выводятся текущие характеристики источника света (Рисунок 4.1).
Получение тени с помощью специальных средств библиотеки OpenGL
Сцена воспроизводится для каждого кадра два раза, над полом окрашенной, под полом - бесцветной.
Для получения сероватой тени от объектов сцены используется смешение цветов и буфер трафарета. Рассмотрим, как это делается.
Параметры, задаваемые для смешения цветов, традиционны:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Буфер трафарета заполняется нулями до рисования тени, а когда рисуется тень, значение в буфере трафарета увеличивается для каждого пиксела, где она присутствует:
glClearStencil(0);
glStencilOp(GL_INCR, GL_INCR, GL_INCR);
glStencilFunc(GL_EQUAL, 0, $FFFFFFF);
Тестирование буфера трафарета используется для воспроизведения только тех пикселов, где значение в буфере трафарета равно нулю; поскольку значение увеличивается, то соответствующие пикселы исключаются из дальнейшего использования.
В результате каждый пиксел тени используется только один раз, чтобы тень получалась однотонной.
При нажатии на клавишу 'С' можно отключать использование буфера трафарета, в этом случае некоторые участки тени становятся темнее из-за того, что при смешении цвета значение альфа-компонента удваивается при наложении объектов. Булевская переменная usestencil является флагом, задающим режим использования буфера трафарета.
Для проекции объектов на плоскость пола используется пользовательская Матрица проекции с нулем на диагонали, чтобы проецировать Y-координату в нуль:
mtх[0,0] := 1.0;
mtx[1,1] := 0;
totx[2,2] := 1.0;
totх[3,3] := 1.0;
Поскольку объекты будут рисоваться дважды, код для их воспроизведения вынесен в отдельную процедуру DrawScene
Теперь перейдем непосредственно к коду кадра:
// рисуем пол, источник света отключаем, иначе пол чересчур темный
glPushMatrix;
glDisable (GL_LIGHTO);
glDisable (GL_LIGHTING);
glColor3f(0.8, 0.8, 1) ;
glBegin(GL_QUADS);
glVertex3f(-0.5, -0.5, 0.5);
glVertex3f(0.5, -0.5, 0.5);
glVertexSf(0.5, -0.5, -0.5);
glVertex3f(-0.5, -0.5, -0.5);
glEnd;
glEnable (GL_LIGHTING); glEnable (GL_LIGHTO);
glPopMatrix;
// рисуем объекты над полом, в красном цвете
glPushMatrix;
glColor3f(1, 0, 0);
glRotatef(Angle, 1, 1, 1) ;
DrawScene;
glPopMatrix;
// рисуем тень
If useStencil then begin
// используется ли буфер трафарета
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
end;
// проецируем объекты на пол
glPushMatrix;
glColor4f(0, 0, 0, 0.3); // цвет объектов задаем черным
glTranslatef(0, -0.5, 0);
glMultMatrixf(Smtx); // для корректной проекции на плоскость пола
glRotatef(Angle, 1, 1, 1);
glDisable(GL_DEPTH_TEST); // отключаем буфер глубины
DrawScene; // рисуем фальшивую сцену под полом
glEnable(GL_DEPTH_TEST); // возвращаем обычный режим
glPopMatrix; glDisable(GL_STENCIL_TEST);
Замечание
Поскольку тень рисуется на поверхности пола, на время ее воспроизведения отключается тестирование буфера глубины, как это делается всегда при воспроизведении соприкасающихся примитивов, иначе у тени появляется паразитный узор.
Пора признаться: я в детстве мечтал стать космонавтом
В этом примере все объекты сцены являются quadric-объектами.
Предлагаю вам дорисовать звезды
Должен сразу извиниться перед астрономами за множество допущенных в этой модели ошибок, с позиций современной науки она совершенно безграмотная, и использовать ее для уроков астрономии нельзя
В процедуру подготовки образа текстуры передается имя файла растра, создание образа текстуры включено в код процедуры
procedure TfrmGL.Preparelmage(bmap: string);
// тип для динамического массива, код подходит для всех версий Delphi
type
PPixelArray = ^TPixelArray;
TPixelArray = array [0..0] of Byte;
var
Bitmap : TBitmap;
Data : PPixelArray; // образ текстуры, размер заранее не оговариваем
BMInfo : TBitmapInfo; // заголовок файла
ImageSize : Integer;// вспомогательные переменные
Temp : Byte; // для перестановки цветов
MemDC : HOC; // вспомогательный идентификатор
begin
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile (bmap); // считываем образ из файла
with BMinfо.bmiHeader do begin
FillChar (BMInfo, SizeOf(BMInfo), 0); // считываем заголовок
biSize := sizeof (TBitmapInfoHeader);
biBitCount := 24;
biWidth := Bitmap.Width;
biHeight := Bitmap.Height;
ImageSize := biWidth * biHeight;
biPlanes := 1;
biCompression := BI_RGB;
MemDC := CreateCompatibleDC (0);
GetMem (Data, ImageSize * 3); // создаем динамический массив
try
GetDIBits (MemDC, Bitmap.Handle, 0, biHeight, Data, // считываем
BMInfo, DIB_RGB_COLORS); // в DIB-формат растр
// битового массива
For I := 0 to ImageSize - 1 do begin // переставляем цвета
Temp := Data [I * 3]; Data [I * 3] := Data [1*3+2];
Data [1*3+2] := Temp,
end;
glTexImage2d(GLJTEXTURE_2D, 0, 3, biWidth, // создаем образ
biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
finally
FreeMem (Data); // освобождаем память DeleteDC (MemDC);
Bitmap.Free;
end;
end;
end;
Это универсальная процедура и годится для любых форматов. В ней осуществляется сравнительно много действий, и вызывается она два раза - для подготовки рисования и планеты, и спутника Используются дисплейные списки:
Quadric := gluNewQuadric;
gluQuadricTexture (Quadric, TRUE);
// обычные параметры текстуры
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN__FILTER, GL_NEAREST),
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// включать режим текстуры для оптимизации надо было бы в кадре
glEnable(GL_TEXTURE_2D);
// список для Земли
glNewList (Earth, GL_COMPILE);
Preparelmage ('..\earth.bmp'); // подготавливаем образ
gluSphere (Quadric, 1 0, 24, 24); glEndList; // список для Луны
glNewList (Moon, GL_COMPILE);
Preparelmage ('..\moon.bmp'); // образ другой
glPushMatrix; // будет перемещение
glTranslatef (1.3, 1.3, 0.3);
gluSphere (Quadric, 0.2, 24, 24);
glPopMatrix; // возвращаемся на место
glEndList;
// после описания списков quadnc-объекты больше не нужны
gluDeleteQuadric (Quadric);
Это очень важно, поэтому хочу еще раз повторить дисплейные списки в таких случаях являются крайне эффективным решением, иначе бы нам приходилось подготавливать и переключать образы текстуры многократно, для каждого кадра.
При описании списка мы обращаемся к процедуре чтения файла Preparelmage Здесь мог бы быть и более обширный код, но в дисплейный список компилируется только одна единственная строка, строка с подготовкой текстуры При вызове списка все промежуточные строки не вызываются, файл не считывается и, в принципе, вы можете ею даже удалить, т. к. он больше не используется.
По сценарию Земля вращается вокруг своей оси, а Луна вращается в противоположную Земле сторону:
glPushMatrix;
glRotatef (-10, 0.0, 1.0, 0.0);
glRotatef (Angle, 0.0, 0.0, 1.0); // поворот Земли вокруг своей оси
glCallList(Earth); glPopMatrix;
// для Луны необходимо добавить вращение вокруг своей оси
glPushMatrix;
glRotatef (-Angle, 0.0, 0.0, 1.0); // угол вращения противоположный
glCallList(Moon); glPopMatrix;
Переходим к следующему примеру, проекту из подкаталога Ex89, в котором показывается работа с полупрозрачной текстурой
(Рисунок 4.62).
Пример использования одномерной текстуры
Всплывающее меню приложения содержит пункты, позволяющие менять ширину полосок, выбирать тип объекта из трех базовых, а также управлять одним из параметров текстуры - генерацией координаты s.
В пользовательской процедуре MakeTeximage подготавливается массив образа текстуры, а также задаются все связанные с ней параметры:
var
TexWidth : GLint =16; // ширина текстуры
GenSOn : Boolean = True; // генерировать ли координату s
// параметром процедуры является требуемая ширина текстуры,
// число должно быть степенью двойки
procedure MakeTeximage (TexImageWidth : GLint);
const // параметры текстуры
TexParams : Array [0..3] of GLfloat = (0.0, 0.0, 1.0, 0.0);
var
Texlmage : Array [0..128 * 3] of GLUbyte; // массив текстуры
3 : GLint; // вспомогательная переменная begin
3 := 0; // чередование красной и синей полосок
While ] < TexImageWidth * 3 - 1 do begin // заполнение массива образа
Texlmage [j] := 255; // красный
Texlmage [3 + 1] := 0; // зеленый
Texlmage [3 + 2] := 0; // синий
Texlmage [3 + 3] := 0; // красный
Texlmage [j + 4] := 0; // зеленый
Texlmage [: + 6] := 255; // синий
lnс (j, 6) ;
end;
// эти команды должны вызываться обязательно
glTexParameter (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameter (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL~NEAREST) ,'
// собственно создание текстуры
glTexImagelD (GL_TEXTURE_1D, 0, 3, TexImageWidth, 0, GL_RGB,
GL_UNSIGNED_BYTE, STexImage); // генерировать ли координату s
If GenSOn
then glEnable (GL_TEXTURE_GEN_S'
else glDisable (GL_TEXTURE_GEN_S);
// уточняем параметры для координаты s, чтобы текстура не выглядела,
// как штриховка многоугольников
glTexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGenfv (GL_S, GL_OBJECT_PLANE, @TexParams); // для поворота полосок
glEnable (GL_TEXTURE_1D); // включаем текстуру
end;
Здесь использован почти минимальный набор действий, которые необходимо выполнить, чтобы пользоваться текстурой; единственное, что можно удалить в этом коде, это строки, связанные с координатой s, но тогда пропадет возможность разворачивать полоски текстуры. Массив образа содержит значения RGB, задающие узор полосок (я задал значения для равных по ширине полосок красного и синего цвета) Команда glTexParameter позволяет задавать параметры текстуры Минимум, что надо сделать - задать значения для фильтров, как и написано в комментарии
Замечание
Если говорить точно, то первую строку можно удалить, но вторая строка должна присутствовать обязательно, даже и со значением, принятым по умолчанию.
Фильтры задают вид текстуры, когда площадь пиксела, на которую она накладывается, в одном случае больше, а в другом меньше элемента текстуры Эти значения я задал равными GL_NEAREST, что соответствует неточному расчету текстуры Другое возможное значение - GL_LINEAR, расчет будет точным, но за счет скорости. Если вы установите такое значение третьего параметра команды glTexParameter, то на границах полосок цвет будет плавно размываться.
Команда glTexImagelD вызывается каждый раз, когда надо задавать текстуру Для одномерной текстуры нужно использовать именно эту команду Ее первый параметр указывает на размерность, значение второго параметра, числа уровней детализации текстуры, никогда не меняйте и задавайте равным нулю Третий параметр задается равным трем или четырем для сохранения цвета, остальные значения приведут к потере цвета полосок. Дальше идет ширина текстуры; как видно из примера, она не обязательно должна совпадать с размером массива Пятый параметр, ширину границы, тоже рекомендую пока не трогать Шестой параметр задает формат данных, смысл остальных параметров понятен.
В общем, из всех параметров команды я рекомендую менять только ширину текстуры, ну и еще задавать имя массива
С текстурой связан набор координат, чаще всего вам потребуется работать с координатами s и t.
Группа команд glTexGen предназначена для установки функции, используемой для генерации координат текстуры Если этого не делать, то значение, задаваемое по умолчанию, приведет к тому, что текстура будет вести себя подобно штриховке, т. е не будет приклеена к поверхности В примере я взял значения аргументов команды, чаще всего используемые на практике, другие значения разберем позднее.
Для того чтобы полоски текстуры правильно разворачивались на сфере, перечисленных действий достаточно, но для цилиндра необходимо скорректировать карту расположения текстуры на объекте, в файле справки детально разбирается смысл всех параметров, связанных с координатами s и t.
Перед описанием списков вызывается команда, связанная с наложением текстуры на quadnc-объекты gluQuadricTexture (Quadric, TRUE);
Эта команда задает, генерировать ли координаты текстуры для таких объектов Если вы удалите эту строку, то при отключенной генерации координаты s текстуры quadnc-объекты (сфера и цилиндр) не будут покрыты текстурой.
Сейчас я вам советую дополнить набор объектов диском из библиотеки glu, а также многогранниками библиотеки glut и посмотреть, как текстура ложится на их поверхность
Координаты текстуры необходимо задавать для ее правильного отображения на поверхность. Введите в программу список для простого квадрата. Чтобы не было путаницы с координатами квадрата и текстуры, координаты вершин зададим отличными от 1. Для каждой вершины квадрата определяем ассоциированные вершины текстуры:
glBegin (GL_QOADS);
glTexCoord2d (0.0, 0.0); // s и t = 0
glVertex2f (-0.5, -0.5); // левый нижний угол квадрата
glTexCoord2d (1.0, 0.0); // s = 1, t = 0
glVertex2f (0.5, -0.5); // правый нижний
glTexCoord2d (1.0, 1.0); // s и t = 1
glVertex2f (0.5, 0.5); // правый верхний
glTexCoord2d (0.0, 1.0); // s = 0, t = 1
glVertex2f (-0.5, 0.5); // левый верхний
glEnd;
To есть для левого нижнего угла квадрата значения s и t нулевые, для правой нижней вершины значение t равно единице и т. д.
Теперь при отключенной генерации координаты s полоски правильно ложатся на квадрат. При включенном режиме значения всех элементов массива параметров должны быть нулевыми, за исключением первого. Для того чтобы перевернуть полоски, надо задавать ненулевым не первый, а второй элемент массива, если же оба их задать равными единице, полоски лягут по диагонали.
Теперь перейдем к двумерной текстуре. В проекте из подкаталога Ех82 поверхность многоугольников покрыта шашками (Рисунок 4.55).
Пример на использование буфера накопления
Пример очень простой: на экране нарисованы два разноцветных прямоугольника, частично перекрывающие друг друга, в области пересечения происходит смешение цветов (Рисунок 4.37). Первые две цифровые клавиши задают режим воспроизведения полигонов, линиями или сплошной заливкой. При инициализации работы приложения вызовом команды glclearAccum задается значение, которым заполняется буфер накопления. Воспроизведение кадра состоит в том, что прямоугольники рисуются в буфере кадра по отдельности, в буфере накопления изображения накапливаются. Ключевую роль здесь играет команда glAccum:
glClear (GL_COLOR_BUFFER_BIT) ;
glCallList(thingl); // красный прямоугольник
// буфер кадра загружается в буфер накопления с коэффициентом 0.5
glAccum (GL_LOAD, 0.5);
glClear(GL_COLOR_BUFFER_BIT); // экран очищается
glCallList(thing2); // зеленый прямоугольник
// наложение старого содержимого буфера накопления
//с содержимым буфера кадра
glAccum(GL_ACCUM, 0.5);
// содержимое буфера накопления выводится в буфер кадра
glAccum(GL_RETURN, 1.0);
Здесь требуются некоторые пояснения.
Если вторым аргументом команды glAccum задается GL_LOAD, старое содержимое буфера аккумуляции затирается, подменяется содержимым буфера кадра.
Первые три действия примера можно осуществлять и так, т. е. явным образом очищая буфер накопления.
// очищаются буфер кадра и буфер накопления
glClear(GL_COLOR_BUFFER_BIT or GL_ACCUM_BUFFER_BIT);
glCallList(thingl); // красный прямоугольник
// содержимое буфера кадра смешивается с содержимым буфера накопления
// (пустым)
glAccum(GL_ACCUM, 0.5);
Вторым параметром команды glAccum можно манипулировать для управления яркостью изображения, в данном примере полигоны помещаются в буфер накопления с коэффициентом 0 5, т е их яркость уменьшается вполовину. При итоговом вызове этой команды яркость можно увеличить, коэффициент может принимать значения больше единицы
Теперь перейдем к проекту из подкаталога Ех59, рисующему на экране несколько объектов из модуля GLUT (рис 4.38).
Пример на использование тумана
Рисунок 4.41 демонстрирует работу проекта из подкаталога Ех67: пять чайников, располагающихся от наблюдателя на различном удалении, атмосфера наполнена дымом.
Цвет дыма в примере задается серым, что является обычным значением при использовании тумана:
fogColor : array[0..3] of GLfloat = (0.5, 0.5, 0.5, 1.0);
При инициализации задаем параметры дымки:
glEnable(GL_FOG); // включаем режим тумана
fogMode := GL_EXP; // переменная, хранящая режим тумана
glFogi(GL_FOG_MODE, fogMode); // задаем закон смешения тумана
glFogfv(GL_FOG_COLOR, fogColor); // цвет дымки
glFogf(GL_FOG_DENSITY, 0.35); // плотность тумана
glHint(GL_FOG_HINT, GL_DONT_CARE);// предпочтений к работе тумана нет
Здесь при инициализации закон "затухания" тумана задается наиболее подходящим для обычных задач значением - GL_EXP, помимо этой константы могут использоваться еще GL_LINEAR и GL_EXP2 (в файле справки приведены соответствующие формулы, по которым происходит потеря цвета).
Приложение имеет всплывающее меню, позволяющее по выбору пользователя задавать различные законы затухания тумана. Обратите внимание, что для линейного закона задаются расстояния до ближней и дальней плоскостей отсечения, в пределах которых туман затухает по линейному закону, более удаленные предметы растворяются в дыму:
procedure TfrmGL.SelectFog(mode: GLint); // выбор режимов тумана begin
case mode of 1 : begin
glFogf(GL_FOG_START, 1.0); // ближняя плоскость для линейного
// затухания
glFogf(GL_FOG_END, 5.0); // дальняя плоскость для линейного
// затухания
glFogi(GL_FOG_MODE, GL_LINEAR); // линейный закон
InvalidateRect(Handle, nil, False);
end;
2 : begin
glFogi(GL_FOG_MODE, GL_EXP2);
InvalidateRect(Handle, nil, False);
end;
3 : begin
glFogi(GL_FOG_MODE, GL_EXP); InvalidateRect(Handle, nil, False); end;
0 : close;
end;
end;
Приведу еще один несложный пример, проект из подкаталога Ех68, иллюстрирующий, как туман можно использовать для передачи глубины пространства (Рисунок 4.42).
Пример на создание многократного отражения
Клавишами управления курсором можно регулировать, как глубоко распространяется это отражение, нажатием на клавишу 'Н' можно менять точку зрения, при взгляде сверху демонстрируются ложные дубликаты объектов.
Главной в этом примере является процедура draw_scene, используемая рекурсивно для многократного воспроизведения объектов сцены:
const
stencilmask : longint = $FFFFFFFF;
procedure TfrmGL.draw_scene(passes:GLint; cullFace:GLenum;
stencilVal:GLuint; mirror: GLint);
var
newCullFace : GLenum;
passesPerMirror, passesPerMirrorRem, i : GLint;
curMirror, drawMirrors : GLUint;
begin
// один проход, чтобы сделать реальную сцену
passes := passes - 1;
// рисуем только в определенных пикселах
glStencilFunc(GL_EQUAL, stencilVal, stencilmask);
// рисуем вещи, которые могут закрыть первые зеркала
draw_sphere;
draw_cone;
// определяем номер отражений зеркала
If mirror о -1 then begin
passesPerMirror := round(passes / (nMirrors - 1));
passesPerMirrorRem := passes mod (nMirrors - 1) ;
If passes > nMirrors - 1
then drawMirrors := nMirrors - 1
else drawMirrors := passes;
end
else begin
// mirror == -1 означает, что это - начальная сцена (не было
// никаких зеркал)
passesPerMirror := roundfpasses / nMirrors);
passesPerMirrorRem := passes mod nMirrors;
If passes > nMirrors
then drawMirrors := nMirrors
else drawMirrors := passes;
end;
i := 0;
While drawMirrors > 0 do begin
curMirror := i mod nMirrors;
If curMirror <> mirror then begin
drawMirrors := drawMirrors - 1;
// рисуем зеркало только в буфере шаблона
glColorMask(False, False, False, False);
glDepthMask(False);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
draw_mirror(mirrors[curMirror]);
glColorMask(True, True, True, True);
glDepthMask(True) ;
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // рисуем отраженную сцену
newCullFace := reflect_through_mirror(mirrors[curMirror], cull-Face) ;
If passesPerMirrorRemoO then begin
// рекурсивное обращение самой к себе
draw_scene(passesPerMirror + 1, newCullFace, stencilVal + 1, curMirror);
passesPerMirrorRem := passesPerMirrorRem - 1;
end
else draw_scene(passesPerMirror, newCullFace, stencilVal + 1,
curMirror);
// возвращаем видовые параметры
undo_reflect_through_mirror(mirrors[curMirror], cullFace);
// обратная сторона в нашей величине шаблона
glStencilFunc(GL_EQUAL, stencilVal, stencilmask);
end;
me (i) ;
end;
draw_room;
end;
В этой главе нас ждет еще один пример с использованием эффекта зеркального отражения, так что мы не прощаемся с этой темой окончательно.
Закончу раздел примером, в котором хотя и эмулируется зеркало, однако непосредственно эффекта отражения не создается
Откройте проект из подкаталога Ех75. После запуска откомпилированного модуля на экране появляется следующая картина по кругу, в центре которого находится глаз наблюдателя, вращаются четыре цветных кубика В верхней части экрана располагается прямоугольник, в котором как в зеркальце заднего вида отражается кубик, находящийся за спиной наблюдателя (Рисунок 4.49).
Простейший пример на операции с буфером трафарета
Инициализация работы приложения начинается с задания характеристик буфера трафарета
glClearStencil(0); // значение заполнения буфера трафарета при очистке
glStencilMask(l); // число битов, задающее маску
glEnable(GL_STENCIL_TEST); // включаем тест буфера трафарета
Первые две команды вызываются с аргументами, имеющими значение, принятое по умолчанию, поэтому могут быть удалены
Замечание
Аргументы этих команд имеют смысл битовых масок, поэтому корректнее задавать их шестнадцатеричными.
Первая команда задает фоновое значение, которым будет заполнен буфер при выполнении команды glclear с аргументом GL_STENCIL_BUFFER_BIT вторая команда разрешает или запрещает перезапись битов в плоскости трафарета
Теперь разберем код перерисовки кадра:
// очищаются буфер цвета и буфер трафарета
glClearf GL_COLOR_BUFFER_BIT or GL_STENCIL_BUFFER_BIT);
// треугольник
// тест всегда завершается положительно
glstencilFunc(GL_ALWAYS, 1, I);
// значение буфера устанавливается в 1 для всех точек треугольника
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glColor3ub(200, 0, 0); // цвет - красный
glBegin(GL_POLYGON);
glVertex3i(-4, -4, 0);
glVertex3i( 4, -4, 0);
glVertex3i( 0, 4, 0) ;
glEnd;
// зеленый квадрат
// только для точек, где в буфере записана единица
glStencilFunc(GL_EQUAL, 1, 1),
// для точек, не подпадающих в тест, значение буфера установить в
// максимальное значение;
// если тест завершился удачно, но в буфере глубины меньшее значение,
// сохранить текущее значение буфера трафарета;
// если в буфере глубины большее значение, задать значение нулевым
glStencilOp(GL_INCR, GLJKEEP, GL_DECR);
glColor3ub(0, 200, 0); glBegin(GL_POLYGON);
glVertex3i(3, 3, 0};
glVertex3i(-3, 3, 0);
glVertex3i(-3, -3, 0);
glVertex3i(3, -3, 0); glEnd;
// синий квадрат
// только для точек, где в буфере записана единица
glstencilFunc(GL_EQUAL, I, 1);
// для всех точек сохранить текущее значение в буфере трафарета
glStencilOp (GLJCEEP, GL_KEEP, GL_KEEP) ;
glColor3ub(0, 0, 200);
glBegin(GL_POLYGON);
glVertex3i(3, 3, 0) ;
glVertex3i(-3, 3, 0);
glVertex3i(-3, -3, 0);
glVertex3i(3, -3, 0);
glEnd;
Файл справки содержит описание всех возможных значений используемых в примере команд. В данном случае во всех точках треугольника значение буфера трафарета задается равным единице. Для зеленого квадрата тестирование завершается успешно только в точках, где уже записана единица, т е там, где был нарисован треугольник Во всех точках, выпадающих за границу треугольника, значение буфера трафарета будет задано в максимальное значение, но эти участки не будут нарисованы
Синий квадрат рисуется также с тестированием в точках, где записана единица, т е там, где рисовался треугольник или предыдущий квадрат Аргументы команды glstencilOp перед воспроизведением синего квадрата уже никак не влияют на характер изображения Перед рисованием зеленого квадрата операции в буфере зададим так:
glStencilOp(GL_INCR, GL_KEEP, GLJ3ECR);
Теперь для всех точек квадрата, не пересекающихся с треугольником, значение в буфере установится в нуль, и синий квадрат в этих точках не появится. Увеличьте произвольно размеры последнего квадрата и убедитесь, что он будет появляться только там, где были нарисованы предыдущие фигуры.
Замечание
Наверное этот раздел покажется поначалу сложным для понимания но хочу вас успокоить тем, что совсем не обязательно сразу же уяснить все тонкости работы с буфером трафарета Вы можете пока пробежаться по примерам чтобы затем вернуться к ним в случае необходимости и еще раз хорошенько все повторить. Сейчас мы попробуем решить одну и ту же задачу двумя немного различными способами Задача состоит в том, чтобы проделать отверстие в квадратной площадке.
На рис 4.22 представлен результат работы проекта из подкаталога Ех33 квадратная площадка с просверленным отверстием в виде кольца (на экране она вращается), сквозь которое видно красную сферу, расположенную позади площадки.
Простейший пример на смешение цветов
Как обычно, разобраться нам помогут примеры. Начнем с проекта из подкаталога Ех39, где на экране строятся два частично перекрывающихся треугольника (рис 4.27)
Диалог с OpenGL начинается с установки параметров смешивания:
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ;
Нет смысла приводить здесь перечень и смысл аргументов команды glBlendFunc, справка по этой команде достаточно подробна Там, в частности, говорится и о том, что прозрачность наилучшим образом осуществляется при использовании значений аргументов GL_SRC_ALPHA и GL_ONE_MINUS SRC_ALPHA, как сделано в рассматриваемом примере
Для обоих треугольников значение альфа установлено одинаковым, 0 75 Чем больше это число, тем ярче рисуются примитивы
Нажимая на клавишу Т, можно менять порядок построения треугольников, нарисованный первым располагается ниже и проглядывает сквозь следующий. Проект из подкаталога Ех40 во многом похож на предыдущий, только здесь нарисовано четыре фигуры, три прямоугольника и один квадрат В примере Дважды накладываются примитивы одной пары цветов, но в разном порядке, из-за чего различаются оттенки получающихся смесей. Следующие два примера также очень похожи друг на друга.В проекте из подкаталога Ех41 на сцене присутствуют два объекта непрозрачная сфера и полупрозрачный куб (рис 4 28).
Новичков такие картинки обычно впечатляют
Не будем рассматривать программу подробно, мы не встретим в ней ничего нового, однако пример этот не бесполезный - подобранный профессионалами набор материалов может вам пригодиться. В примерах пятой и шестой глав я использовал некоторые из этих материалов. Поскольку чайники появляются на экране сравнительно медленно, в этом примере не используется двойная буферизация. Дальше мы рассмотрим серию моих переложений на Delphi программ из набора примеров OpenGL SDK и закрепим наши знания по теме этого раздела. Начнем с проекта из подкаталога Ex11, на котором основаны и несколько следующих примеров. На экране располагаются конус, тор и сфера (Рисунок 4.7).
Эту композицию будем использовать в качестве тестовой
Все параметры, за исключением позиции источника света, задаются по умолчанию. Последнее число (w-компонент) в массиве, связанном с позицией источника света, равно нулю, источник света располагается на бесконечности.
Здесь все просто, можем перейти к следующему примеру, проекту из подкаталога Ex12. Объекты сцены окрашены в фиолетовый цвет, добавилась строка, задающая диффузную составляющую источника света:
glLightfv(GL_LIGHTO, GL_DIFFUSE, @light_diffuse);
В проекте из подкаталога Ex13 - другая картина, возникшая благодаря тому, что задается фоновое отражение, отличное от принятого по умолчанию:
glLightfv(GL LIGHTO, GL AMBIENT, @light ambient);
В последнем примере этой серии, проекте из подкаталога Ex14, модель тонирования задается значением, отличным от принятого по умолчанию:
glShadeModel (GL_FLAT);
На Рисунок 4. 8 показан получающийся результат - фигуры композиции потеряли гладкость своих форм.
Команда glShadeModel может существенно повлиять на получающиеся образы
Стоит сказать, что манипулирование свойствами источника света и оптическими свойствами материалов позволяет достичь весьма впечатляющих визуальных эффектов, поэтому давайте изучим данную тему максимально подробно.
В следующем простом примере, проекте из подкаталога Ех15, рисуется сфера с красивым бликом на поверхности. У источника света задается позиция, остальные параметры - по умолчанию. Материал определяется диффузной и зеркальной составляющими, размер блика описывается следующей строкой:
glMaterialf(GL_FRONT, GL_SHININESS, 25.0);
В этом примере есть то, чего мы ранее не встречали: здесь включается режим коррекции цвета материала и вызывается новая для нас команда:
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GLJ3IFFUSE);
Команда glColorMaterial задает, какая из характеристик материала корректируется текущим цветом. Фактически это парная команда к режиму GL_COLOR_MATERIAL, однако в предыдущих примерах мы использовали значение, принятое по умолчанию.
Замечание
С точки зрения оптимизации эта команда предпочтительнее команды glMaterial.
В примере первоначальные значения элементов массива diffuseMaterial определяют диффузные свойства материала, по нажатию клавиш 'R', 'G' и 'В' увеличивается вес красного, зеленого или синего в текущем цвете:
procedure changeRedDiffuse;
begin
diffuseMaterial[0] := diffuseMaterial[0] + 0.1;
If diffuseMaterial[0] > 1.0
then diffuseMaterial[0] := 0.0;
glColor4fv(@diffuseMaterial);
end;
Проект из подкаталога Ex 16 представляет собой еще одну модель планетной системы: вокруг звезды вращается планета, вокруг которой вращается спутник (Рисунок 4.9).
Можно заглянуть внутрь чайников
Вырезка осуществляется способом, знакомым нам по предыдущей главе:
glClipplane (GL_CLIP_PLANEO, @eqn);
glEnable (GL_CLIP_PLANEO);
Верхний объект нарисован без дополнительных манипуляций и его внутренности выглядят невзрачно.
Перед воспроизведением второго чайника включается режим расчета освещенности для обеих сторон поверхности. Материал для этих сторон задается одинаковым:
glLightModelf (GL_LIGHT_MODEL_TWO_SIDE, 1);
glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, @mat_diffuse);
Последний чайник отличается от предыдущего тем, что его внутренняя и Наружная поверхности покрыты разными материалами:
glMaterialfv (GL_FRONT, GL_DIFFUSE, @mat_diffuse);
glMaterialfv (GL_BACK, GL_DIFFUSE, @back diffuse);
Замечание
Для того чтобы увидеть внутренности объекта, необходимо следить, чтобы режим GL_CULL_FACE не был включен, иначе внутренние стороны многоугольников рисоваться не будут. В этом примере уже нельзя пренебречь вызовом glFrontFace, иначе второй и третий чайники будут вывернуты наизнанку.
Проект из подкаталога Ex20 является модификацией предыдущего примера, чайник здесь уже вращается и выглядит еще более живописным (рис 4.12)
Чайник снаружи изумрудный, внутри - золотой Такое увидишь только на экране монитора
Материалы для внутренней и внешней поверхностей чайника я взял из примера с двадцатью чайниками, приведенного выше.
Теперь мы изучили все, что необходимо для знакомства с классической программой, переложение которой на Delphi я поместил в подкаталог Ex2l Программа рисует группу из трех вращающихся шестеренок (Рисунок 4. 13)
Один из моментов работы проекта Gears
С помощью клавиш управления курсором и клавиши 'Z' можно изменять положение точки зрения в пространстве.
Для закрепления материала рассмотрим еще несколько примеров. Вы спокойно можете пропустить эту часть, если чувствуете, что предыдущих примеров оказалось достаточно для прочного овладения приемами работы с материалами.
Рис 4. 14 иллюстрирует работу программы - проекта из подкаталога Ex22, в которой происходят колебания и вращения системы из трех объектов.
Пример на колебания, объем фигур изменяется с помощью операции масштабирования
Первоначально объекты только колеблются, клавиша <Insert> позволяет управлять процессом вращения.
Для создания объектов системы используются функции библиотеки glu, quadric-объекты создаются перед описанием списков, после чего сразу же Удаляются.
Обратите внимание, что для колебательного изменения объема фигур используется масштабирование, задаваемое перед вызовом списков, в таких случаях нельзя забывать о включении режима пересчета нормалей:
9lEnable(GL_NORMALIZE);
Проект из подкаталога Ex23 является продолжением предыдущего примера, однако колебания системы здесь затухающие. Еще один несложный пример на свойства материала - проект из подкаталога Ex24 переносит все дальше в глубины космоса (Рисунок 4. 15).
Еще одна модель из отдельных кусочков
Программа написана мною, но автором модели являюсь не я, а Геннадий Обухов, адрес его страницы я указал в приложении 1.
Модель состоит из 444 патча размером 4x4, по шестнадцать точек. Для хранения данных о патчах введен пользовательский тип:
type
TVector = record
x, у, z: GLfloat; end;
TPatch = Array [0.. 15] of TVector;
Модель так же, как и в предыдущем примере, хранится в списке. Список заполняется данными, считываемыми из текстового файла, каждая строка которого содержит координаты очередной вершины. Эта программа тоже является в некотором роде универсальной: требование к файлу заключается в том, что точки должны записываться порциями по шестнадцать, каждая группа представляет собой опорные точки очередного патча.
Я не могу поместить на дискету дополнительные примеры к этой программе, текстовые файлы слишком велики, поясню только, как создаются подобные файлы данных.
Я использовал свободно распространяемый модельер sPatch, позволяющий использовать встраиваемые модули (plug-in) для экспортирования моделей, созданных в нем из патчей, в произвольный формат. В частности, этот модельер позволяет записывать и в формате dxf. Адрес, по которому можно получить sPatch, указан в приложении 1.
Ключевой процедурой разбираемого примера является процедура инициализации поверхности:
Procedure TfrmGL. Init_Surface;
var
f: TextFile;
1: Integer;
Model: TList; // список модели
wrkPatch: TPatch; // вспомогательная переменная, патч
PwrkPatch: ^TPatch; // указатель на патч 188
begin
Model := TList.Create; // создание списка модели
AssignFile (f, 'Parrot.txt'); // открытие файла
ReSet (f);
While not eof (f) do begin
For i := 0 to 15 do // точки считываются по шестнадцать
ReadLn (f, wrkPatch [i].x, wrkPatch [i].y, wrkPatch [i].z);
New (pwrkPatch); // выделение памяти под очередной элемент
pwrkPatch^ := wrkPatch, // задаем указатель на элемент
Model.Add (pwrkPatch); // собственно добавление в список
end;
CloseFile (f);
glNewList (SURFACE, GL_COMPILE);
glPushMatrix; // матрица запоминается из-за масштабирования
glScalef (2.5, 2.5, 2.5) ;
For i := 0 to Model.Count - 1 do begin // цикл построения патчей
glMap2f(GL_MAP2_VERTEX_3, 0, I, 3, 4, 0, 1, 12, 4, Model.Items[i]);
glEvalMesh2(GL_FILL, 0, 4, 0, 4);
end;
glPopMatrix;
glEndList;
Model.Free; // удаление списка
end;
Закончу рассмотрение примера двумя замечаниями:
список модели заполняется порциями по шестнадцать точек, это обязательно для верной адресации в памяти отдельных патчей;
файл опорных точек не годится для построения модели по отдельным многоугольникам, в этом случае она будет лишь отдаленно напоминать исходную поверхность.
Замечание
Несмотря на то, что при высоком уровне детализации разбиения поверхности на отдельные треугольники мы получим поверхность, не уступающую по качеству воспроизведения варианту со сплайнами, я настоятельно посоветую вам использовать все-таки поверхность Безье Высокая детализация приведет к огромному количеству воспроизводимых примитивов, и скорость воспроизведения заметно упадет.
Приведу еще один прекрасный пример на использование патчей, проект из подкаталога Ех31 (рис 4.20).
Теперь просверлены два отверстия, обратите внимание на их пересечения
Теперь в квадрате просверлены два отверстия, пересекающиеся друг с другом, причем области пересечения дырок заполнены материалом Параметры операций с буфером трафарета задаются один раз - при инициализации работы приложения:
glStencilFunc(GL_EQUAL, 0, 1);
// в следующей команде важны первый и третий аргументы
glStencilOp(GL_INCR, GL_INCR, GL_INCR);
glEnable(GL_STENCIL_TEST); // разрешаем тест трафарета
Комбинируя значения последних двух аргументов команды glstencilFunc, вы можете получать самые различные эффекты при вырезке отверстий. Отверстие в площадке получается с помощью манипуляций с цветом:
glClear(GL_COLOR_BUFFER_BIT or GL_STENCIL_BUFFER_BIT);
glPushMatrix;
glPushMatrix;
glColor3f(1. 0, 0. 0, 0. 0); // сфера красного цвета
glTranslatef (45. 0, 40. 0, -150. 0);
gluSphere (qObj, 50. 0, 20, 20);
glPopMatrix;
If fRot then glRotatef(theta, 1. 0, 1. 0, 0. 0); // поворот площадки
glColorMask(False, False, False, False); // отключить работу с цветом
glPushMatrix;
// первая дырка
glTranslatef(45. 0, 45. 0, 0. 0);
gluDisk(qObj, 15. 0, 20. 0, 20, 20);
// вторая дырка
glTranslatef(20. 0, 20. 0, 0. 0);
gluDisk(qObj, 15. 0, 20. 0, 20, 20);
glPopMatrix;
glColorMask(True, True, True, True); // включить работу с цветом
glColor3f(1. 0, 1. 0, 0. 0); // задаем цвет желтым
// площадка
glBegin(GL_QUADS);
glNormal3f(0. 0, 0. 0, 1. 0);
glVertex3f(0. 0, 0. 0, 0. 0);
glVertex3f(100. 0, 0. 0, 0. 0);
glVertex3f(100. 0, 100. 0, 0. 0);
glVertex3f(0. 0, 100. 0, 0. 0);
glEnd;
glPopMatrix;
Смысл примера состоит в том, что для выделяемой области, т. e области отверстий, мы заносим в буфер кадра значение, соответствующее цвету фона Задание маски цвета из четырех отрицаний соответствует тому, что ни один компонент палитры цвета не будет записываться для области отверстий - отключается работа с буфером кадра Этот прием мы часто будем использовать.
Можно и не использовать буфер трафарета, а основываться только на буфе-Ре кадра и буфере глубины. Проект из подкаталога Ex35 содержит пример, функционирующий подобно предыдущему, но не использующий буфер шаблона.
Каждое отверстие рисуется два раза, чуть ниже и чуть выше площадки, но на время их воспроизведения отключается работа с буфером цвета:
glColorMask(False, False, False, False); // отключается работа с цветом
glPushMatrix;
// первая дырка
glTranslatef(45. 0, 45. 0, 0. 01); // над площадкой
gluDisk(qObj, 15. 0, 20. 0, 20, 20);
glTranslatef(0. 0, 0. 0, -0. 02); // под площадкой
gluDisk(qObj, 15. 0, 20. 0, 20, 20);
// вторая дырка
glTranslatef(20. 0, 20. 0, 0. 02); // над площадкой
gluDisk(qObj, 15. 0, 20. 0, 20, 20);
glTranslatef(0. 0, 0. 0, -0. 02); // под площадкой
gluDisk(qObj, 15. 0, 20. 0, 20, 20);
glPopMatrix;
glColorMask(True, True, True, True); // возвращается работа с цветом
glColor3f(1. 0, 1. 0, 0. 0);
// площадка желтого цвета
glBegin(GL_QUADS);
glNormal3f(0. 0, 0. 0, 1. 0);
glVertex3f(0. 0, 0. 0, 0. 0);
glVertex3f(100. 0, 0. 0, 0. 0);
glVertex3f(100. 0, 100. 0, 0. 0);
glVertex3f(0. 0, 100. 0, 0. 0);
glEnd;
glPopMatrix;
Отверстия приходится рисовать по два раза потому, что иначе через них местами проглядывает площадка.
Я уже однажды говорил об этом, но сейчас еще раз напомню в рассматриваемых примерах на сцене присутствует не больше пяти объектов, поэтому не заметно, насколько сильно тормозит приложение интенсивная работа с буферами Если требуется только прорезать отверстия в поверхностях, эффективнее самостоятельно разбить поверхность на отдельные зоны и нарисовать дырку подобно тому, как это делалось в примерах главы 2. В главе 5 мы рассмотрим еще один пример того, как создать дырявую поверхность.
Следующий пример, проект из подкаталога Ех30, относится к разряду классических, в работе над ним моя заслуга состоит только в переносе программы на Delphi.
Рисунок 4. 24 иллюстрирует работу приложения - на экране два тора, в середине экрана прорезано квадратное отверстие, сквозь которое проглядывает синяя сфера.
При изменении размеров окна программы получается очень интересный эффект
Пример своеобразный во многих отношениях. В нем вводятся дисплейные списки, состоящие только в том, что в них задаются свойства для желтого и синего материалов. При создании окна включается работа с буфером трафарета, а воспроизведение начинается уже в обработчике события, связанного с изменением Размеров окна формы:
Procedure TfrmGL. FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glClear(GL_STENCIL_BUFFER_BIT); // очищаем буфер трафарета
glMatrixMode(GL_PROJECTION);
glLoadldentity;
g!0rtho(-3.0, 3.0, -3.0, 3.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadldentity;
// создаем квадрат посередине сцены
glStencilFunc (GL_ALWAYS, $1, $1);
glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
glBegin(GL_QUADS);
glVertex3f (-1.0, 0.0, 0.0);
glVertex3f (0.0, 1.0, 0.0);
glVertex3f (1.0, 0.0, 0.0);
glVertex3f (0.0, -1.0, 0.0);
glEnd;
// переопределяем видовые параметры
glMatrixMode(GL_PROJECTION);
glLoadldentity;
gluPerspective(45.0, ClientWidth / ClientHeight, 3.0, 7.0);
glMatrixMode(GL_MODELVIEW);
glLoadldentity;
glTranslatef(0.0, 0.0, -5.0);
InvalidateRect(Handle, nil, False);
end;
Видовые параметры переопределяются по ходу обработки события. Вы можете вставить showMessage в этот обработчик, чтобы создать паузу и увидеть, как заданы видовые параметры в первом и во втором случаях. Квадрат строится в "мировой" системе координат, затем видовые параметры берут за основу размеры экрана. Поэтому при изменениях размера окна торы не меняют пропорций, а квадрат принимает форму ромба.
Замечание
Обращаю ваше внимание на то, что квадрат "рисуется" не в буфере кадра, т е на экране, а в буфере трафарета.
Теперь перейдем к собственно воспроизведению:
// рисуем синюю сферу там, где значение буфера трафарета равно 1
glStencilFunc (GLJ2QUAL, $1, $1);
glCallList (BLUEMAT);
glutSolidSphere (0.5, 20, 20);
// рисуем желтые торы там, где значение буфера трафарета не равно 1
glStencilFunc (GL_NOTEQUAL, $1, $1);
glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix;
glRotatef (45.0, 0.0, 0.0, 1.0);
glRotatef (45.0, 0.0, 1.0, 0.0);
glCallList (YELLOWMAT);
glutSolidTorus (0.275, 0.85, 20, 20);
glPushMatrix;
glRotatef (90.0, 1.0, 0.0, 0.0);
glutSolidTorus (0.275, 0.85, 20, 20);
glPopMatrix;
glPopMatrix;
Поскольку параметры glstencilop не изменились перед воспроизведением синей сферы, рисуются все ее участки, но в области, пересекающейся с вырезанным квадратом, мы видим лицевую сторону сферы, а в остальных ее участках рисуется задняя поверхность. Пример простой, но очень изящный, рекомендую вам разобраться в нем основательно.
Рисунок 4.25 иллюстрирует работу следующего, тоже весьма интересного примера, проекта из подкаталога Ех37.
При рисовании полупрозрачных замкнутых объектов появляются ненужные узоры
Реализовано все тривиально - у всех объектов, присутствующих на сцене, оптические свойства материала заданы с единичным значением альфа-компонента, для сферы это значение равно 0. 5. При вращении сферы на ее поверхности появляется узор, который портит всю картину. Эта проблема очень распространена, и новички часто натыкаются на нее. Связана она с наложением передней и задней полупрозрачных поверхностей сферы и вообще любой замкнутой полупрозрачной поверхности.
В этом разделе уже был пример с полупрозрачной сферой, где такой проблемы не возникало, но там сфера не вращалась, и мы всегда наблюдали ее с выгодной точки зрения. Если вы посмотрите проект из подкаталога Ex49, в котором та же полупрозрачная сфера вращается по всем осям, то обнаружите, что в некоторых положениях появляется паразитный узор.
Самым простым решением будет не воспроизводить внутреннюю часть сферы, но если по сценарию внутренняя поверхность изготовлена из другого материала, мы этого не увидим. To есть сфера не получится действительно полупрозрачной, как бы изготовленной из матового стекла. В проекте из подкаталога Ex50 предложено более изящное решение: вначале воспроизводится внутренняя поверхность сферы, а затем внешняя:
glEnable(GL_BLEND); // включаем смешение
glEnable(GL_CULL_FACE); // включаем отсечение сторон полигонов
glCullFace(GL_FRONT); // не воспроизводить лицевую поверхность сферы
draw_sphere(Angle); // вывести заднюю поверхность сферы
glCullFace(GL_BACK); // не воспроизводить заднюю поверхность сферы
draw_sphere(Angle); // вывести переднюю поверхность сферы
glDisable(GL_CULL_FACE); // отключить сортировку поверхностей
glDisable(GLJ3LEND); // отключить режим смешения
Замечание
Решение хорошее, но для простых задач можно все-таки воспроизводить только внешнюю сторону объекта, так будет быстрее
Теперь мы подошли к еще одной проблеме, связанной с полупрозрачностью. Сделаем конус в нашей системе также полупрозрачным (проект из подкаталога Ех51). Для этого альфа-компонент для материала, из которого изготовлен конус, задаем отличным от единицы, в данном случае равным 0.5, как и для сферы.
Замечание
Поскольку основание конуса соприкасается с полом, на время воспроизведения основания отключаем тестирование глубины Иначе при перемещениях точки зрения в местах соприкосновения появляется ненужный узор.
Учитывая то, что мы уже сталкивались с проблемой замкнутых полупрозрачных объектов, сортируем передние и задние поверхности и конуса, и сферы:
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
// рисуем заднюю поверхность конуса и сферы
glCullFace(GL_FRONT);
draw_cone;
draw_sphere(Angle);
// рисуем переднюю поверхность конуса и сферы
glCullFace(GL_BACK);
draw_cone;
draw_sphere(Angle);
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
Казалось бы, этого достаточно, однако сфера при прохождении за конусом становится невидимой. Эта проблема также ставит многих в тупик, когда на сцене должны присутствовать несколько полупрозрачных объектов. Здесь уже нельзя ограничиться воспроизведением только задней или передней поверхности, иначе картинка получается или неестественной, или тусклой.
Если на сцене присутствует
На Рисунок 4. 31 изображен снимок работы программы из подкаталога Ex51 оба объекта стали действительно полупрозрачны при любых положениях в пространстве.
Решение состоит в том, чтобы согласовать содержимое буфера смешения и буфера глубины, первым нужно воспроизводить наиболее удаленный объект
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
If Angle < 180 then begin
// сфера за конусом, первой воспроизводим сферу
glCullFace(GL_FRONT); // вначале задние стороны
draw_sphere(Angle + 45. 0);
draw_cone;
glCullFace(GL_BACK); // затем передние
draw__sphere(Angle + 45. 0);
draw_cone();
end
else begin
// конус за сферой, первым воспроизводим конус
glCullFace(GL_FRONT); // вначале задние стороны
draw_cone();
draw_sphere(Angle + 45. 0);
glCullFace(GL_BACK); // затем передние
draw_cone();
draw_sphere(Angle + 45. 0);
end;
glDlsable(GL_CULL_FACE);
glDisable(GL_BLEND);
Рисунок 4 32 демонстрирует работу еще одного красивого примера, проекта из подкаталога Ex53
Обязательно посмотрите, как работает этот красивый пример
При щелчке кнопкой мыши по поверхности окна расходятся круги случайного цвета, напоминающие чернильные разводы. Пятна смешиваются при наложении, отчего получаются картинки завораживающей красоты.
В программе введена константа, задающая плотность разводов:
const
density = 36;
Введен класс, соответствующий отдельному пятну:
type
TDrip = class public
// массивы цветов пятна
outer_color, nng_color, inner_color: Array [0.. 3] of GLfloat;
outer_radius, ring_radius: GLfloat;
procedure Draw;
procedure fill_points;
private
divisions: GLint; // деления, соответствуют плотности
points: Array [O.. density * 8 - 1] of GLfloat; // точки пятна
end;
Следующие константы задают максимальное количество одновременно присутствующих на экране пятен и максимальный радиус пятна, по достижении которого пятно тает:
const
max_drips = 20; max_ring_radius = 250.0;
Нам необходимо запоминать позицию центра каждого пятна, а также иметь массив, хранящий данные на каждое пятно:
var
dnp_position : Array [0..max_drips-l, 0. 1] of GLfloat;
first_drip, new_drip : GLint; // текущее количество пятен
drips : Array [0. .max_dnps - 1] of TDrip;
При рисовании каждого пятна рисуются треугольники, расходящиеся от центра пятна. Если тип примитива заменить на GL_LINES, в результате получатся красивые снежинки.
procedure TDrip.Draw; // метод - нарисовать отдельное пятно
var
1 : GLint;
begin
glBegin(GL__TRIANGLES) ;
For i := 0 to divisions-1 do begin
glColor4fv(@inner_color);
glVertex2f(0.0, 0.0); // треугольники, выходящие из центра пятна
glColor4fv(@ring_color);
glVertex2f(points[2*i] * ring_radius, points[2*i + 1] * ring_radius),
glVertex2f(points[2*((i+l) mod divisions)] * ring_radius,
points[(2*((i+l) mod divisions)) + 1] * ring radius);
end;
glEnd;
end;
При заполнении массива точек пятна берутся значения, равномерно лежащие внутри круга:
procedure TDrip.fill_pomts;
var
i : GLint;
theta : GLfloat;
delta : GLfloat;
begin
delta := 2.0 * PI / divisions;
theta := 0.0;
For i := 0 to divisions-1 do begin
points[2 * i] := cos(theta);
points[2 * i + 1] := sin(theta);
theta := theta + delta;
end;
end;
Если, например, синус умножить на какое-нибудь число, получатся не круги, а овалы
Пользовательская процедура create_drip вызывается при каждом щелчке кнопкой мыши, ей передаются координаты центра нового пятна и требуемый цвет:
procedure create_drip(x, у, г, g, b : GLfloat);
begin
drips [new_drip] := TDrip. Create/With drips[new_drip] do begin
divisions := density,
fill_points;
inner_color[0] := r;ring_color[0] := r;outer_color[0] := r;
inner_color [ 1 ] := g;nng_color [1] := g;outer_color [1] := g;
inner_color[2] : = b;ring_color[2] := b;outer_color[2] := b;
// альфа-компонент края пятна нулевой
inner_color[3] := 1.0;ring_color[3] := 1.0;outer_color[3] := 0.0;
ring_radius := 0.0;outer_radius := 0.0;
end;
drip_position[new_drip][0] := x;
drip_position[new_drip][1] := y;
// увеличиваем счетчик пятен
new_drip := (new_drip + 1) mod max_drips;
If (new_drip = first_drip)
then first_drip := (first_drip + 1) mod max_drips;
end;
При очередном воспроизведении кадра радиусы всех присутствующих на экране пятен увеличиваются, при достижении максимального радиуса пятно растворяется.
Анимация создается простым зацикливанием, но вряд ли этот пример будет работать где-то чересчур быстро, т к используется так называемый "альфа блэндинг" (blend - смешивать):
Procedure TfrmGL.WMPaint(var Msg: TWMPaint);
var
ps : TPaintStruct; rel_size : GLfloat; i : GLint;
begin
BeginPaint(Handle, ps);
i := first_drip;
glClear(GL_COLOR_BUFFER_BIT);
While i<>new_drip do begin
drips[i].ring_radius := drips[i].ring_radius + 1;
drips[i].outer_radius := drips[i].outer_radius + 1;
rel size := drips[i].ring_radius / max_ring_radius;
// корректируем альфа-компонент, края пятна полупрозрачные
drips[i]. nng_color [3] := 0;
drips[i].inner_color[3] := 5-5*rel_size*rel_size;
// смещаемся в центр пятна
glPushMatrix;
glTranslatef(drip_position[i][0], drip_position[i][1], 0.0);
drips[i].draw; // рисуем пятно
glPopMatrix;
// пятно достигло максимального размера
If (drips[i].ring_radius > max_ring_radius)
then first_drip := (first_drip + 1) mod max_drips;
i:=(i+l) mod max_drips;
end;
SwapBuffers(DC);
EndPaint(Handle, ps);
InvalidateRect(Handle, nil, False); // зацикливаем программу
end;
Если на вашем компьютере пример работает недостаточно быстро, можете увеличить шаг, с которым увеличиваются радиусы пятен.
Содержимое буфера глубины доступно для визуализации
Окно приложения не изменяется в размерах, для хранения содержимого буфера глубины введен массив с размерностью, подогнанной под размеры окна:
Zbuf: Array [0.. WIN_WIDTH - 1, 0.. WIN HEIGHT - 1] of GLfloat;
Для хранения значения глубины на каждый пиксел экрана требуется одно вещественное число. Левая половина окна является обычной областью вывода, в ней рисуется вращающийся тор После рисования тора содержимое буфера глубины копируется в массив. Для второго экрана видовые параметры задаются так, чтобы удобно было установить позицию растра, затем массив zbuf копируется на экран:
glViewport(0, 0, round(ClientWidth/2), ClientHeight);
glScissor(0, 0, round(ClientWidth/2), ClientHeight);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
// для правого экрана задается перспективная проекция
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
gluPerspective(60. 0, ClientWidth / ClientHeight, 5. 0, 70. 0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glPushMatrix;
gluLookAt(25. 0, 25. 0, 50. 0, 25. 0, 25. 0, 20. 0, 0. 0, 1. 0, 0. 0);
glTranslatef(25. 0, 25. 0, 10. 0);
glRotatef (Angle, 1. 0, 0. 0, 0. 0);
glCallList(Torus);
// копируем в массив содержимое буфера глубины
glReadPixels(0, 0, WIN_WIDTH, WIN_HEIGHT, GL_DEPTH_COMPONENT, GL FLOAT,
Zbuf);
glPopMatrix;
// левый экран
glViewport(round(ClientWidth/2) +1, 0, round(ClientWidth/2), ClientHeight);
glScissor(round(ClientWidth/2) + 1, 0, round(ClientWidth/2), ClientHeight);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glPushMatrix;
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
glRasterPos2i (-1, -1) ; // позиция вывода в координатах экрана
// вывод содержимого массива
glDrawPixels(WIN_WIDTH, WIN_HEIGHT, GL_LUMINANCE, GL_FLOAT, @Zbuf);
glPopMatrix;
Содержимое массива (буфера глубины) выводится оттенками серого, пикселы, наиболее близко расположенные к наблюдателю, выглядят темнее, глубина максимальна для наиболее удаленных точек.
Если третий аргумент команды glDrawPixels изменить, например, на GL_GREEN, получим ту же самую картинку в оттенках зеленого. Комбинируя значения пятого аргумента команды glReadPixels и третьего параметра команды glDrawPixels, можно получать различные видеоэффекты (посмотрите мою пробу на эту тему в подкаталоге Ех57).
Возможно, вам потребуется, чтобы на экране присутствовало только одно трансформированное изображение. Двойная буферизация будет здесь как никогда кстати.
В проекте из подкаталога Ех57 демонстрируется, как это можно сделать (Рисунок 4.36).
Простейший пример на получение эффекта зеркального отражения
Код воспроизведения следующий:
procedure TfrmGL. WMPaint(var Msg: TWMPaint);
var
ps: TPaintStruct; Begin
BeginPaint(Handle, ps);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BOFFER_BIT);
glLoadIdentity;
glTranslatef(0, -0. 5, -4);
// здесь пол рисуется только в буфер трафарета
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, $FFFF); // пол рисовать всегда
glColorMask(FALSE, FALSE, FALSE, FALSE);
glDisable(GL_DEPTH_TEST);
DrawFloor; // собственно пол
// восстанавливаем нормальные установки
glColorMask(TRUE, TRUE, TRUE, TRUE);
glEnable(GL_DEPTH_TEST);
// отражение рисуется только там, где значение в буфере
// трафарета равно единице
glStencilFunc(GL_EQUAL, 1, $FFFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
// рисуем отраженную сцену
glPushMatrix;
glScalef(l, -1, 1); // переворачиваем по оси Y
DrawObjects;
glPopMatrix;
// рисуем настоящий пол, полупрозрачным, чтобы можно было увидеть
// отраженные объекты
glDepthMask(FALSE);
DrawFloor;
glDepthMask(TRUE);
// для воспроизведения подлинной системы отключаем буфер трафарета,
// иначе она также будет обрезаться областью пола
glDisable(GL_STENCIL_TEST);
DrawObjects;
glFinish;
SwapBuffers(DC);
EndPaint(Handle, ps);
Angle: = (Angle + 2) mod 360; // для поворота в следующем кадре
InvalidateRect(Handle, nil, False);
end;
Все вроде просто, но если вы внимательно посмотрите на пример, то обнаружите мою небольшую хитрость: окно приложения нельзя изменять в размерах.
Сделал я это специально, иначе при сужении окна по вертикали, начинает выглядывать фальшивая система объектов за границей пола.
Ответ на вопрос "а почему?" состоит в том, что третий аргумент команды glstencilOp при подготовке вывода фальшивой системы имеет не совсем
подходящее значение. Этот аргумент должен быть GL__ZERO или GL_INCR, но при таких значениях на поверхности отраженных объектов появляется непрошенный узор.
Мы уже встречали эту проблему и знаем, что она связана с наложением альфа-компонентов для замкнутых фигур, знаем также, что она легко решается сортировкой поверхностей.
Привожу окончательный вариант соответствующего фрагмента кода; можете убедиться, что после внесения изменений все работает совершенно корректно:
glStencilFunc(GL_EQUAL, I, $FFFF) ;
glStencilOp(GLJCEEP, GLJCEEP, GL_INCR);// здесь изменен третий аргумент
glPushMatrix;
glScalefd, -1, 1);
glCullFace (GL_FRONT);//сортировка поверхностей, внутренняя и внешняя
glEnable (GL_CULL_FACE); // поверхности воспроизводятся отдельно
DrawObjects;
glCullFace (GL_BACK);
DrawObjects;
glDisable (GL_CULL_FACE);
glPopMatrix;
Познакомимся теперь, как можно создавать эффект многократного отражения. Рисунок 4.48 демонстрирует работу следующего примера, проекта из подкаталога Ех74: на стенах комнаты висят два зеркала, одно напротив другого, объекты сцены многократно отражаются в этих зеркалах.
Пример на использование различных штриховок
Три прямоугольника верхней линии штрихуются с различными шаблонами, крайние прямоугольники штриховкой не покрываются. Для всех прямоугольников верхней строки текущий цвет задан белым.
Нижние прямоугольники рисуются все без штриховки, переход оттенков обеспечивается тем, что текущий цвет для каждого из прямоугольников задается со все увеличивающейся интенсивностью.
При использовании штриховки многоугольников в пространстве появляется особый эффект, рассмотрим его на следующих нескольких примерах.
В проекте из подкаталога Ex78 на экране рисуется вращающийся додекаэдр Программу отличает то, что при ее инициализации включается режим штриховки многоугольников. Штриховка первоначально задается точечной, и ничего необычного поначалу не заметно. Нажатием клавиши пробела можно поменять шаблон; при использовании второго, в виде насекомых, становится видно, что штриховка не проецируется на поверхность объекта, а заполняет плоскостным узором область, ограниченную объектом (рис 4 51).
Штриховка многоугольников используется чаще всего для плоскостных построений
Этим свойством можно пользоваться для придания эффекта некоторой призрачности. Проект из подкаталога Ex79 содержит программу, иллюстрирующую, как реализовать такой эффект. На сцене присутствуют знакомые нам по некоторым предыдущим примерам сфера и конус, но сфера здесь выглядит эфемерно (рис 4 52).
Штриховкой можно пользоваться для создания призрачных объектов
Поскольку в OpenGL из многоугольников строятся все объемные фигуры вообще, режим штриховки распространяется и на сферу
Шаблон штриховки в программе заполняется случайными числами:
procedure create_stipple_pattern(var pat: TPattern; opacity: GLfloat);
var
x, у: GLint;
Begin
For у: = 0 to 31 do Begin pat[y]: = 0;
For x: = 0 to 31 do
If (random > 0. 6) // чем меньше это число, тем плотнее штриховка
then pat[y]: = pat[y] xor (1 shl x);
end;
end;
При воспроизведении кадра режим штриховки включается только для сферы
glEnable(GL_POLYGON_STIPPLE);
draw_sphere(Angle);
glDisable(GL_POLYGON_STIPPLE);
Посмотрите, какой эффект возникает, если менять штриховку по ходу работы приложения, для чего вставьте следующие две строки перед очередной перерисовкой кадра:
create_stipple_pattern(spherePattern, 0.5) ;
glPolygonStipple(spherePattern);
Замечание
Старайтесь выносить подобные вычислительные операции за пределы собственно воспроизведения кадра
Если попытаться и второй объект сцены, конус, сделать таким же эфемерным, то сфера при прохождении за ним становится невидимой - конус закрывает ее, так как шаблоны штриховки у них одинаковы.
Посмотрите проект из подкаталога Ех80: здесь такого не происходит, поскольку для каждого объекта сцены шаблон задается индивидуально.
glEnable(GL_POLYGON_STIPPLE); // включить режим штриховки
glPolygonStipple(@conePattern); // задаем шаблон для конуса
draw_cone; // рисуем штрихованный конус
glPolygonStipple(spherePattern); // задаем шаблон для сферы
draw_sphere(Angle); // рисуем штрихованную сферу
glDisable(GL_POLYGON_STIPPLE); // выключим режим
Для наложения бликов требуются дополнительные манипуляции
Если не применять особых ухищрений, то блик в такой ситуации не появляется, поскольку OpenGL применяет текстуру после прорисовки отражающей составляющей источника света. Для смешения бликов и текстуры необходимо выполнить двухшаговый алгоритм: нарисовать поверхность с текстурой без отражения источника света, включить смешение и перерисовать поверхность с матовым белым материалом и только отражающей составляющей источника света.
Пункты всплывающего меню позволяют посмотреть по отдельности действие каждого из этих этапов.
Следующий пример (проект из подкаталога Ex84) очень важен, несмотря на кажущуюся простоватость - не пропустите eго. На экране располагаются два объекта, каждый из них имеет свою текстуру (Рисунок 4. 57).
Если в кадре используется
Начинающие часто спрашивают, как организовать работу с несколькими текстурами. Этот пример показывает два возможных способа эффективной организации такой работы.
Первый способ состоит в использовании дисплейных списков, здесь они оказываются как нигде кстати. На этом простом примере эффективность такого подхода пока мало заметна, но нас ждет еще один пример, где она проявится в полной мере.
Дисплейные списки при компиляции запоминают только команды OpenGL, поэтому при вызове списка все промежуточные действия по подготовке образа текстуры не будут затормаживать работу приложения Использование списков существенно облегчает работу также с точки зрения кодирования: нет необходимости держать массивы под каждый образ и переключаться между ними по ходу работы приложения.
В примере описываются два списка, каждый из которых содержит код по подготовке образов. Перед тем как нарисовать примитив, вызывается нужный список. Сами массивы образов могут уже использоваться для других нужд или вовсе не существовать (не существуют они и для нашей программы, а образы текстуры хранятся в памяти, занятой OpenGL).
Этот пример иллюстрирует и другой способ, основанный на использовании недокументированных команд.
Команда glBmdTexture позволяет связывать текстуры, создавать и вызывать именованные последовательности команд подобные дисплейным спискам, но предназначенные для идентификации только текстур. С ней связаны команда glGenTextures, генерирующая имена текстур, и команда glDeleteTextures, освобождающая память от текстурных списков
Замечание
Прототипы всех этих команд необходимо задавать самостоятельно
По выбору можно использовать любой из этих двух способов, выбор управляется переменной HaveTexObj.
В программе массив техОbj, состоящий из двух целых, хранит имена дисплейных или текстурных списков. Значения идентификаторов генерируются системой OpenGL:
if HaveTexObj // используются текстурные списки
then glGenTextures( 2, @TexObj) // генерировать два имени
else begin // используются дисплейные списки
TexObj[0] := glGenLists(2); // генерировать имена дисплейных
// списков
TexObj[1] := ТехОЬj[0]+1;
end;
При подготовке текстуры создается одна из двух разновидностей списков, для команды glBmdTexture концом описания списка является начало описания следующего:
if HaveTexObj // использовать текстурные списки
then glBmdTexture ( GL_TEXTURE_2D, TexObj [0]) // описание
else glNewList( TexObjf[0], GL_COMPILE); // начало для дисплейного
// списка
// красным на белом
For i:=0 to height-1 do
For j:=0 to width - 1 do begin p := i*width+];
if (texl[ (height-i-1) *width+;j]) <>0 then begin
tex[p] [0] := 255;
tex[p][1] := 0;
tex[p] [2] := 0;
end
else begin
tex[p][0] := 255;
tex[p][1] := 255;
tex[p][2] := 255;
end
end;
// собственно создание текстуры
glTexImage2D( GL_TEXTURE_2D, 0, 3, width, height, 0,
GL_RGB, GLJJNSIGNED_BYTE, @tex);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
If not HaveTexObj then glEndList;
Перед рисованием примитивов вызывается нужный список:
glPushMatrix;
glTranslatef( -1.0, 0.0, 0.0);
glRotatef( Angle, 0.0, 0.0, 1.0);
if HaveTexObj // какой из типов списков вызывать
then glBmdTexture ( GL_TEXTURE_2D, ТехОbj[0])
else glCallList( ТехОbj[0]);//здесь хранятся только параметры
glBegin( GL_POLYGON) ;
glTexCoord2f( 0.0, 0.0);
glVertex2f( -1.0, -1.0);
glTexCoord2f( 1.0, 0.0);
glVertex2f( 1.0, -1.0);
glTexCoord2f( 1.0, 1.0);
glVertex2f( 1.0, 1.0);
glTexCoord2f( 0.0, 1.0);
glVertex2f( -1.0, 1.0);
glEnd;
glPopMatrix;
В конце работы, как это принято в среде культурных программистов, память освобождается:
if HaveTexObj
then glDeleteTextures( 2, STexObj) // для текстурных списков
else glDeleteLists (TexObj[0], 2);
Возможно, вы захотите использовать для своих проектов традиционную для Демонстрационных программ текстуру в виде кирпичной кладки, тогда можете взять в качестве шаблона проект из подкаталога Ех85, где найдете все необходимые "стройматериалы"
(Рисунок 4 58).
Шаблон многоугольников
В этом разделе мы подготовимся к легендарной теме использования текстуры в OpenGL.
Шаблон многоугольников является простым способом нанесения узоров на многоугольники, его можно использовать в качестве текстуры для плоских задач.
Сейчас последует не очень выразительный и зрелищный пример, но он во многом полезный. В проекте из подкаталога Ех76 рисуется плоская деталь, с которой мы начинали наше изучение примитивов OpenGL. Тогда нам пришлось изрядно потрудиться, чтобы нарисовать невыпуклый многоугольник.
Теперь все стало несколько проще: я ввел массив, хранящий координаты десяти вершин, лежащих на контуре фигуры:
detal : Array [0..9] of TVector =
((-0.23678, 0.35118, 0.0),
(-0.23678, 0.7764, 0.0),
(-0.37966, 0.7764, 0.0),
(-0.55, 0.60606, 0.0),
(-0.55, -0.4, 0.0),
(0.45, -0.4, 0.0),
(0.45, 0.60606, 0.0),
(0.27966, 0.7764, 0.0),
(0.13678, 0.7764, 0.0),
(0.13678, 0.35118, 0.0) );
Используются tess-объекты и просто перечисляются вершины контура:
gluTessBeginPolygon (tobj, nil);
gluTessBeginContour(tobj);
For i := 0 to 9 do
gluTessVertex(tobj, [Sdetalfi], (Metal [i]);
gluTessEndContour(tobj);
gluTessEndPolygon(tobj);
Вышесказанное пока не относится к теме раздела, а только позволяет нам вспомнить некоторые пройденные темы.
Теперь по поводу собственно узора, в данном случае штриховки. Для нее я взял массив из 16 одинаковых строчек:
const
р75 : Array [0..127] of GLUbyte =
( $аа, $аа, $аа, $аа, $ff, $ff, $ff, $ff,
...
Значение каждого байта будет задавать узор восьми пикселов строки.
Для подключения штриховки необходимо включить соответствующий режим и вызвать команду, устанавливающую шаблон:
glEnable(GL_POLYGON_STIPPLE);
glPolygonStipple(@p75);
Теперь деталь покрывается равномерным узором. В программе случайно меняется текущий цвет, вся штриховка монохромная. Она может быть и разноцветной; если для каждой вершины многоугольника цвет задавать индивидуально, то штриховка покроется цветными переливами.
Рисунок 4.50 иллюстрирует работу следующего, совсем простого примера (подкаталог Ех77).
Смешение цветов и прозрачность
Так называемый альфа-компонент, задаваемый четвертым аргументом в командах, связанных с цветом фона и примитивов, используется для получения смеси цветов
Режим смешения включается командой glEnable с аргументом GL_BIEND Помимо этой команды необходимо задать пиксельную арифметику с помощью glBlendFunc Аргументы этой команды определяют правила, по которым происходит смешение поступающих значений RGBA с содержимым буфера кадра.
Свойства материала
Помимо свойств материала в этом разделе мы также продолжим изучение свойств источника света.
Свойства материала задаются с помощью команды glMaterial Характеристики, определяющие оптические свойства материала, и соответствующие им символьные константы являются следующими" рассеянный цвет (GL_AMBIENT), диффузный цвет (GL_DIFFUSE), зеркальный цвет (GL_SPECULAR), Излучаемый цвет (GL_EMISSION), степень зеркального отражения (GL_ SHININESS).
Значением последнего параметра может быть число из интервала [0,128], oстальные параметры представляют собой массивы четырех вещественных чисел.
Зеркальный цвет задает цветовую гамму бликов материала, степень зеркального отражения определяет, насколько близка поверхность к идеальному зеркалу.
Поработайте с проектом из подкаталога Ех04 и выясните смысл всех характеристик материала "своими глазами".
Этот пример является продолжением предыдущего, в нем добавились пункты меню, соответствующие характеристикам материала. Важно заметить: чтобы цветовой фильтр не перебивал задаваемые свойства, в примере не включается режим GL_COLOR_MATERIAL.
Начните знакомство с примером с вариации значения степени зеркального отражения, которое определяет цветовую насыщенность блика от источника света. С помощью клавиши 'S' можно регулировать размер блика на сфере, нажимая ее одновременно с <Shift> - увеличить, а без <Shift> - уменьшить эти размеры.
Замечание
Чтобы блик появился на сфере, установите зеркальный цвет в значение, отличное от принятого по умолчанию.
Следующий пример, проект из подкаталога Ех05, знакомит нас с еще одним аспектом, связанным с источником света - с моделью освещения.
Модель освещения задается с помощью команды glLightModel. В примере внутренняя и внешняя стороны цилиндра имеют разные свойства материала, внутренняя сторона окрашена синим, внешняя - красным. Свойства материала заданы в массивах ambFront и ambBack. Для того чтобы включить освещенность для внутренней стороны многоугольников, вызывается команда glLightModel, у которой вторым аргументом задается символическая константа GL_LIGHT_MODEL_TWO_SIDE, а третьим аргументом - ноль или единица:
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); // для обеих сторон
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @ambFront); // задняя
// сторона
glMaterialfv (GL_BACK, GL_AMBIENT_AND_DIFFUSE, @ambBack); // передняя
// сторона
Я советую вам не спеша разобраться с этим упражнением. Например, посмотрите результат, который получается при замене константы GL_AMBIENT_ANC__DIFFUSE На GL_DIFFUSE И GL_AMBIENT. Вы увидите, что диффузный цвет материала
наиболее сильно определяет цветовые характеристики поверхности.
Следующий пример, проект из подкаталога Ех06, совсем простой - на экране вращается объемная деталь, построенная на основе тестовой фигуры второй главы (Рисунок 4.2).
Такая текстура часто используется в демонстрационных проектах
Но чаще всего вы будете нуждаться в том, чтобы использовать текстуру, загружаемую из файла. Следующие примеры помогут нам разобраться, как это сделать.
Замечание
Поскольку я установил ограничение для примеров этой книги, содержащееся в том, что все они должны компилироваться в Delphi третьей версии, то буду
использовать bmp-файлы В старших версиях среды вы легко можете использовать другие форматы, что будет экономнее по отношению к дисковому пространству.Рисунок 4.59 показывает один из моментов работы примера из подкаталога Ех86, в котором образ текстуры загружается из bmp-файла.
Текстура
Текстура подобна обоям, наклеиваемым на поверхность. Тема этого раздела является самой интересной, но она столь обширна, что рассмотреть ее досконально нам не удастся, этому можно посвятить целиком отдельную книгу.
Думаю, что наилучшим образом вы разберетесь с данной темой только тогда, когда самостоятельно попробуете решить какую-то задачу, я же приведу набор готовых решений только для самых распространенных задач, возникающих при использовании текстуры в OpenGL.
Текстура бывает одномерной и двумерной. Одномерная текстура может использоваться только для нанесения узора в виде полосок, двумерная позволяет наносить прямоугольные образы.
Начнем изучение темы с одномерной текстуры. Подкаталог Ех81 содержит следующий пример: на объекты нескольких типов накладываются чередующиеся красные и синие полоски (Рисунок 4.53).
Текстура загружается из файла
Файл картинки я позаимствовал из дистрибутива C++ Builder 4.0. Если мы заранее знаем размер файла, то можем использовать объект класса TBitmap для загрузки текстуры, а размеры массива образа задавать под размеры растра:
procedure TfrmGL.BmpTexture;
var
Bitmap: TBitmap;
Bits: Array [0..63, 0..63, 0..2] of GLubyte; // массив образа, 64x64
i, j: Integer; begin
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile('gold.bmp1); // загрузка текстуры из файла
{---заполнение битового массива---}
For i := 0 to 63 do
For ] := 0 to 63 do begin
bits [i, j, 0] := GetRValue(Bitmap.Canvas.Pixels [1,3]);
bits [i, ;j, 1] := GetGValue(Bitmap.Canvas.Pixels[1,3]);
bits [i, j, 2] := GetBValue(Ritmap.Canvas.Pixels[1,3]);
end;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL NEAREST);
glTex!mage2D(GL_TEXTURE_2D, 0, GL_RGBA,
64, 64, // здесь задается размер текстуры
О, GL_RGB, GL_UNSIGNED_BYTE, @Bits); // чтобы цвет объекта не влиял на текстуру
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glEnable (GL__TEXTURE_2D) ;
Bitmap.Free;
end;
Обратите внимание, что здесь используются функции API, вырезающие значение веса для цветовых составляющих пиксела, поскольку в формате OpenGL вначале идет красный цвет, в растре же первым идет синий цвет
Замечание
Для простоты в примерах я часто включаю режим использования текстуры при инициализации приложения. С точки зрения оптимизации надо включать и выключать ее для каждого кадра.
Следующий пример, проект из подкаталога Ех87, содержит подсказку, как быть в случае, если мы заранее не знаем размер растра или хотим использовать универсальный подход к этому вопросу. К тому же этот проект помогает тем читателям, которые приняли мое предложение о том, чтобы знакомиться с программированием, основанным только на использовании функций API.
Замечание
Для экономии места на дискете в оставшихся примерах я буду использовать ограниченный набор растров, хотя для многих примеров можно подобрать и более подходящие картинки.Этот и некоторые следующие примеры будут крутиться вокруг астрономической темы (Рисунок 4.60).
Текстуру можно использовать для эмуляции зеркального отражения
Секрет примера состоит не в удачном подборе образа текстуры, при любом другом картинка будет выглядеть так же превосходно. Главное в нем - это режим генерации координат текстуры, который задается приближенным к искажениям на поверхности сферы:
glTexGeni (GL_S, GL_TEXTURE_GEN__MODE, GL_SPHERE_MAP); // подобно сфере
glTexGeni (GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable (GL_TEXTURE_GEN_S); // включается генерация обеих координат
glEnable (GL_TEXTURE_GEN_T);
Советую вам неспешно разобраться с этим примером, попробуйте поочередно отключать генерацию то одной, то другой координаты и посмотреть получающийся результат.Такой же эффект эмуляции зеркального отражения, но на поверхности цилиндра, демонстрируется в проекте из подкаталога Ex92 (рис 4.65).
Текстуру можно использовать и в качестве фона
На заднем плане сцены рисуем квадрат, покрытый текстурой:
glMatrixMode(GL_PROJECTION);
glPushMatrix; // запоминаем нормальное значение матрицы проекций
glLoadldentity; // подгоняем так, чтобы квадрат занял
glOrtho(-50.0,50.О,-50. О, 50. О, 200.О, 300.0); // нужное положение
glMatrixMode(GL_MODELVIEW);
glDepthMask(FALSE); // без записи в буфер глубины
glEnable(GL_TEXTURE_2D); // текстура включается только для фона
glBegin(GL_QUADS); // квадрат, покрытый текстурой
glNormal3f(0.0,0.0,1.0);
glTexCoord2f(0.0,0.0) ;
glVertex3f(0.0,0.0,0.0);
glTexCoord2f(1.0,0.0);
glVertex3f(100.0,0.0,0.0);
glTexCoord2f(1.0,1.0);
glVertex3f(100.0,100.0,0.0);
glTexCoord2f(0.0,1.0);
glVertex3f(0.0,100.0,0.0);
glEnd;
glDisable(GL_TEXTURE_2D);
glDepthMask(TRUE); // возвращаем использование буфера глубины
glMatrixMode(GL_PROJECTION); // важно восстановить
glPopMatrix; // нормальное состояние в матрице проекций
glMatrixMode(GL_MODELVIEW);
glPushMatrix; // рисуем объекты сцены
glTranslatef(50.0, 50.0, 150.0);
glRotatef(Angle, 1.0, 1.0, 0.0);
glRotatef(Angle / (random (1) + I), 0.0, 0.0, 1.0);
glutSolidlcosahedron;
glPopMatrix;
Для этого добавьте следующие строки в процедуру инициализации:
glTexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni (GLJT, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
Туда же переместите строку с включением режима использования текстуры, а генерацию координат текстуры надо включать только при рисовании объекта:
glEnable (GL_TEXTURE_GEN_S);
glEnable (GL_TEXTURE_GEN_T);
glutSolidTeapot (1.0);
glDisable (GL_TEXTURE_GEN_S);
glDisable (GL_TEXTURE_GEN_T);
В последнем примере главы я попытаюсь ответить сразу на несколько вопросов, часто возникающих у начинающих.
В проекте из подкаталога Ех95 рисуется волнообразная поверхность, на которой искаженно выводится текстура (Рисунок 4.69).
Тень и отражение
Вообще говоря, автоматизация этих эффектов в OpenGL не предусмотрена, т. е. нет готовых команд или режимов, после включения которых объекты сцены будут снабжены тенью или будут отражаться на какой-либо поверхности.
Для простых случаев задач тень можно рисовать самому, я думаю, что это самое оптимальное по скорости решение задачи.
Рисунок 4.43 демонстрирует работу программы из подкаталога Ех69, в которой на полу рисуется тень от подвешенного над ним в пространстве параллелепипеда.
Объект можно передвигать в пространстве с помощью клавиш управления курсором, тень корректно отображается для любого положения.
Теперь мы можем гордиться своими астрономическими моделями
Файл растра с картой Земли я взял из DirectX SDK фирмы Microsoft Функция чтения растра основана на коде для методов класса TBitmap:
// функция возвращает размеры образа и указатель на массив образа
function ReadBitmap(const FileName : String;
var sWidth, tHeight: GLsizei): pointer; const
szh = SizeOf(TBitmapFileHeader); // размеры заголовка растра
szi = SizeOf(TBitmapInfoHeader);
type
TRGB = record
R, G, В : GLbyte;
end;
TWrap = Array [0..0] of TRGB;
var
BmpFile : File;
bfh : TBitraapFileHeader; // заголовок файла
bmi : TBitmapInfoHeader; x, size: GLint;
temp: GLbyte; // для перестановки красного и синего
begin
AssignFile (BmpFile, FileName);
Reset (BmpFile, 1);
Size := FileSize (BmpFile) - szh - szi;// заголовки в растр не включать
Blockread(BmpFile, bfh, szh); // считываем заголовки
BlockRead (BmpFile, bmi, szi);
If Bfh.bfType <> $4042 then begin // формат не подходит
MessageBox(Window, 'Invalid Bitmap', 'Error', MB OK);
Result .= nil;
Exit;
end;
sWidth := bmi.biWidth; // из заголовка узнаем размеры собственно растра
tHeight := bmi.biHeight;
GetMem (Result, Size); // выделяем память для массива образа
BlockRead(BmpFile, Result74, Size); // считываем собственно растр
For x := 0 to sWidth*tHeight-l do // переставить синий и красный
With TWrap(resultA)[x] do begin temp := R;
R := B;
В := temp;
end;
end;
В программе есть несколько упрощений, скорректируйте их, если хотите получить профессиональный код.
Я не стал загромождать текст и использовать защищенный режим (tryexcept), но вы должны обязательно его использовать для обработки возможных исключений. Кроме того, вы должны помнить о том, что описанная функция не может считывать монохромные растры Ниже я приведу более универсальный код, но для примеров, основанных на низкоуровневом подходе, я бы посоветовал вам посмотреть функцию чтения монохромных растров из последней главы этой книги. Чтобы применить функцию ReadBitmap, используем временно создаваемый указатель:
wrkPointer : Pointer;
sWidth, tHeight : GLsizei;
// указатель будет создан функцией
wrkPointer := ReadBitmap('..\earth.bmp', sWidth, tHeight,;
glTex!mage2D(GL_TEXTURE_2D, 0, 3, sWidth, tHeight, 0,
GL_RGB, GL_UNSIGNED_BYTE, wrkPointer);
Freemem(wrkPointer); // удаляем указатель
В следующем примере мы познакомимся сразу с несколькими важными вещами Во-первых, в нем используется немного другой подход к считыванию данных растра, а во-вторых, мы еще раз обсудим, как использовать несколько текстур в кадре
В проекте из подкаталога Ех88 вокруг планеты вращается спутник, каждый из объектов имеет свою текстуру (рис 4.61)
Теперь мы можем накладывать несколько текстур одновременно
Код процедуры подготовки текстуры ничем не отличается от предыдущего, только поверхность океанов сделана более "плотной". По сценарию разметка видна только на поверхности воды, поэтому помимо сферы планеты введен еще один список для сферы чуть меньшего радиуса:
glNewList (Earth, GL_COMPILE); // наружная сфера планеты
PrepareImage ('..\earth.bmp');
glEnable(GL_CULL_FACE); // сортируем многоугольники
glCullFace(GL_FRONT);
gluSphere (Quadric, 1.0, 24, 24);
glCullFace(GL_BACK);
gluSphere (Quadric, 1.0, 24, 24);
glDisable(GL_CULL_FACE);
glEndList;
// сфера со второй текстурой, располагается внутри первой
glNewList (Inside, GL_COMPILE);
PrepareImage ('..\grid.bmp');
glEnable(GL_CULL_FACE); // сортировка многоугольников
glCullFace(GL_FRONT);
gluSphere (Quadric, 0.99, 24, 24); // радиус чуть меньше, чем у Земли
glCullFace(GL_BACK);
gluSphere (Quadric, 0.99, 24, 24);
glDisable(GL_CULL_FACE);
glEndList;
Теперь важно воспроизводить объекты в правильном порядке (мы разбирали это в разделе, посвященном смешиванию цветов), иначе не получится наложения текстур:
glPushMatrix;
glRotatef (-10, 0.0, 1.0, 0.0);
glRotatef (Angle, 0.0, 0.0, 1.0);
glEnable (GL_BLEND); // так оптимальнее
glCallList(Inside); // вначале - внутреннюю сферу
glCallList(Earth); // потом - наружную
glDisable (GL_BLEND);
glPopMatrix;
Основные моменты, связанные с использованием текстуры, мы с вами изучили, теперь уточним еще некоторые вопросы
На Рисунок 4 64 представлена работа следующего примера, проекта из подкаталога Ex91, где наша знакомая модель неузнаваемо преобразилась Файл растра для этого примера я взял из DirectX SDK фирмы Microsoft:
Теперь тестовая деталь выглядит более реалистично
В этом примере интересно то, как при некоторых положениях детали ее грани отсвечивают слабым фиолетовым оттенком.
Массив свойств материала детали содержит небольшую составляющую красного, чем больше это число (до некоторых пределов), тем выразительнее будет выглядеть деталь:
MaterialColor: Array [0..3] of GLfloat = (0.1, 0.0, 1.0, 1.0);
Как правило, по ходу работы приложения требуется менять текущие свойства материала, как это делается в проекте из подкаталога Ех07. На экране вращается кубик, "проткнутый" тремя цилиндрами (Рисунок 4.3).
Только континенты непрозрачны
Для использования альфа-компонента потребовался вспомогательный динамический массив, в который последовательно перебрасываются данные из основного. Согласно замыслу точки с преобладанием синего цвета должны быть полупрозрачными, значение альфа для них задается маленьким, для всех остальных точек это значение равно 1 (вы можете варьировать все параметры для получения своих собственных эффектов):
GetMem (Data, ImageSize * 3); // обычный массив образа
GetMem (DataA, ImageSize * 4); // вспомогательный + альфа-компонент
// для точек try
GetDIBits (MemDC, Bitmap.Handle, 0, biHeight, Data, BMInfo, DIB RGB COLORS);
// переставляем синий и красный цвета
For I := 0 to ImageSize - 1 do begin
Temp := Data [I * 3];
Data [I * 3] := Data [I * 3 + 2];
Data [I * 3 + 2] := Temp;
end;
// точки с преобладанием синего делаем полупрозрачными
For I := 0 to ImageSize - 1 do Begin
DataA [I * 4] := Data [I * 3]; // перебрасываем данные
DataA [I * 4 + 1] := Data [I * 3 + 1];
DataA [I * 4 + 2] := Data [I * 3 + 2];
If (Data [I * 3 + 2] > 50) and // чтобы участки белого цвета
(Data [I * 3 + 1] < 200) and // не стали полупрозрачными
(Data [I * 3] < 200)
then DataA [I * 4 + 3] := 27 // степень прозрачности синего
else DataA [I * 4 + 3] := 255; // сплошным
end;
// !!! - при подготовке текстуры добавился альфа-компонент
glTexImage2d(GL_TEXTURE_2D, 0, 3, biWidth,
biHeight, 0,
GL_RGBA, // здесь надо было скорректировать
GL_UNSIGNED_BYTE, DataA);
При описании списка не забываем сортировать многоугольники, иначе появится ненужный узор:
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glNewList (Earth, GL_COMPILE);
PrepareImage ('..\earth.bmp');
// оптимальнее включать режимы только при необходимости
glEnable (GL_BLEND);
glEnable(GL_CULL_FACE); // сортировка многоугольников
glCullFace(GL_FRONT);
gluSphere (Quadric, 1.0, 24, 24);
glCullFace(GL_BACK);
gluSphere (Quadric, 1.0, 24, 24);
glDisable(GL_CULL_FACE); // отключаем режимы
glDisable (GL_BLEND);
glEndList;
Думаю, здесь все понятно, и мы можем перейти к следующему примеру, чтобы научиться накладывать несколько текстур, что реализуется в проекте из подкаталога Ex90 (Рисунок 4.63).
Туман
Туман является самым простым в использовании спецэффектом, предназначенным для передачи глубины пространства. Он позволяет имитировать атмосферные эффекты дымки и собственно тумана.
При его использовании объекты сцены перестают быть чересчур яркими и становятся более реалистичными, естественными для восприятия.
Посмотрим на готовом примере, как работать в OpenGL с этим эффектом.
В этом примере содержимое экрана запоминается в массиве
Фиолетовая сфера надвигается на наблюдателя и "растворяется" После того как сфера ушла из поля зрения, на сцене остается только фоновая картинка
Трюк заключается в следующем: при самом первом воспроизведении кадра рисуется только площадка, служащая фоном Содержимое экрана сразу же после этого запоминается в массиве, делается как бы слепок экрана:
If first then begin // сделать только один раз
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glCallList(walls); // нарисовать площадку фона
// делаем снимок с экрана
glReadPixels(О, О, 255, 255, GL_RGBA, GL_UNSIGNED_BYTE, @pixels);
first := FALSE; // устанавливаем флаг
Makelmage; // подготовка списка фона
end
else glCallList(zaplmage); // вызов списка фона glPushMatrix;
// рисуем фиолетовую сферу
glTranslatef (20.0, 5.0, 5.0 + dz);
glCallList (sphere);
glPopMatrix;
Если не использовать особых приемов, то либо на экране останутся следы от сферы, либо фоновая картинка не будет объемной.
Для простоты массив, хранящий снимок с экрана, взят постоянных размеров, под клиентскую область экрана 255x255:
Pixels : Array [0..254, 0..254, 0..3] of GLUbyte;
Из-за этого упрощения при изменении размеров окна картинка портится и даже возможно аварийное завершение работы приложения
Замечание
Можно либо запретить изменять размеры окна, либо менять размерность массива при их изменении.
Поскольку запоминаются компоненты RGBA для каждой точки экрана, последний индекс имеет четыре значения Альфа-компонент в этом примере можно и не запоминать, однако размерность массива все равно нужно будет оставить такой же
В процедуре подготовки списка фона текущая видовая проекция сохраняется и подменяется на ортографическую, в которой позиция вывода растра устанавливается в нужную точку. В принципе, сразу после этого можно выводить массив пикселов на экран, но в примере сделано эффектнее - видовая матрица восстанавливается, и только после этого выводится битовый массив
Обратите внимание, что вывод массива пикселов на экран осуществляется при запрещенной записи в буфер глубины, иначе выводимая фоновая картинка оказывается по глубине на переднем плане и будет загораживать выводимую затем сферу:
procedure TfrmGL.Makelmage;
begin
glNewList(zapImage,GL_COMPILE);
glDisable(GL_LIGHTING);
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix; glLoadldentity;
glOrthof(0.0, ClientWidth, 0.0, ClientHeight, -5.0, 50.0);
glMatrixMode(GLJMODELVIEW);
glPushMatrix; glLoadldentity;
glRasterPos2i(0,0);
glPopMatrix;
glMatrixMode(GL_PROJECTION);
glPopMatrix;
glMatrixMode(GL_MODELVIEW);
glDisable(GL_DEPTH_TEST); // буфер глубины - только для чтения
// вывод на экран массива пикселов
glDrawPixels(ClientWidth, ClientHeight, GL RGBA, GL UNSIGNED BYTE,
@pixels);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEndList;
end;
В качестве образа текстуры можно взять часть экрана
В примере на экране находится лупа, которая передвигается с помощью клавиш управления курсором.
Собственно лупа представляет собой полусферу. Обратите внимание, что кривизну сферы можно менять для варьирования величины искажений:
const eqn : Array [0..3] of GLdouble = (0.0, -1.0, 0.0, 0.0);
glNewList (Zoom, GL_COMPILE);
glClipPlane (GL_CLIP_PLANEO, @eqn);
glEnable (GL_CLIP_PLANEO);
glScalef (1.0, 0.15, 1.0); // уменьшаем кривизну полусферы
glEnable(GL_TEXTURE_2D);
gluSphere (Quadric, 0.5, 24, 24);
glDisable(GL_TEXTURE_2D); glDisable (GL_CLIP__PLANEO) ;
glEndList;
После воспроизведения основного объекта, планеты, считываем в массив часть экрана, затем используем этот массив в качестве образа для текстуры, накладываемой на полусферу:
glReadPixels (posX, posY, 128, 128, GL_RGB, GL_UNSIGNED_BYTE, @Pixels);
glPushMatrix;
glTranslatef (AddX, -5.0, AddZ);
glTex!mage2d(GL_TEXTURE_2D, 0, 3, 128, 128,
0, GL_RGB, GL_UNSIGNED_BYTE, @Pixels);
glCallList(Zoom) ;
glPopMatrix;
На этом примере главу 4 действительно можно считать законченной.
В примере используется двумерная текстура
Замечание
Для двумерной текстуры размеры массива образа должны быть степенью двойки, например, массив размером 256x512 подходит, а 255x255 - нет.
Помимо массива для основной текстуры вводится вспомогательный массив для подобраза, необходимый для работы команды glTexSubimage2D:
const
checklmageWidth = 64; // ширина текстуры шахматной доски
checklmageHeight = 64; // высота текстуры шахматной доски
sublmageWidth =16; // размеры вспомогательного массива
sublmageHeight = 16;
var
checklmage : Array [0..checklmageHeight - 1, 0..checklmageWidth - 1,
0..3] of GLubyte;
sublmage : Array [0..sublmageHeight - 1, 0..sublmageWidth - 1, 0..3] of GLubyte;
После того как массив заполнен, устанавливается двумерная текстура, параметры задаются только самые необходимые:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTex!mage2D(GL_TEXTURE_2D, 0, GL_RGBA, checklmageWidth,
checklmageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, @checklmage);
Последняя команда в этой последовательности имеет параметры со смыслами, аналогичными одномерной текстуре, и точно так же я рекомендую не менять их значения за исключением аргументов, связанных с массивом образа текстуры.
При воспроизведении кадра на время рисования площадки включается режим наложения двумерной текстуры:
glEnable(GL_TEXTURE_2D); // включить режим
//не учитывать цвет примитивов
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glBegin(GL_QUADS); // два независимых четырехугольника
glTexCoord2f(0.0, 0.0);
glVertexSf(-2.О, -1.0, 0.0);
glTexCoord2f(0.0, 1.0);
glVertex3f(-2.0, 1.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertexSf(0.0, 1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertexSf(0.0, -1.0, 0.0);
glTexCoord2f(0.0, 0.0);
glVertexSf(1.0, -1.0, 0.0);
glTexCoord2f(0.0, 1.0);
glVertexSf(1.0, 1.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertexSf(2.41421, 1.0, -1.41421);
glTexCoord2f(1.0, 0.0);
glVertexSf(2.41421, -1.0, -1.41421);
glEnd;
9lDisable(GL_TEXTURE_2D); // режим отключается, немного экономится время
Режим GL_DECAL включается для того, чтобы цвета текстуры и поверхности Не смешивались. Можете задать текущим произвольный цвет - примитивы. не окрасятся, а если отключить этот режим, то шашки станут окрашенными. Здесь этот режим включается для того, чтобы не появлялись искажения при наложении текстуры на правую площадку. Пример иллюстрирует также работу команды glTexSubimage2D, позволяющей подменять часть образа текстуры. В примере при нажатии клавиши 'S' на первоначальной текстуре появляется квадратик с мелкой красной шашечкой, а после нажатия на 'R' текстура восстанавливается:
if Key = Ord ('S') then Begin // "Подобраз"
glTexSubImage2D(GL_TEXTURE_2D, 0,
12, 44, // x, у в координатах текстуры
subImageWidth, subImageHeight, GL_RGBA, GL_UNSIGNED_BYTE, @subImage);
InvalidateRect(Handle, nil, False);
end;
If Key = Ord ('R') then Begin // восстановление
// заново переустановить текстуру
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth, checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, @checkImage);
InvalidateRect(Handle, nil, False);
end;
Команда glTexSubimage2D не представлена ни в файле справки, ни в модуле opengl. pas, ее прототип я описал в программе.
Рисунок 4. 56 иллюстрирует работу следующего примера, располагающегося в подкаталоге Ex83 и подсказывающего, как можно наложить блики от источника света на поверхность, покрытую текстурой.
В примере можно менять текущие установки источника света
Пункт меню Reset позволяет вернуться к первоначальным установкам, соответствующим принятым в OpenGL по умолчанию
Проведем простые эксперименты. Задайте фоновое отражение каким-либо чистым цветом, например красным. После этого наименее освещенные части поверхности сферы окрашиваются этим цветом. Если значения цветового фильтра, RGB, тоже установить красными, вся сфера окрасится равномерно.
Верните значения установок в первоначальные и установите значения диффузного отражения в тот же чистый цвет. Теперь в этот цвет окрашиваются участки поверхности сферы, наиболее сильно освещенные источником света. Слабо освещенные участки окрашены в оттенки серого. Если RGB установить в этот же цвет, насыщенность им увеличивается по всей поверхности. Если обе характеристики задать равными, поверхность равномерно окрашивается ровным светлым оттенком.
Проект этот простой, но весьма интересный. Самые красивые результаты получаются при цветовом фильтре из смеси базовых цветов и отражениях, заданных в чистые цвета.
В зеркальце можно увидеть, что происходит за спиной наблюдателя
Для зеркальца на экране создается вырезка, точка зрения наблюдателя разворачивается на 180 градусов, и сцена воспроизводится второй раз:
// обычная точка зрения
glViewport(0, 0, ClientWidth, ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadldentity;
gluPerspective(45.0, ClientWidth / ClientHeight, 0.1, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadldentity;
gluLookAt(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
// воспроизводим систему из четырех кубиков
glPushMatrix;
glRotatef(Angle, 0.0, 1.0, 0.0);
glCallList(l);
glPopMatrix;
// точка зрения для зеркальца
glDisable(GL_LIGHTING); // чтобы линии не стали серыми
glViewport(ClientWidth shr 2, ClientHeight-(ClientHeight shr 2),
ClientWidth shr 1, ClientHeight shr 3) ;
glEnable(GL_SCISSOR_TEST); // вырезка для зеркальца
glScissor(ClientWidth shr 2, ClientHeight-(ClientHeight shr 2),
ClientWidth shr 1, ClientHeight shr 3);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadldentity; glMatrixMode(GL_PROJECTION);
glLoadldentity;
glOrtho(-1.001, 1.001, -1.001, 1.001, -1.001, 1.001); // белая рамочка вокруг зеркальца
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINE_LOOP);
glVertex3i(-1, 1, 1);
glVertex3i( 1, 1, 1);
glVertex3i( 1,-1, 1);
glVertexSi (-1,-1, 1);
glEnd;
// вид внутри зеркальца glLoadldentity;
a := (ClientWidth shr 1) / (ClientHeight shr 3);
b := (ClientHeight shr 3)/ (ClientWidth shr 1);
gluPerspective(45.0*b, a, 0.1, 100.0);
// разворачиваемся на 180 градусов
glMatrixMode(GL_MODELVIEW);
glLoadldentity;
9luLookAt( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glEnable(GL_LIGHTING);
glScalef(-1.0, 1.0, 1.0); // важно - эмулируем зеркало
//Разворачиваем нормали кубиков, так эффектнее
glFrontFace(GL_CW);
glPushMatrix;
glRotatef(Angle, 0.0, 1.0, 0.0);
glCallList(l);
glPopMatrix;
glFrontFace(GL_CCW);
glDisable(GL_SCISSOR_TEST);
Вывод на палитру в 256 цветов
В этом разделе я смогу вернуть некоторые свои долги, накопившиеся в предыдущих главах.
Ранее я обещал показать, почему наши самые первые примеры, где для упрощения не использовались операции с матрицей проекции, не годятся в качестве шаблона проектов.
Одну из таких программ я взял в качестве основы проекта из подкаталога Ех25, но вместо кубика на сцену поместил чайник.
Если при задании параметров вида не использовать ставшей привычной для нас последовательности операций, чайник выглядит на экране совсем невзрачным, будто вывернутым наизнанку.
Специально для этого случая приходится задавать модель освещенности с расчетом освещенности для обеих сторон многоугольников. Но и после этого тестовая фигура выглядит не очень эффектно.
Для тренировки я вставил строки с обычной последовательностью действий с матрицами проекций и матрицей моделирования, они первоначально закомментированы. Если вы уберете знаки комментария и удалите строки первого варианта, то тестовая фигура будет выглядеть на порядок эффектнее без каких-то дополнительных манипуляций:
procedure TfrmGL.FormResize(Sender: TObject);
begin glViewport(0, 0, ClientWidth, ClientHeight);
// один вариант
{--------------------}
glLoadldentity;
glFrustumf(1, 1, -1, 1, 5, 10) ;
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
{-------------}
// второй вариант
{ --------------------}
{ glMatrixMode(GL_PROJECTION);
glLoadldentity;
glFrustum (-1, 1, -1, 1, 5, 10);
glMatrixMode(GL_MODELVIEW);
glLoadldentity;
{-------------------------}
glTranslatef(0.0, 0.0, -8.0); // перенос объекта - ось Z
glRotatef(30.0, 1.0, 0.0, 0.0); // поворот объекта - ось X
glRotatef(70.0, 0.0, 1.0, 0.0); // поворот объекта - ось Y
InvalidateRect(Handle, nil, False);
end;
Надеюсь, это вас убедило в том, что подход, используемый в первых примерах, не годится для проектов с повышенными требованиями к качеству изображения.
Теперь по поводу палитры в 256 цветов. Все наши приложения не могут осуществлять корректный вывод на такую палитру, если текущие настройки экрана заданы с таким значением: экран приложения, использующего OpenGL, залит черным, а вместо объектов сцены выводятся чаще всего малопонятные скопления точек. Предполагая, что такая палитра сегодня мало используется, я не стал корректировать палитру в каждом проекте, ограничусь единственным примером на эту тему.
Замечание
Если вы собираетесь распространять свои приложения, то я вам рекомендую позаботиться и о тех пользователях, которые по каким-либо причинам используют только 256 цветов. Качество воспроизведения, конечно, при этом страдает, но это все же лучше, чем ничего.
Проект из подкаталога Ех26 иллюстрирует, какие действия необходимо предпринять, если формат пиксела содержит установленный флаг, указывающий на необходимость корректировки палитры (PFD_NEED PALETTE).
Код я снабдил комментариями, с помощью которых вы при наличии желания сможете в нем разобраться. Не будем подробно рассматривать производимые манипуляции, поскольку эта тема не относится непосредственно к библиотеке OpenGL. Замечу только, что обработку сообщений, связанных с изменением системной палитры, в принципе можно и не производить, Делается это для корректности работы приложения.