В предыдущей главе мы рассмотрели пример программы, визуализировавшей каркас трехмерной сферы, но не углублялись в процесс настройки нашего приложения.
Теперь мы полностью проанализируем код этого приложения и объясним, каким образом происходит инициализация OpenGL и реализуется визуализация объектов.
В следующих частях этой главы и следующих 3 главах мы будем рассматривать вопросы визуализации 2D графики, а потом перейдем к программированию трехмерной графики.
Инициализация OpenGl
После того, как объект SimpleOpenGLControl прошел инициализацию, стартует загрузка формы. Мы всегда будем отслеживать это событие, так как именно здесь мы будем проводить начальную настройку нашей программы.Для визуализации трехмерной сцены, в предыдущей главе мы использовали следующий код:
/*http://esate.ru, Anvi*/
private void Form1_Load(object sender, EventArgs e) { // инициализация 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); }
Здесь в первую очередь проходит инициализация библиотеки Glut.
Как видно из кода, для работы с функциями библиотеки OpenGL используется класс Gl, находящийся в пространстве имен Tao.OpenGL.
Для работы с функциями библиотеки Glut используется класс Glut.
Таким образом, по сравнению с использованием этих библиотек в С++, мы всего лишь вызываем методы из классов соответствующих библиотек, где они очень удобно описаны.
Мы обязательно должны вызвать функцию glutInit() до того, как начнем использовать любые другие функции данной библиотеки, так как эта функция производит инициализацию библиотеки Glut.
Далее мы вызываем следующую функцию:
/*http://esate.ru, Anvi*/
Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH);
Эта функция устанавливает режим отображения. В нашем случае устанавливается RGB режим для визуализации (GLUT_RGB – это псевдоним GLUT_RGBA, он устанавливает RGBA режим битовой маски окна). Далее мы устанавливаем двойную буферизацию окна. Двойная буферизация, как правило, используется для устранения мерцания, возникающего в процессе быстрой перерисовки кадров несколько раз подряд. GLUT_DEPTH указывает при инициализации окна, если в приложении будет использоваться буфер глубины.
После инициализации окна мы устанавливаем цвет очистки окна с помощью функции:
/*http://esate.ru, Anvi*/
Gl.glClearColor(255, 255, 255, 1);
Чтобы анализировать код дальше, важно понимать, как устроен процесс создания сцены в OpenGL.
- Все начинается с позиционирования объема видимости в пространстве. Представьте, что мы установили камеру в каких-либо координатах.
- Теперь в данном пространстве мы устанавливаем некую модель (объект), которая будет попадать в объем видимости нашей камеры. Например, перед установленной камерой появился человек.
- Следующим шагом будет проецирование, в котором мы определим форму того объема в пространстве, который мы видим.
- И заключительным шагом мы получаем изображение объекта в рамках порта просмотра.
/*http://esate.ru, Anvi*/
Gl.glViewport(0, 0, AnT.Width, AnT.Height);
Здесь мы указываем библиотеке OpenGL на то, что вывод будет осуществляться во всей области элемента AnT (наш элемент, расположенный на форме для визуализации в него сцены). Определяем тот самый порт просмотра, куда последним шагом будет визуализироваться модель.
После этого происходит настройка проекции. Для этого мы сначала вызываем функцию:
/*http://esate.ru, Anvi*/
Gl.glMatrixMode(Gl.GL_PROJECTION);
Функция glMatrixMode предназначена для того, чтобы задавать матричный режим: будет определена матрица, над которой мы в дальнейшем будем производить операции. В нашем случае это GL_PROJECTION – матрица проекций.
Следующей командой мы очищаем матрицу с помощью функции glLoadIdentity (функция заменяет текущую матрицу на единичную). Далее мы устанавливаем тип текущей проекции с помощью функции gluPerspective.
/*http://esate.ru, Anvi*/
Gl.glLoadIdentity(); Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200);
Функция gluPerspective определена в библиотеке GLU – OpenGL Utility Library (GLU). Эта библиотека является надстройкой над библиотекой OpenGL, реализующей ряд более продвинутых функций. Она также является свободно распространяемой и поставляется вместе с библиотекой OpenGL.
Данная функция строит пирамиду охвата видимости, основываясь на угле визуального охвата, отношении сторон порта просмотра и установке ближней и дальней плоскости просмотра (рис. 1).
Рисунок 1. Пирамида видимости.
Теперь, когда проекция определена, мы устанавливаем в качестве текущей матрицы объектно-видовую матрицу и очищаем ее.
/*http://esate.ru, Anvi*/
Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity();
Теперь нам остается только включить некоторые опции, необходимые для корректной визуализации нашей сцены. Это тест глубины, а также отображение цветов материалов для рисуемой геометрии.
/*http://esate.ru, Anvi*/
Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glEnable(Gl.GL_COLOR_MATERIAL);
Вот и все, что касалось инициализации OpenGL.
Теперь рассмотрим, что же делалось в функции, визуализировавшей трехмерную сферу.
Код этой функции:
/*http://esate.ru, Anvi*/
// обработчик кнопки "визуализировать" private void button1_Click(object sender, EventArgs e) { Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); Gl.glLoadIdentity(); Gl.glPushMatrix(); Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0); // рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32); Gl.glPopMatrix(); Gl.glFlush(); AnT.Invalidate(); }
Когда пользователь нажимает на кнопку и вызывается данная функция, первым делом производится очистка окна (т.к. до этого уже мог быть реализован какой-либо вывод, очистка экрана перед визуализацией кадра – это стандартный метод).
Для этого используется функция glClear. В качестве параметра функция получает данные, значения каких буферов ей необходимо очистить. В нашем случае это буфер цвета и буфер глубины.
Теперь мы очищаем объектно-видовую матрицу. Таким образом, камера как бы установилась в начало координат. Теперь мы можем совершить ее перемещение в пространстве.
Но перед этим мы вызываем функцию:
/*http://esate.ru, Anvi*/
Gl.glColor3f(255, 0, 0);
которая устанавливает красный цвет отрисовки геометрии (и основывается на RGB составляющих).
Итак, перемещение. Функция glPushMatrix осуществляет помещение текущей матрицы в стэк матриц, откуда в дальнейшем мы сможем ее вернуть с помощью функции glPopMatrix.
Таким образом, мы осуществим перемещение отрисовываемой сферы в пространстве, не изменив саму матрицу, отвечающую за положение камеры (наблюдателя).
Если не использовать такой подход, то с каждым визуализированным кадром наша камера будет перемещаться, и очень скоро мы вообще не сможем ее найти.
Сохранив матрицу в стэке, мы производим перемещение объекта на 6 едениц по оси Z, после чего выполняем поворот сцены на 45 градусов сразу по двум осям:
/*http://esate.ru, Anvi*/
Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0);
После этого мы выполняем рисование сферы радиусом 2 в виде сетки в той области, куда мы переместились. Сфера будет разбита на 32 меридиана и 32 параллели.
Для визуализации используется библиотека FreeGlut. В одном из уроков, посвященных трехмерной визуализации, мы реализуем с вами собственный алгоритм визуализации сферы.
/*http://esate.ru, Anvi*/
// рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32);
Возвращаем сохраненную в стэке матрицу:
/*http://esate.ru, Anvi*/
Gl.glPopMatrix();
Дожидаемся, пока библиотека OpenGL завершит визуализацию этого кадра:
/*http://esate.ru, Anvi*/
Gl.glFlush();
И посылаем нашему элементу AnT, в котором происходит визуализация нашей сцены, сигнал о том, что необходимо обновить отображаемый кадр, т.е. другими словами вызываем его перерисовку.
/*http://esate.ru, Anvi*/
AnT.Invalidate();
Вот и всё. Мы подробно рассмотрели весь код программы, созданной в последней части предыдущей главы. Это объяснило многие вещи в вопросе инициализации OpenGL и визуализации трехмерной графики. Однако многие темы все еще остаются неосвещенными.
Начиная со следующей части этой главы, мы будем закреплять навыки программирования и визуализации двухмерной графики на примере разработки большого количества программ.