Учебник по Visual C++ .Net

         

Строим икосаэдр


Для иллюстрации работы с массивами вершин создадим более сложный объект — икосаэдр. Это такой дссятистенный дом с острой пятиугольной крышей и таким же полом, но углы пола смещены (повернуты) на л/5 относительно углов потолка.

Икосаэдр имеет 20 треугольных граней и 12 вершин (1 + 5 на потолке и 1 + 5 на полу). Благодаря своей правильности он может быть задан с помощью всего лишь двух чисел, которые лучше вычислить один раз и запомнить. Этими числами является косинус и синус угла в три пятых окружности, то есть

static double

//====== atan(l.) - это пи/4

angle = 3. * atan(1.)/2.5, //====== 2 характерные точки

V = cos(angle), W = sin(angle);

Этот код мы вставим внутрь функции рисования, чтобы не плодить глобальные переменные и не нарываться на конфликты имен. Вот новая версия функции DrawScene:

void DrawScene() { static double

//====== 2 характерные точки

angle = 3. * atan(l.)/2.5, V = cos(angle), W = sin(angle),

//=== 20 граней икосаэдра, заданные индексами вершин

static GLuint id[20][3] =



(0,1, 4), (8,1,10), (7,3,10), (6,10,1),

(0,4, 9), (8,10,3), (7,10,6), (9,11,0),

(9,4, 5), (5,8, 3), (7,6,11), (9,2,11),

(4,8, 5), (5,3, 2), (11,6,0), (9,5, 2),

(4,1,8), (2,3,7), (0,6,1), (7,11,2)

//====== Начинаем формировать список команд

glNewList (1,GL_COMPILE) ;

//====== Выбираем текущий цвет рисования

glColor3d (1., 0.4, 1 . ) ;

glBegin (GLJTRIANGLES) ;

for (int i = 0; i < 20; i++)

{

//====== Грубый подход к вычислению нормалей

glNorma!3dv(v[id[i] [0] ] ) ;

glVertex3dv(v[id[i] [0] ] ) ;

glNorma!3dv(v[id[i] [1] ] ) ;

glVertex3dv(v[id[i] [1] ] ) ;

glNorma!3dv(v[id[i] [2] ] ) ;

glVertex3dv(v[id[i] [2] ] ) ;

}

glEnd() ;

//====== Конец списка команд

glEndList ();

}

Точное вычисление нормалей

Проверьте результат и обсудите качество. В данном варианте нормали в вершинах заданы так, как будто изображаемой фигурой является сфера, а не икосаэдр. Это достаточно грубое приближение. Если поверхность произвольного вида составлена из треугольников, то вектор нормали к поверхности каждого из них можно вычислить точно, опираясь на данные о координатах вершин треугольника. Из $ курса векторной алгебры вы, вероятно, помните, что векторное произведение двух векторов а и b определяется как вектор п, перпендикулярный к плоскости, в которой лежат исходные векторы. Величина его равна площади параллелограмма, построенного на векторах а и b как на сторонах, а направление определяется так, что векторы a, b и п образуют правую тройку. Последнее означает, что если представить наблюдателя на конце вектора п, то он видит поворот вектора а к вектору b, совершаемый по кратчайшему пути против часовой стрелки. На рис. 6.4. изображена нормаль п (правая тройка) при различной ориентации перемножаемых векторов а и b.




Рис. 6.2. Ориентация вектора нормали

Если координаты векторов а и b известны, то координаты нормали вычисляю по следующим формулам. Длина вектора нормали п зависит от длин вектор сомножителей и величины угла между ними:

Nx=AxBz-AzBy

Ny=AzBx-AxBz

Nz=AxBy-AyBx

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



Чтобы нивелировать зависимость цвета вершины от амплитуды нормали, обыч вектор нормали масштабируют (или нормируют), то есть делают его длину р; ной единице, оставляя неизменным направление. С учетом сказанного создал две вспомогательные функции. Первая масштабирует, а вторая вычисляет н< маль к плоскости треугольника. Алгоритм вычисления использует координа двух сторон, прилегающих к текущей вершине треугольника:

//====Нормирование вектора нормали (или любого другого)

void Scale(double v[3])

{

double d = sqrt(v[0]*v[0]+v[l]*v[l]+v[2]*v[2]);

if (d == 0.)

{

MessageBox(0,"Zero length vector","Error",MB_OK);

return;

}

void getNorm(double vl[3], double v2[3], double out[3])

{

//===== Вычисляем координаты вектора нормали

//====== по формулам векторного произведения

out[0] = vl[l]*v2[2] - vl[2]*v2[l];

out[l] = vl[2]*v2(0] - vl[0]*v2[2] ;

out[2] =vl[0]*v2[l] - vl[l]*v2[0];

Scale(out);

}

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

void DrawScene()

{

static double

angle - 3. * atanfl.)/2.5, V = cos(angle), W = sin(angle),

v[12] [3] = {

{-V,0.,W}, {V,0.,W}, {-V,0.,-W},

{V,0.,-W}, {0.,W,V}, {0.,W,-V},

{0.,-W,V}, {0. ,-W,-V}, {W,V, 0.},



{-W,V,0.}, {W,-V,0.}, {-W,-V,0.}

};

static GLuint id[20][3] = {

(0,1, 4), {0,4, 9}, (9,4, 5), (4,8, 5}, (4,1,8),

(8,1,10), (8,10,3), (5,8, 3), (5,3, 2), (2,3,7),

(7,3,10), (7,10,6), (7,6,11), (11,6,0), (0,6,1),

(6,10,1), (9,11,0), (9,2,11), (9,5, 2), (7,11,2) 1;

glNewList(l,GL_COMPILE); glColorSd (1., 0.4, 1.) ;

glBegin(GLJTRIANGLES);

for (int i = 0; i < 20; i++)

{

double dl[3], d2[3], norm[3];

for (int j = 0; j < 3; j++)

{

dl[j] =v[id[i][0]] [j] -v[id[i][l]J [j];

d2[j] =v[id[i][l]] [j] -v[id[i][2J] [j];

}

//====== Вычисление и масштабирование нормали

getNorm(dl, d2, norm);

glNormal3dv(norm);

glVertexSdv(v [ id[i] [1]]);

glVertex3dv(v[id[i] [1] ] glVertex3dv(v[id[i] [2] ]

glEnd() ;

}

glEndList () ;

}

Функцию нормировки всех нормалей можно возложить на автомат OpenGL, если включить состояние GL_NORMALIZE, но обычно это ведет к замедлению перерисовки и, как следствие, выполнения приложения, если изображение достаточно сложное. В нашем случае оно просто, и поэтому вы можете проверить действие настройки, если вставите вызов glEnable (GL_NORMALIZE); в функцию Init (до вызова OrawScene) и временно выключите вызов Scale(out); производимый в функции getNorm. Затем вернитесь к исходному состоянию.


Содержание раздела