Опубликован: 13.07.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 20:

Геометрические примитивы в OpenGL

Установка отсекающего объема (куба) для рисования сцены

Прежде всего мы должны установить отсекающий объем, который удобно считать как канву, в границах которой мы будем строить свою сцену. Отсекающий объем мы сделаем точно таким же, как в предыдущей работе. Устанавливая ортогональный отсекающий объем, мы как-бы устанавливаем углубленную сцены с неподвижной правой системой координат, оси x и y направлены по ширине и высоте сцены соответственно, ось z направлена к зрителю, а начало координат определяется заданными размерами при создании сцены. Далее относительно этих координат мы будем рисовать объекты, но перед тем, как их визуальное представление дойдет до зрителя, оно может быть последовательно преобразовано различными матрицами поворотов и сдвига. Как в кривом зеркале: отображение искажается, а сам объект нет.

  • Скопируйте из файла GLRect.cpp работы " Лекция 19. Простая программа на OpenGL " код функции ChangeSize(), чтобы новое содержимое файла выглядело так
#include "stdafx.h"
  
// Прототипы функций
void RenderScene(void);
void SetupRC(void);
void ExecuteMenu(int);
void ChangeSize(int, int);
  
// Точка входа приложения
void main(void)
{
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
  glutCreateWindow("Primitives");
  glutDisplayFunc(RenderScene);
  glutReshapeFunc(ChangeSize);
  SetupRC();

  // Создание меню и добавление опций выбора
  glutCreateMenu(ExecuteMenu);
  glutAddMenuEntry("Опция 1", 1);
  glutAddMenuEntry("Опция 2", 2);
  glutAddMenuEntry("Опция 3", 3);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
  
  glutMainLoop();
}
  
//****************************************************
// Функция обратного вызова обработки выбора пользователя
void ExecuteMenu(int choice)
{
  switch(choice){
    case 1:
      // Вариант выбора 1
      break;
    case 2: 
      // Вариант выбора 2
      break;
    case 3:
      // Вариант выбора 3
      break;
    default:
      ;
  }
    
  // Вызвать принудительно визуализацию
  glutPostRedisplay();
}
  
//****************************************************
// Функция обратного вызова для рисования сцены
void RenderScene(void)
{
  // Окно очищается текущим цветом,
  // установленным функцией glClearColor()
  glClear(GL_COLOR_BUFFER_BIT);
  // В буфер вводятся команды рисования
  glFlush();
}
  
//****************************************************
// Устанавливается состояние инициализации
void SetupRC(void)
{
  glClearColor(0.0F, 0.0F, 1.0F, 1.0F);// RGB + alpha-канал
}
  
//**********************************************************
// Вызывается библиотекой GLUT при изменении размеров окна
void ChangeSize(int width, int height)
{
  GLfloat aspectRatio;
  // Замена числовых границ объема на константную переменную
  const GLfloat nRange = 100;
  
  // Предотвращаем деление на нуль
  if(height == 0)
    height = 1;
  
  // Устанавливаем поле просмотра с размерами окна
    glViewport(0, 0, width, height);
  
  // Устанавливает матрицу преобразования в режим проецирования
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  // Устанавливаем размеры перспективы (отсекающего объема)
  // (left, right, bottom, top, near, far)
  aspectRatio = (GLfloat)width / (GLfloat)height;
    if (width <= height)
    glOrtho (-nRange, nRange, -nRange / aspectRatio,
        nRange / aspectRatio, 
        -nRange, nRange);
    else 
    glOrtho (-nRange * aspectRatio, 
        nRange * aspectRatio, 
        -nRange, nRange, 
        -nRange, nRange);
  
  // Восстановливает матрицу преобразования в исходный режим вида
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();  // Загружаем единичную матрицу преобразований
}
Листинг 21.4. Новое содержимое файла Primitive.cpp после очевидных изменений

Для дальнейшей работы при визуализации (при рендеринге в функции RenderScene ) мы будем использовать новые возможности OpenGL, такие как

  • glRotate()
  • glPushMatrix()
  • glPopMatrix()

без особых объяснений, чтобы не отвлекаться от основного материала. Скажем лишь, что данные функции позволяют рисовать в трехмерном пространстве и помогают легко визуализировать внешний вид рисунков под различными углами.

Упражнение 1. Рисование точек (Points)

Основным примитивом, используемым при рисовании, является точка. Для задания координат точки используется функция glVertex3f ( glVertex2f - для точки на плоскости с координатой z = 0, glVertex4f - трехмерная точка + масштаб). Для того, чтобы рисование выполнялось быстро как единый пакет, в OpenGL принят такой механизм: вначале точки расставляются командой glVertax3f, а затем подается сигнал пропустить их все разом через "жернова" преобразующих матриц и представить зрителю на экране в поле просмотра, сформировав кадр сцены. Механизм пакетной обработки устанавливается внутри блочных функций glBegin()...glEnd().

