Подготовка сцены 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 учитывает направление нормали (перпендикуляра) к поверхности. Реалистичность изображения во многом достигается благодаря аккуратному вычислению нормалей. Нормаль является характеристикой вершины (узла сетки).