
Создайте окно программы и разместите на ней элемент openglsimplecontrol , как показано на рисунке 1, после чего установите его размеры 500х500. Переименуйте данный объект, дав ему имя AnT.

Рисунок 1.
Справа от данного элемента разместите элемент comboBox, после чего в его свойствах установите значение параметра DropDownStyle = DropDownList. После этого выпадающие элементы перестанут быть доступными для редактирования. После этого, измените элементы Items как показано на рисунке 2.

Рисунок 2.
Помимо этого установите элемент trackBar в окно формы. Перейдя к его свойствам установите rientation равным Vertical. Максимальный диапазон установите равным ста.
Так же не забудьте установить ссылки на используемые библиотеки Tao (рисунок 3).

Рисунок 3.
Для реализации визуализации будет использоваться таймер – после инициализации окна он будет генерировать событие, называемое тиком таймера, раз в 30 миллисекунд – добавьте элемент таймер, переименуйте экземпляр в RenderTimer и установите время тика 30 миллисекунд (как показано на рисунке 5), а так же добавьте ему событие для обработки тика.

Рисунок 4.
Инициализация окна и OpenGl происходит, так же как и в предыдущих проектах.
Нам потребуется объявить ряд переменных, для дальнейшей работы программы:
InitializeComponent();}
AnT.InitializeContexts();
Как и раньше, функция Form1_Load отвечает за инициализацию OpenGL. Но теперь, помимо этого здесь происходит построение массива геометрии тела, постоенного вращением на основе заданного заранее массива GeometricArray.
Код этой функции максимально подробно комментирован.
// инициализация Glut}
Glut.glutInit();
Glut.glutInitDisplayMode( Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH);
// очитка окна
Gl.glClearColor(255, 255, 255, 1);
// установка порта вывода в соотвествии с размерами элемента anT
Gl.glViewport(0, 0, AnT.Width, AnT.Height);
// настройка проекции
Gl.glMatrixMode( Gl.GL_PROJECTION);
Gl.glLoadIdentity();
Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200);
Gl.glMatrixMode( Gl.GL_MODELVIEW);
Gl.glLoadIdentity();
// настройка параметров OpenGL для визуализации
Gl.glEnable( Gl.GL_DEPTH_TEST);
Gl.glEnable( Gl.GL_LIGHTING);
Gl.glEnable( Gl.GL_LIGHT0);
// количество элементов последовательности геометрии, на основе которых будет строится тело вращения
count_elements = 8;
// непосредственное заполнение точек.
// после изменения данной геометрии мы сразу получим новое тело вращения.
GeometricArray[0,0] = 0;
GeometricArray[0,1] = 0;
GeometricArray[0,2] = 0;
GeometricArray[1,0] = 0.7;
GeometricArray[1,1] = 0;
GeometricArray[1,2] = 1;
GeometricArray[2, 0] = 1.3;
GeometricArray[2, 1] = 0;
GeometricArray[2, 2] = 2;
GeometricArray[3,0] = 1.0;
GeometricArray[3,1] = 0;
GeometricArray[3,2] = 3;
GeometricArray[4, 0] = 0.5;
GeometricArray[4, 1] = 0;
GeometricArray[4, 2] = 4;
GeometricArray[5, 0] = 3;
GeometricArray[5, 1] = 0;
GeometricArray[5, 2] = 6;
GeometricArray[6, 0] = 1;
GeometricArray[6, 1] = 0;
GeometricArray[6, 2] = 7;
GeometricArray[7, 0] = 0;
GeometricArray[7, 1] = 0;
GeometricArray[7, 2] = 7.2f;
// по умолчанию мы будем отрисовывать фигуру в режиме GL_POINTS
comboBox1.SelectedIndex = 0;
// построение геометрии тела вращения
// принцип сводится к 2м цилкам - на основе первого перебираются
// вершины в геометрической последовательности
// второй использую параметр Iter производит поворот последней линии геометрии вокруг центра тела вращения
// при этом используется заранее определенный угол angle который определяется как 2*Pi / количество медиан объекта
// за счет выполнения этого алгоритма получается набор вершин описывающих оболочку тела врещения.
// остается только соединить эти точки в режиме рисования примитивов для получения
// визуализированного объекта
// цикл по последовательности точек кривой, на основе которой будет построено тело вращения
for ( int ax = 0; ax < count_elements; ax++)
{// цикла по медианам объекта, заранее определенным в программе
for ( int bx = 0; bx < Iter; bx++)
{// для всех (bx > 0) элементов алгоритма используются предыдушая построенная последовательность
// для ее поворота на установленный угол
if (bx > 0)
{double new_x = ResaultGeometric[ax, bx - 1, 0] * Math.Cos(Angle) - ResaultGeometric[ax, bx - 1, 1] * Math.Sin(Angle);
double new_y = ResaultGeometric[ax, bx - 1, 0] * Math.Sin(Angle) + ResaultGeometric[ax, bx - 1, 1] * Math.Cos(Angle);
ResaultGeometric[ax, bx, 0] = new_x;
ResaultGeometric[ax, bx, 1] = new_y;
ResaultGeometric[ax, bx, 2] = GeometricArray[ax, 2];}
else // для построения первой мидианы мы используем начальную кривую, описывая ее нулевым значением угла поворота
{double new_x = GeometricArray[ax, 0] *Math.Cos(0) - GeometricArray[ax, 1] * Math.Sin(0);
double new_y = GeometricArray[ax, 1] *Math.Sin(0) + GeometricArray[ax, 1] * Math.Cos(0);
ResaultGeometric[ax, bx, 0] = new_x;
ResaultGeometric[ax, bx, 1] = new_y;
ResaultGeometric[ax, bx, 2] = GeometricArray[ax, 2];}
}
}
// активация таймера
RenderTimer.Start();
Итак, геометрии объекта построена, остается обработать сообщение таймера, для вызова функции отрисовки , а так же реализовать непосредственно функцию Draw.
В функции Draw мы рассмотрим 3 вида визуализации, которые будут использованы в зависимости от установленного режима в элементе comboBox. Визуализация с помощью точек – самая простая.
Визуализация с помощью линий или полигонов – уже сложнее. Постарайтесь максимально разобрать алгоритм, чтобы понять суть его работы.
// вызываем функцию, отвечающей за отрисовку сцены}
Draw();
// два параметра, которые мы будем использовать для непрерывного вращения сцены вокруг 2 координатных осей}
rot_1++;
rot_2++; // очистка буфера цвета и буфера глубины
Gl.glClear( Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
Gl.glClearColor(255, 255, 255, 1);
// очищение текущей матрицы
Gl.glLoadIdentity();
// установка положения камеры (наблюдателя). Как видно из кода
// дополнительно на полложение наблюдателя по оси Z влияет значение
// установленное в ползунке, доступном для пользователя.
// таким образом, при перемещении ползунка, наблюдатель будет отдалятся или приближатся к объекту наблюдения
Gl.glTranslated(0, 0, -7 -trackBar1.Value);
// 2 поворота (углы rot_1 и rot_2)
Gl.glRotated(rot_1, 1, 0, 0);
Gl.glRotated(rot_2, 0, 1, 0);
// устанавливаем размер точек равный 5
Gl.glPointSize(5.0f);
// условие switch определяет установленный режим отображения, на основе выбранного пункта элемента
// comboBox, установленного в форме программы
switch (comboBox1.SelectedIndex)
{case 0: // отображение в виде точек
{// режим вывода геометрии - точки
Gl.glBegin( Gl.GL_POINTS);
// выводим всю ранее просчитанную геометрию объекта
for ( int ax = 0; ax < count_elements; ax++)
{for ( int bx = 0; bx < Iter; bx++)
{// отрисовка точки
Gl.glVertex3d(ResaultGeometric[ax, bx, 0], ResaultGeometric[ax, bx, 1], ResaultGeometric[ax, bx, 2]);}
}
// завершаем режим рисования
Gl.glEnd();
break ;}
case 1: // отображение объекта в сеточном режиме, используя режим GL_LINES_STRIP
{// устанавливаем режим отрисвки линиями (последовательность линий)
Gl.glBegin( Gl.GL_LINE_STRIP);
for ( int ax = 0; ax < count_elements; ax++)
{for ( int bx = 0; bx < Iter; bx++)
{
Gl.glVertex3d(ResaultGeometric[ax, bx, 0], ResaultGeometric[ax, bx, 1], ResaultGeometric[ax, bx, 2]);
Gl.glVertex3d(ResaultGeometric[ax + 1, bx, 0], ResaultGeometric[ax + 1, bx, 1], ResaultGeometric[ax + 1, bx, 2]);
if (bx + 1 < Iter - 1)
{Gl.glVertex3d(ResaultGeometric[ax + 1, bx + 1, 0], ResaultGeometric[ax + 1, bx + 1, 1], ResaultGeometric[ax + 1, bx + 1, 2]);
}
else
{Gl.glVertex3d(ResaultGeometric[ax + 1, 0, 0], ResaultGeometric[ax + 1, 0, 1], ResaultGeometric[ax + 1, 0, 2]);
}
}
}
Gl.glEnd();
break ;}
case 2: // отрисовка оболочки с расчетом нормалей для корректного затенения граней объекта
{Gl.glBegin( Gl.GL_QUADS); // режим отрисовки полигонов состоящих из 4 вершин
for ( int ax = 0; ax < count_elements; ax++)
{for ( int bx = 0; bx < Iter; bx++)
{// вспомогательные переменные, для более наглядного использования кода при расчете нормалей
double x1 = 0, x2 = 0, x3 = 0, x4 = 0, y1 = 0, y2 = 0, y3 = 0, y4 = 0, z1 = 0, z2 = 0, z3 = 0, z4 = 0;
// первая вершина
x1 = ResaultGeometric[ax, bx, 0];
y1 = ResaultGeometric[ax, bx, 1];
z1 = ResaultGeometric[ax, bx, 2];
if (ax + 1 < count_elements) // если текущий ax не последний
{// берем следующую точку последовательности
x2 = ResaultGeometric[ax + 1, bx, 0];
y2 = ResaultGeometric[ax + 1, bx, 1];
z2 = ResaultGeometric[ax + 1, bx, 2];
if (bx + 1 < Iter - 1) // если текущий bx не последний
{// берем следующую точку последовательности и следующий медивн
x3 = ResaultGeometric[ax + 1, bx + 1, 0];
y3 = ResaultGeometric[ax + 1, bx + 1, 1];
z3 = ResaultGeometric[ax + 1, bx + 1, 2];
// точка соотвествующуя по номеру , только на соседнем медиане
x4 = ResaultGeometric[ax, bx + 1, 0];
y4 = ResaultGeometric[ax, bx + 1, 1];
z4 = ResaultGeometric[ax, bx + 1, 2];}
else
{// если это последний медиан - то в качесвте след. мы берем начальный (замыкаем геометрию фигуры)
x3 = ResaultGeometric[ax + 1, 0, 0];
y3 = ResaultGeometric[ax + 1, 0, 1];
z3 = ResaultGeometric[ax + 1, 0, 2];
x4 = ResaultGeometric[ax, 0, 0];
y4 = ResaultGeometric[ax, 0, 1];
z4 = ResaultGeometric[ax, 0, 2];}
}
else // данный элемент ax последний, следовательно мы будем использовать начальный (нулевой) вместо данного ax
{// слудуещей точкой будет нулевая ax
x2 = ResaultGeometric[0, bx, 0];
y2 = ResaultGeometric[0, bx, 1];
z2 = ResaultGeometric[0, bx, 2];
if (bx + 1 < Iter - 1)
{x3 = ResaultGeometric[0, bx + 1, 0];
y3 = ResaultGeometric[0, bx + 1, 1];
z3 = ResaultGeometric[0, bx + 1, 2];
x4 = ResaultGeometric[ax, bx + 1, 0];
y4 = ResaultGeometric[ax, bx + 1, 1];
z4 = ResaultGeometric[ax, bx + 1, 2];}
else
{x3 = ResaultGeometric[0, 0, 0];
y3 = ResaultGeometric[0, 0, 1];
z3 = ResaultGeometric[0, 0, 2];
x4 = ResaultGeometric[ax, 0, 0];
y4 = ResaultGeometric[ax, 0, 1];
z4 = ResaultGeometric[ax, 0, 2];}
}
// переменные для расчета нормал
double n1 = 0, n2 = 0, n3 = 0;
// нормаль будем расчитывать как векторное произведение граней полигона
// для нулевого элемента нормаль мы будем считать немного по другому.
// на самом деле разница в расчете нормали актуальна только для 1 и последнего и первого полигона на медиане
if (ax == 0) // при расчете нормали для ax мы будем использовать точки 1,2,3
{n1 = (y2 - y1) * (z3 - z1) - (y3 - y1) * (z2 - z1);
n2 = (z2 - z1) * (x3 - x1) - (z3 - z1) * (x2 - x1);
n3 = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);}
else // для остальных - 1,3,4
{n1 = (y4 - y3) * (z1 - z3) - (y1 - y3) * (z4 - z3);
n2 = (z4 - z3) * (x1 - x3) - (z1 - z3) * (x4 - x3);
n3 = (x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3);}
// если не включен режим GL_NORMILIZE то мы должны в обязательном порядке
// произвести нормализацию вектора нормали, перед тем как передать информацию о нормали
double n5 = (double)Math.Sqrt(n1 * n1 + n2 * n2 + n3 * n3);
n1 /= (n5 + 0.01);
n2 /= (n5 + 0.01);
n3 /= (n5 + 0.01);
// передаем информацию о нормали
Gl.glNormal3d(-n1, -n2, -n3);
// передаем 4 вершины для отрисовки полигона
Gl.glVertex3d(x1, y1, z1);
Gl.glVertex3d(x2, y2, z2);
Gl.glVertex3d(x3, y3, z3);
Gl.glVertex3d(x4, y4, z4);}
}
// завершаем выбранный режим рисования полигонов
Gl.glEnd();
break ;}
}
// возвращаем сохраненную матрицу
Gl.glPopMatrix();
// завершаем рисование
Gl.glFlush();
// обновляем элемент AnT
AnT.Invalidate();
На рисунках 5,6,7 представлены результаты работы программы: вращающееся тело с различными режимами отрисвки геометрии.

Рисунок 5.

Рисунок 6.

Рисунок 7.
В следующей главе мы рассмотрим создание 3D объектов , реализация которых уже имеется в библиотеке GLUT, а так же функции openGL для изменения их положения в пространстве и масштабировании, без изменений в структуре геометрии объектов.
Скачать исходный код проекта - геометрические преобразования, openGL
Обсуждение данного урока: Формирование тел вращения - реализация алгоритма на OpenGl.
Далее: 11.1 Немного о библиотеке GLUT. Модельные трансформации. Оболочка программы.