Давайте добавим к проекту функцию, которая будет рисовать спираль, состоящую из точек. Назовем ее Points() и назначим ее выполнение при выборе пользователем первого пункта контекстного меню. Для видимости переменной, устанавливающей выбор пользователя, введем глобальную переменную choice. Это плохая практика в стиле языка C, но данная версия OpenGL не поддерживает классы, позволяющие инкапсулировать данные. А другого выхода из соображений сделать программу проще, не отвлекаясь на языковые моменты, у нас пока нет.

В функции SetupRC() установим цвет фона черный с непрозрачным альфа-каналом, а цвет рисования точек выберем зеленым (как в космических приборах!). В функции рендеринга RenderScene() введем варианты вызова функций рисования в зависимости от выбора пользователя. Внутри функции Poins() применим описанный ранее механизм рисования. Таким образом, реализующий сказанное код в составе всей программы должен выглядеть так

#include "stdafx.h" // Искать в текущем каталоге проекта
  
// Прототипы функций
void RenderScene(void);
void SetupRC(void);
void ExecuteMenu(int);
void ChangeSize(int, int);
void Points(); // Пустой аргумент необязателен
void SpecialKeys(int key, int x, int y);// Можно указать только типы
  
// Глобальная переменная выбранного варианта
int choice = 0;
  
// Точка входа приложения
void main(void)
{
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
  glutCreateWindow("Primitives");  // Заголовок окна
  glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
  glutReshapeFunc(ChangeSize);  // При изменении размера окна
  SetupRC();
  
  // Создание меню и добавление опций выбора
  glutCreateMenu(ExecuteMenu);
  glutAddMenuEntry("Рисование функцией Points", 1);
  glutAddMenuEntry("Опция 2", 2);
  glutAddMenuEntry("Опция 3", 3);
  glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
  // Конец создания меню
  
  glutSpecialFunc(SpecialKeys); // Для управления с клавиатуры
  
  glutMainLoop();  // Цикл сообщений графического окна
}
  
//****************************************************
// Глобальные переменные для создания вращения
GLfloat xRot = 0.0f;
GLfloat yRot = 0.0f;
//****************************************************
// Функция обратного вызова обработки выбора пользователя
void ExecuteMenu(int choice)
{
  // Сбрасываем углы вращения прежнего варианта
  xRot = yRot = 0;
  
  // Передаем выбор в глобальную переменную
  ::choice = choice; 
    
  // Вызвать принудительно визуализацию
  glutPostRedisplay();
}
  
//****************************************************
// Функция обратного вызова для рисования сцены
void RenderScene(void)
{
  // Окно очищается текущим цветом,
  // установленным функцией glClearColor()
  glClear(GL_COLOR_BUFFER_BIT);

  // Отработка выбора пользователя
  // Пока один вариант !
  switch(::choice){
    case 1:
      Points();
  }
  
  // В буфер вводятся команды рисования
  glFlush();
}
  
//****************************************************
// Устанавливается состояние инициализации
void SetupRC(void)
{
  glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
  glColor3f(0.0f, 1.0f, 0.0f); // Цвет рисования зеленый
}
  
//**********************************************************
// Вызывается библиотекой GLUT при изменении размеров окна
void ChangeSize(int width, int height)
{  
  // Предотвращаем деление на нуль
  if(height == 0)
    height = 1;
  
  // Устанавливаем поле просмотра с размерами окна
    glViewport(0, 0, width, height);
  
  // Устанавливает матрицу преобразования в режим проецирования
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  // Устанавливаем размеры перспективы (отсекающего объема)
  // (left, right, bottom, top, near, far)
  GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;// Для коррекции
  const GLfloat nRange = 100; // Размеры отсекающих плоскостей
  
  // Первоначально задали куб, теперь его корректируем
  // в зависимости от искажения графического окна
  if (width <= height)
    glOrtho (-nRange, nRange, -nRange / aspectRatio,
          nRange / aspectRatio, 
          -nRange, nRange);
  else 
    glOrtho (-nRange * aspectRatio, 
          nRange * aspectRatio, 
          -nRange, nRange, 
          -nRange, nRange);
  
  // Восстановливает матрицу преобразования в исходный режим вида
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}
  
//**********************************************************
// Подключаем по мере надобности
#define GL_PI 3.1415f // Число Пи
#include <math.h> // Для функций sin() и cos()
//**********************************************************
// Функция рисования пружины точками
// Все углы в радианах
void Points() // Пустой аргумент необязателен
{
  GLfloat x,y,z,angle; // Переменные для координат и угла
  
  // Запомнить первоначальное состояние матрицы вращения
  glPushMatrix();
  
  // Выполнить два последовательных поворота
  // для будущей визуализации сцены
  glRotatef(xRot, 1.0f, 0.0f, 0.0f);// Новое состояние матрицы вращения
  glRotatef(yRot, 0.0f, 1.0f, 0.0f);// Следующее новое состояние
  
  // Сначала расставить все точки (произвести монтаж сцены),
  // потом послать сцену пакетом на визуализацию
  glBegin(GL_POINTS);
  
  // Начинаем расставлять точки
  z = -50.0f;
  
  // Задаем три оборота в радианах
  for(angle = 0.0f; angle <= 2.0f * GL_PI * 3.0f; angle += 0.1f){
    // Вычисляем очередную точку на окружности
    x = 50.0f * sin(angle);
    y = 50.0f * cos(angle);
  
    // Определяем точку со сдвигом по оси z  
    glVertex3f(x, y, z);
  
    z += 0.5f; // Приближаем к зрителю
  }
  
  glEnd(); // Закончили расставление, посылаем пакет на визуализацию
  
  // Восстанавливаем матрицу вращения в исходное состояние
  glPopMatrix();
}
  
