Подготовка
сцены OpenGL
Считая, что
данные о координатах точек изображаемой поверхности уже известны и расположены
в контейнере m_cPoints, напишем коды функции DrawScene, которая создает изображение
поверхности и запоминает его в виде списка команд OpenGL. Как вы помните, одним
из технологических приемов OpenGL, которые ускоряют процесс передачи (rendering),
является предварительная заготовка изображения, то есть запоминание и компиляция
списка рисующих команд.
Напомним, что
отображаемый график представляет собой криволинейную поверхность (например,
равного уровня температуры). Ось Y, по которой откладываются интересующие пользователя
значения функции, направлена вверх. Ось X направлена вправо, а ось Z — вглубь
экрана. Часть плоскости (X, Z), для точек которой известны значения Y, представляет
собой координатную сетку. Изображаемая поверхность расположена над плоскостью
(X, Z), а точнее, над этой сеткой. Поверхность можно представить себе в виде
одеяла, сшитого из множества лоскутов. Каждый лоскут мы будем задавать в виде
четырехугольника, как-то ориентированного в пространстве. Все множество четырехугольников
поверхности также образует сетку. Для задания последовательности четырехугольников
в OpenGL существует пара команд:
glBegin
(GL_QUADS) ;
//
Здесь располагаются команды, задающие четырехугольники
glEnd()
;
Четырехугольник
задается координатами своих вершин. При задании координат какой-либо вершины,
например, командой givertex3f (х, у, z);, можно сразу же определить ее цвет,
например, командой gicolor3f (red, green, blue);. Если цвета вершин будут разными,
а режим заполнения равен константе GL_FILL, то цвета внутренних точек четырехугольника
примут промежуточное значение. Конвейер OpenGL производит аппроксимацию цвета
так, что при перемещении от одной вершины к другой он изменяется плавно.
Режим растеризации
или заполнения промежуточных точек графического примитива задается командой
glPolygonMode. OpenGL различает фронтальные (front-facing polygons), обратные
(back-facing polygons) и двухсторонние многоугольники. Режим заполнения их отличается,
поэтому первый параметр функции glPolygonMode должен определить тип полигона
(GL_FRONT, GL_BACK или GL_FRONT_AND_BACK).
Второй параметр
собственно и определяет режим заполнения. Он может принимать значение GL_POINT,
GL_LINE или GL_FILL. Первый выбор даст лишь обозначение примитива в виде его
вершин, второй — даст некий скелет, вершины будут соединены линиями, а третий
заполнит все промежуточные точки примитива. По умолчанию принят режим GL_FILL
и мы получаем сплошной лоскут.'Если в качестве первого параметра задать GL_FRONT_AND_BACK,
то изменения второго параметра будут касаться обеих поверхностей одеяла. Другие
сочетания дают на первый взгляд странные эффекты: так, если задать сочетание
(GL_FRONT, GL_LINE), то лицевая сторона одеяла будет обозначена каркасом (frame
view), а изнаночная по умолчанию будет сплошной (GL_FILL). Поверхность при этом
будет полупрозрачна.
Мы решили оставить
неизменным значение GL_FRONT_AND_BACK для первого параметра и дать пользователю
возможность изменять режим заполнения (второй параметр glPolygonMode) по его
желанию. Впоследствии внесем эту настройку в диалог свойств СОМ-объекта, а результат
выбора пользователя будем хранить в переменной m_FillMode. С учетом сказанного
введите коды реализации функции DrawScenel
//======
Подготовка изображения
void COpenGL::DrawScene()
{
//======
Создание списка рисующих команд
glNewListd,
GL_COMPILE) ;
//======
Установка режима заполнения
//======
внутренних точек полигонов
glPolygonMode(GL_FRONT_AND_BACK,
m_FillMode);
//======
Размеры изображаемого объекта
UINTnx
= m_xSize-l, nz = m_zSize-l;
//======
Выбор способа создания полигонов
if
(m_bQuad)
glBegin
(GL QUADS);
//===
Цикл прохода по слоям изображения (ось Z) for (UINT z=0, i=0; z<nz; z++,
i++)
//=== Связанные полигоны начинаются
//===
на каждой полосе вновь if (!m_bQuad)
glBegin(GL_QUAD_STRIP)
;
//=== Цикл прохода вдоль оси X
for (UINT x=0; x<nx ; х++, i++)
{
// i, j, k, n — 4 индекса вершин примитива при
// обходе в направлении против часовой стрелки
int j = i + m_xSize,
//
Индекс узла с большим Z
k
= j+1, // Индекс узла по диагонали
n
= i+1; // Индекс узла справа
// Выбор координат 4-х вершин из контейнера
float
xi
= m_cPoints [i] . х,
yi
= m_cPoints [i] .y,
zi
= m_cPoints [i] . z,
xj
= m_cPoints [ j ] .x,
yj
= m_cPoints [ j ] .y,
zj
= m_cPoints [ j ] .z,
xk
= m_cPoints [k] .x,
yk
= m_cPoints [k] . y,
zk
= m_cPoints [k] . z,
xn
= m_cPoints [n] .x,
yn
= m_cPoints [n] .y,
zn
= m_cPoints [n] . z,
//=== Координаты векторов боковых сторон
ах = xi-xn,
ay = yi-yn,
by = yj-yi,
bz
= zj-zi,
//=== Вычисление вектора нормали
vx = ay*bz,
vy = -bz*ax,
vz
= ax*by,
//===
Модуль нормали
v
= float (sqrt (vx*vx + vy*vy + vz*vz) ) ;
//======
Нормировка вектора нормали
vx /= v;
vy /= v;
vz
/= v;
//======
Задание вектора нормали
glNormalSf
(vx,vy
f
vz);
//
Ветвь создания несвязанных четырехугольников
if
(m_bQuad)
{
//======
Обход вершин осуществляется
//=== в направлении против часовой стрелки
glColorSf (0.2f, 0.8f, l.f);
glVertex3f (xi, yi, zi);
glColor3f <0.6f, 0.7f, l.f);
glVertexSf (xj, уj, zj);
glColorSf (0.7f, 0.9f, l.f);
glVertexSf (xk, yk, zk);
glColorSf (0.7f, 0.8f, l.f);
glVertexSf
(xn, yn, zn); }
else
//
Ветвь создания цепочки четырехугольников
{
glColor3f
(0.9f, 0..9f, l.Of);
glVertexSf
(xi, yi, zi);
glColorSf
(0.5f, 0.8f, l.0f);
glVertexSf (xj, уj, zj);
}
}
//======
Закрываем блок команд GL_QUAD_STRIP
if
(!m_bQuad)
glEnd();
}
//======
Закрываем блок команд GL_QUADS
if
(m_bQuad) glEnd() ;
//======
Закрываем список команд OpenGL
glEndList ();
}
Для осмысления алгоритма надо учитывать, что количество узлов сетки вдоль того или иного направления (X или Z) на единицу больше количества промежутков (ячеек). Кроме того, надо иметь в виду, что при расчете освещения OpenGL учитывает направление нормали (перпендикуляра) к поверхности. Реалистичность изображения во многом достигается благодаря аккуратному вычислению нормалей. Нормаль является характеристикой вершины (узла сетки).