//**********************************************************
// Управление с клавиатуры стрелками
// для задания новых значений матрицы поворота
void SpecialKeys(int key, int x, int y)
{
  if(key == GLUT_KEY_UP)  // Стрелка вверх
    xRot -= 5.0f;
  
  if(key == GLUT_KEY_DOWN)// Стрелка вниз
    xRot += 5.0f;
  
  if(key == GLUT_KEY_LEFT)// Стрелка влево
    yRot -= 5.0f;
  
  if(key == GLUT_KEY_RIGHT)// Стрелка вправо
    yRot += 5.0f;
  
  // Вызвать принудительно визуализацию с помощью RenderScene()
  glutPostRedisplay();
}
Листинг 21.5. Полный листинг программы для данного этапа рисования точек
  • Запустите приложение с приведенным кодом, через контекстное меню включите первую опцию и поуправляйте рендерингом кнопками-стрелками

Обратите внимание, что на начальном этапе мы формируем пружину в плоскости x0y, которая по стенкам воображаемого цилиндра приближается к зрителю. Для того, чтобы ее увидеть, мы изменяем матрицу поворота клавишами-стрелками. Для этого мы используем функцию обратного вызова библиотеки GLUT.

Код приведен с подробными комментариями. Поразбирайтесь и поэкспериментируйте с ним. Мой результат такой


Прежде, чем двигаться дальше, давайте поступим несколько нетрадиционно - так, как не следует поступать. А сделаем вот что. Поскольку конец нашего кода мы изменять дальше не будем, уберем его в отдельный заголовочный файл, а на освободившееся место включим этот файл. Препроцессор перед компиляцией вставит этот хвост в нужном месте в общий листинг программы, а компилятор ничего не заметит.

  • Откройте панель Solution Explorer и щелкните правой кнопкой мыши на узле проекта
  • Выполните команду контекстного меню Add/New Item и в появившемся диалоговом окне задайте следующие настройки добавления к проекту нового файла

  • Перенесите из файла Primitives.cpp весь код, начиная с функции SetupRC() и до конца, а на оставшееся место поместите инструкцию включения. После этого действия содержимое файла Primitives.cpp должно выглядеть так
#include "stdafx.h" // Искать в текущем каталоге проекта
  
// Прототипы функций
void RenderScene(void);
void SetupRC(void);
void ExecuteMenu(int);
void ChangeSize(int, int);
void Points(); // Пустой аргумент необязателен
void SpecialKeys(int key, int x, int y);// Можно указать только типы
  
// Глобальная переменная выбранного варианта
int choice = 0;
  
// Точка входа приложения
void main(void)
{
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
  glutCreateWindow("Primitives");  // Заголовок окна
  glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
  glutReshapeFunc(ChangeSize);  // При изменении размера окна
  SetupRC();
  
  // Создание меню и добавление опций выбора
  glutCreateMenu(ExecuteMenu);
  glutAddMenuEntry("Рисование функцией Points", 1);
  glutAddMenuEntry("Опция 2", 2);
  glutAddMenuEntry("Опция 3", 3);
  glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
  // Конец создания меню
  
  glutSpecialFunc(SpecialKeys); // Для управления с клавиатуры
  
  glutMainLoop();  // Цикл сообщений графического окна
}
  
//****************************************************
// Глобальные переменные для создания вращения
GLfloat xRot = 0.0f;
GLfloat yRot = 0.0f;
//****************************************************
// Функция обратного вызова обработки выбора пользователя
void ExecuteMenu(int choice)
{
  // Сбрасываем углы вращения прежнего варианта
  xRot = yRot = 0;
  
  // Передаем выбор в глобальную переменную
  ::choice = choice; 
    
  // Вызвать принудительно визуализацию
  glutPostRedisplay();
}
  
//****************************************************
// Функция обратного вызова для рисования сцены
void RenderScene(void)
{
  // Окно очищается текущим цветом,
  // установленным функцией glClearColor()
  glClear(GL_COLOR_BUFFER_BIT);

  // Отработка выбора пользователя
  // Пока один вариант !
  switch(::choice){
    case 1:
      Points();
  }
  
  // В буфер вводятся команды рисования
  glFlush();
}
  
#include "Points.h" // Включение перенесенного кода
Листинг 21.6. Остаток кода в файле Primitives.cpp после переноса "хвоста"
  • Постройте приложение и ничего не должно измениться, зато сколько свободного места стало в файле

Мы и дальше в этой работе будем так поступать: создадим очередной хвост кода в файле Primitives.cpp, а потом перекинем в другой файл, чтобы не мешал разрабатывать новые варианты.

Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000