Практикум прикладного программирования на C# в среде VS.NET 2005

Непрограммируемый конвейер в OpenGL

Показывать лекцию целиком

Файлы к лабораторной работе Вы можете скачать здесь.

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

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

Создание заготовки приложения OpenGL

В данной лабораторной работе мы познакомимся с процессом преобразования графической информации, который реализован в библиотеке OpenGL как непрограммируемый конвейер.

  • Выполните команду меню оболочки File/New/Project.
  • В появившемся окне мастера создания заготовки проекта задайте имя проекта ProjectionMatrix и выберите каталог размещения проекта, как показано на снимке
  • В следующем окне оставьте установки мастера по умолчанию и щелкните на кнопке Finish
  • Переименуйте функцию _tmain() файла ProjectionMatrix.cpp в main(), измените в заголовке определения функции main() тип возвращаемого значения и удалите аргументы, чтобы код выглядел так
  • #include "stdafx.h"
      
    void main()
    {
    }
  • Через панель Solution Explorer оболочки откройте файл stdafx.h, который создал мастер проекта, и добавьте в самое его начало после инструкции #pragma once код подключения стандартных заголовочных файлов для операционной системы и OpenGL
  • // stdafx.h : include file for standard system include files,
    // or project specific include files that are used frequently, but
    // are changed infrequently
    //
      
    #pragma once
      
    #include <windows.h>
    #include <gl/gl.h>
    #include <gl/glu.h>
    #include <gl/glut.h>  
        
    #define WIN32_LEAN_AND_MEAN    // Exclude rarely-used stuff from Windows headers
    #include <iostream>
    #include <tchar.h>
        
    // TODO: reference additional headers your program requires here
    Здесь предполагается, что ваша платформа настроена на разработку приложений OpenGL в соответствии с рекомендациями, которые нужно было выполнить в работе №19.

    В этой лабораторной работе мы опять выполним несколько упражнений, которые упакуем в одно приложение. Соответственно, для вызова каждого отдельного упражнения предусмотрим свой пункт контекстного меню. Для взаимодействия с пользователем и рисования простых геометрических объектов применим инструментальные средства библиотеки GLUT. Код приложения будем постепенно наращивать по мере выполнения упражнений рассматриваемой темы.

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

    При моделировании взгляда наблюдателя обычно его отождествляют с фотокамерой и наделяют теми же характеристиками: направление оптической оси на сцену, угол поворота относительно оптической оси и ракурс видимости сцены. В исходном состоянии OpenGL камера находится в начале мировых координат, смотрит в отрицательную сторону оси z, направляющий вектор камеры (нормаль) совпадает с осью y (камера стоит на плоскости x0z ).

    Управление объектами на сцене и камерой осуществляется с помощью последовательных преобразований, реализуемых матрицами 4x4. Конвейер преобразований в OpenGL состоит из трех частей, которые настраиваются по отдельности. Выбор требуемого режима осуществляется командой glMatrixMode(GLenum mode), которая с помощью значения перечисления mode определяет, какую из матриц преобразования размером 4x4 сделать текущей и вносить в нее изменения последующими командами:

  • mode = GL_MODELVIEW: Модельные преобразования (модельно-видовая матрица) - применяются к размещению объектов на сцене.
  • mode = GL_PROJECTION: Видовые преобразования (проекционная матрица) - применяются к размещению и ориентации точки обзора (настройка фотокамеры).
  • mode = GL_TEXTURE: Текстурные преобразования (текстурная матрица) - применяются для управления текстурами заполнения объектов. В данной лабораторной работе мы не будем применять текстуры, поэтому и останавливаться здесь на них не будем.
  • В начальном состоянии по умолчанию любая из этих матриц преобразования размером 4x4 является единичной.

    Команды управления текущими матрицами преобразования

  • В любой момент текущей матрице преобразования можно придать значение единичной матрицы с помощью команды glLoadIdentity().
  • В текущую матрицу преобразования можно загрузить желаемое значение, определяемое массивом m из 16 элементов, с помощью команды glLoadMatrix{fd}(const TYPE *m).
  • Команда glLoadTransposeMatrix{fd}(const TYPE *m) также загружает в текущую матрицу преобразования желаемое значение, определяемое массивом m из 16 элементов, только предварительно транспонирует этот массив. Окончания f или d - для команд с аргументами типа float или double.
  • Текущую матрицу преобразования можно умножить справа на произвольную матрицу, определяемую массивом из 16 элементов, с помощью команды glMultMatrix{fd}(const TYPE *m). Результат умножения помещается в текущую матрицу преобразования. Окончания f или d - для команд с аргументами типа float или double.
  • Команда glMultTratsposeMatrix{fd}(const TYPE *m) также умножает текущую матрицу преобразования справа на произвольную матрицу, определяемую массивом из 16 элементов, только предварительно транспонирует этот массив. Результат умножения помещается в текущую матрицу преобразования. Окончания f или d - для команд с аргументами типа float или double.
  • Любой текущей матрицы преобразования можно передать значение с помощью указанных функций. Но весь вопрос в том, как правильно определить эти значения. При решении геометрических задач человеку всегда удобнее мыслить категориями элементарных последовательных преобразований (элементарных переходов), таких как плоскопараллельные смещения, повороты и изменения масштаба.

    Модельные преобразования

    В мировой системе координат мы строим свою сцену из отдельных объектов, часть из которых должны быть подвижны и называются актерами. Как правило, вначале объект создается вершинами примитивов относительно начала мировых координат, а затем настраивается матрица преобразования модели для помещения объекта в требуемое место сцены. Затем относительно мировой системы координат мы определяем взгляд наблюдателя на построенную сцену с помощью камеры. Размещение трехмерных объектов на сцене, а также управление их дальнейшим положением и ориентацией осуществляется с помощью матриц преобразований модели, которые реализуют следующие три действия:

  • Перемещение - плоско-параллельный перенос всего объекта в некоторую точку мирового пространства. Реализуется командой glTranslate{fd}(TYPE x, TYPE y, TYPE z). Окончания f или d - для команд с аргументами типа float или double. В аналитических выкладках принято обозначать как матрицу T.
  • Вращение - поворот объекта вокруг некоторой линии, определенной в мировых координатах, на заданный угол. Реализуется командой glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z). Окончания f или d - для команд с аргументами типа float или double. В аналитических выкладках принято обозначать как матрицу R.
  • Масштабирование - растяжение, сжатие или отражение объекта по соответствующим осям. Реализуется командой glScale{fd}(TYPE x, TYPE y, TYPE z). Окончания f или d - для команд с аргументами типа float или double. В аналитических выкладках принято обозначать как матрицу S. Масштабирование со значением больше +1.0 растягивает объект по соответствующей оси, со значением меньше +1.0 - уменьшает объект. Значение -1.0 приводит к зеркальному отображению объекта.
  • Все модельные преобразования применяются к объектам, заданным координатами вершин, последовательным домножением текущей матрицы справа на новую матрицу, определяющую нужное геометрическое преобразование. Последовательность применение тех или иных действий имеет значение. Например, при прочих равных условиях разные результаты дадут действия, выполненные в последовательности: переместить/повернуть и повернуть/переместить. Это и понятно, поскольку совершенно разными будут итоговые матрицы для таких действий:

    M = M * T * R;   M = M * R * T

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

    Проекционные преобразования

    В результате последовательности действий с текущей матрицей проецирования мы определяем отсекающий объем видимости, который настраивает OpenGL в следующих аспектах:

  • Как сцена будет отображаться на экране монитора - посредством ортогонального или перспективного проецирования
  • Какие объекты или части объектов войдут в окончательное изображение, а какие будут отсечены как не попавшие в объем видимости.
  • Для формирования матрицы проецирования нужно сделать ее текущей, выполнив команду glMatrixMode(GL_PROJECTION).

    Ортогональный отсекающий объем видимости устанавливается командой

    glOrtho(left, right, bottom, top);

    с аргументами типа GLdouble.

    Для плоских сцен, где отсекающий объем превращается в отсекающую рамку видимости с координатой z=0, можно применить упрощенный вариант команды из библиотеки GLU

    gluOrtho2D(left, right, bottom, top, near, far);

    с аргументами типа GLdouble.

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

    glFrustum(left, right, bottom, top, near, far);

    с аргументами типа GLdouble. Функция glFrustum() не требует, чтобы отсекающий объем был симметричным. Значения параметров near и far должны быть положительными.

    Перспективный отсекающий объем можно задать и другой командой gluPerspective() из библиотеки GLU, которая делает то же самое, что и glFrustum(), но несколько иным способом.

    gluPerspective(fovy, aspectratio, near, far);

    с аргументами типа GLdouble   fovy означает угол ракурса; aspectratio - характеристическое число, равное отношению ширины к высоте основания усеченной пирамиды.

    Изначально созданные ортогональный или перспективный отсекающие объемы видимости определяют наблюдателя, находящегося в начале мировой системы координат и смотрящего вдоль отрицательного направления оси 0z. Чтобы поставить наблюдателя перед сценой или определить иной угол зрения, можно поступить двумя способами:

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

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

    gluLookAt(x1, y1, z1, x2, y2, z2, x3, y3, z3);

    имеющую девять аргументов типа GLdouble. Нужная точка обзора (положение глаза) описывается первой тройкой аргументов. Вторая тройка аргументов определяет любую точку на линии взгляда, но обычно координаты центра сцены. Третья тройка определяет вектор вверх ориентации (нормаль) видимого объема камеры.

    По умолчанию камера располагается в начале системы мировых координат, смотрит вниз по оси z, а положительное направление оси y указывает вектор ориентации (нормаль). Это аналогично вызову функции

    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);

    Значение z для линии взгляда равно -100.0, но может быть любым отрицательным значением z, так как линия взгляда останется такой же. Подобный вызов не имеет смысла, поскольку эти значения приняты по умолчанию.

    Стеки матриц

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

    Глубина стека в реализации Microsoft равна:

  • mode = GL_MODELVIEW: Модельные преобразования (модельно-видовая матрица) - 32 матрицы (стек изначально не пустой и хранит единичную матрицу 4x4 )
  • mode = GL_PROJECTION: Видовые преобразования (проекционная матрица) - 2 матрицы (для хранения одной ортогональной и одной перспективной матриц)
  • mode = GL_TEXTURE: Текстурные преобразования (текстурная матрица) - 2 матрицы
  • Глубину соответствующего стека, поддерживаемого платформой, можно узнать командами:

  • glGet(GL_MAX_MODELVIEW_STACK_DEPTH);
  • glGet(GL_MAX_PROJECTION_STACK_DEPTH);
  • glGet(GL_MAX_TEXTURE_STACK_DEPTH);
  • Управление стеком выполняется командами:

  • glPushMatrix(); - запомнить промежуточное значение текущей матрицы преобразования
  • glPopMatrix(); - восстановить значение текущей матрицы преобразования
  • Если глубина стека превышена, то система генерирует сообщение об ошибке GL_STACK_OVERFLOW, а при попытке извлечь матрицу из пустого стека генерируется ошибка GL_STACK_UNDERFLOW.

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

    Упражнение 1. Построение модели атома в ортогональной проекции (Atom)

    Используем рассмотренные преобразования для решения простейшей задачи - формирования модели атома. Разместим в файле ProjectionMatrix.cpp общую часть программы, а код каждого упражнения будем размещать в отдельном файле.

  • Заполните файл ProjectionMatrix.cpp следующим кодом
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"  // Упражнение: "1) Простая модель атома"
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
      }
        
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    // для первого упражнения Atom
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          RenderSceneAtom();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    // для первого упражнения Atom
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      glutTimerFunc(100, TimerFunc, 1); // Заряжаем новый таймер
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками
    // для задания новых значений матрицы поворота
    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;
      
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Добавьте к проекту новый заголовочный файл (Header File) с именем Atom.h и заполните его следующим кодом
  • //**********************************************************
    // Прототипы
    void Atom();        
    void RenderSceneAtom(void);
    void ChangeSizeAtom(int, int);  // При изменении размеров окна
    void ShowOrbit(GLfloat radius);
      
    //**********************************************************
    // Упражнение 1: "1) Простая модель атома"
    void Atom() 
    {
      // Угол поворота вокруг ядра
      static GLfloat fElect = 0.0f;
      
      GLfloat radius;
      
      // Сбрасываем буфер цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Сбрасываем матрицу наблюдения модели в значение единичной матрицы
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      
      // Отодвигаем всю сцену назад по оси z
      // чтобы видеть ее
      glTranslatef(0.0f, 0.0f, -100.0f);  
      
      // Управление видом
      glRotatef(xRot, 1.0f, 0.0f, 0.0f);
      glRotatef(yRot, 0.0f, 1.0f, 0.0f);
      
      // Красное ядро прямо по центру линии наблюдения
      glColor3ub(255, 0, 0);
      glutSolidSphere(10.0f, 15, 15);
      
      // Задаем желтый цвет для всех электронов
      glColor3ub(255,255,0);
      
      // ПЕРВЫЙ ЭЛЕКТРОН
      radius = 90.0f;
      // Сохраняем исходное преобразование наблюдения в стеке матриц
      glPushMatrix();
      ShowOrbit(radius);  // Рисуем орбиту 
      // Формируем точку центра первого электрона
      // поворотом на угол относительно оси 0y
      glRotatef(fElect, 0.0f, 1.0f, 0.0f);
      // Сдвинули точку по оси 0x на radius единиц
      glTranslatef(radius, 0.0f, 0.0f);
      // Рисуем электрон
      glutSolidSphere(6.0f, 15, 15);
      // Восстанавливаем исходную матрицу преобразования наблюдения
      glPopMatrix();
      
      // ВТОРОЙ ЭЛЕКТРОН
      radius = 70.0f;
      glPushMatrix();
      glRotatef(120.0f, 0.0f, 0.0f, 1.0f);
      ShowOrbit(radius);  // Рисуем орбиту 
      glRotatef(fElect + 90.0f, 0.0f, 1.0f, 0.0f);
      glTranslatef(-radius, 0.0f, 0.0f);
      glutSolidSphere(6.0f, 15, 15);
      glPopMatrix();
      
      // ТРЕТИЙ ЭЛЕКТРОН
      radius = 60.0f;
      glPushMatrix();
      glRotatef(-120.0f,0.0f, 0.0f, 1.0f);
      ShowOrbit(radius);  // Рисуем орбиту 
      glRotatef(fElect - 90.0f, 0.0f, 1.0f, 0.0f);
      glTranslatef(0.0f, 0.0f, radius);
      glutSolidSphere(6.0f, 15, 15);
      glPopMatrix();
      
      // Увеличиваем угол поворота
      fElect += 10.0f;
      fElect = fElect > 360.0f ? 0.0f : fElect;
      
      // Переключаем буферы рисования
      glutSwapBuffers();
    }
      
    //**********************************************************
    #define GL_PI 3.1415f
    #include <math.h> // Для тригонометрических функций
      
    void ShowOrbit(GLfloat radius)
    {
      // Переменные орбиты
      GLfloat x, y = 0.0f, z, angle;
      
      glBegin(GL_LINE_LOOP);
      for(angle = 0.0f; angle < 2.0f * GL_PI; angle += 0.1f)
      {
        x = radius * sin(angle);
        z = radius * cos(angle);
        glVertex3f(x, y, z);
      }
      glEnd();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    // для первого упражнения Atom
    void RenderSceneAtom(void)
    {
      // Сбрасываем буфер цвета и буфер глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Рисуем сцену
      Atom();
      
      // Прокачка сообщений
      glFlush();
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    // для первого упражнения Atom
    void ChangeSizeAtom(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 * 2.0f,
              nRange * 2.0f);
      else 
        glOrtho (-nRange * aspectRatio, 
              nRange * aspectRatio, 
              nRange, 
              -nRange, 
              -nRange * 2.0f,
              nRange * 2.0f);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
  • Разберитесь с кодом, запустите упражнение, поуправляйте изображением с помощью клавиш-стрелок
  • На динамической модели атома снимки экрана могут быть такими

    Упражнение 2. Модель полого бруска в ортогональной проекции (Ortho)

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

  • Через панель Solution Explorer вызовите для корневого узла проекта контекстное меню и командой Add/New Item добавьте новый заголовочный файл с именем Ortho.h
  • Дополните файл ProjectionMatrix.cpp следующими изменениями для подключения второго упражнения (выделены синим цветом)
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"  // Упражнение: "1) Простая модель атома"
    #include "Ortho.h"  // Упражнение 2: "2) Брусок в ортогональной проекции"  
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
        case 2:
          ChangeSizeOrtho(w, h);
          break;
      }
      
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          SetupLight(false);
          RenderSceneAtom();
          break;
        case 2:
          SetupLight(true);
          RenderSceneOrtho();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
        case 2:
          ChangeSizeOrtho(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      glutTimerFunc(100, TimerFunc, 1); // Заряжаем новый таймер
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками
    // для задания новых значений матрицы поворота
    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;
      
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      glutAddMenuEntry("2) Брусок в ортогональной проекции", 2);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Заполните файл Ortho.h следующим кодом, реализующим второе упражнение
  • //**********************************************************
    // Прототипы
    void Ortho();        
    void RenderSceneOrtho(void);
    void ChangeSizeOrtho(int, int);  // При изменении размеров окна
    void SetupLight(bool);        // Устанавливаем источник света сцены
      
    //**********************************************************
    // Упражнение 2: "2) Брусок в ортогональной проекции"
    void Ortho() 
    {
      float fZ,bZ;
      
      fZ = 100.0f;
      bZ = -100.0f;
      
      // Сбрасываем матрицу преобразования модели в значение единичной матрицы
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      
      // Поворачиваем сцену на угол, определяемый клавишами-стрелками
      glRotatef(xRot, 1.0f, 0.0f, 0.0f);
      glRotatef(yRot, 0.0f, 1.0f, 0.0f);
      
      // Устанавливаем цвет рисования красный
      glColor3f(1.0f, 0.0f, 0.0f);
      
      glBegin(GL_QUADS); // Рисуем наружнюю сторону
        // Нормаль по оси Z
        glNormal3f(0.0f, 0.0f, 1.0f);  
      
        // Левая грань
        glVertex3f(-50.0f, 50.0f, fZ);
        glVertex3f(-50.0f, -50.0f, fZ);
        glVertex3f(-35.0f, -50.0f, fZ);
        glVertex3f(-35.0f,50.0f,fZ);
      
        // Правая грань
        glVertex3f(50.0f, 50.0f, fZ);
        glVertex3f(35.0f, 50.0f, fZ);
        glVertex3f(35.0f, -50.0f, fZ);
        glVertex3f(50.0f,-50.0f,fZ);
      
        // Верхняя грань
        glVertex3f(-35.0f, 50.0f, fZ);
        glVertex3f(-35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 50.0f,fZ);
      
        // Нижняя грань
        glVertex3f(-35.0f, -35.0f, fZ);
        glVertex3f(-35.0f, -50.0f, fZ);
        glVertex3f(35.0f, -50.0f, fZ);
        glVertex3f(35.0f, -35.0f,fZ);
      
        // Левая верхняя секция
        // Нормаль по оси Y
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-50.0f, 50.0f, fZ);
        glVertex3f(50.0f, 50.0f, fZ);
        glVertex3f(50.0f, 50.0f, bZ);
        glVertex3f(-50.0f,50.0f,bZ);
      
        // Верхняя секция
        glNormal3f(0.0f, -1.0f, 0.0f);
        glVertex3f(-50.0f, -50.0f, fZ);
        glVertex3f(-50.0f, -50.0f, bZ);
        glVertex3f(50.0f, -50.0f, bZ);
        glVertex3f(50.0f, -50.0f, fZ);
      
        // Левая секция
        glNormal3f(1.0f, 0.0f, 0.0f);
        glVertex3f(50.0f, 50.0f, fZ);
        glVertex3f(50.0f, -50.0f, fZ);
        glVertex3f(50.0f, -50.0f, bZ);
        glVertex3f(50.0f, 50.0f, bZ);
      
        // Правая секция
        glNormal3f(-1.0f, 0.0f, 0.0f);
        glVertex3f(-50.0f, 50.0f, fZ);
        glVertex3f(-50.0f, 50.0f, bZ);
        glVertex3f(-50.0f, -50.0f, bZ);
        glVertex3f(-50.0f, -50.0f, fZ);
      glEnd();
      
      glFrontFace(GL_CW);    // Лицевыми будем считать те грани, вершины
                  // которых обходятся по часовой стрелки
                  // для рисования задней стенки
      
      glBegin(GL_QUADS);    // Рисуем внутреннюю сторону
        // Нормаль по оси Z
        glNormal3f(0.0f, 0.0f, -1.0f);  
      
        // Левая грань
        glVertex3f(-50.0f, 50.0f, bZ);
        glVertex3f(-50.0f, -50.0f, bZ);
        glVertex3f(-35.0f, -50.0f, bZ);
        glVertex3f(-35.0f,50.0f,bZ);
      
        // Правая грань
        glVertex3f(50.0f, 50.0f, bZ);
        glVertex3f(35.0f, 50.0f, bZ);
        glVertex3f(35.0f, -50.0f, bZ);
        glVertex3f(50.0f,-50.0f,bZ);
      
        // Верхняя грань
        glVertex3f(-35.0f, 50.0f, bZ);
        glVertex3f(-35.0f, 35.0f, bZ);
        glVertex3f(35.0f, 35.0f, bZ);
        glVertex3f(35.0f, 50.0f,bZ);
      
        // Нижняя грань
        glVertex3f(-35.0f, -35.0f, bZ);
        glVertex3f(-35.0f, -50.0f, bZ);
        glVertex3f(35.0f, -50.0f, bZ);
        glVertex3f(35.0f, -35.0f,bZ);
      
        // Внутренняя часть
        glColor3f(0.75f, 0.75f, 0.75f); // Серый
      
        // Нормаль по оси Y
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 35.0f, bZ);
        glVertex3f(-35.0f,35.0f,bZ);
      
        // Нижняя секция
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-35.0f, -35.0f, fZ);
        glVertex3f(-35.0f, -35.0f, bZ);
        glVertex3f(35.0f, -35.0f, bZ);
        glVertex3f(35.0f, -35.0f, fZ);
      
        // Левая секция
        glNormal3f(1.0f, 0.0f, 0.0f);
        glVertex3f(-35.0f, 35.0f, fZ);
        glVertex3f(-35.0f, 35.0f, bZ);
        glVertex3f(-35.0f, -35.0f, bZ);
        glVertex3f(-35.0f, -35.0f, fZ);
      
        // Правая секция
        glNormal3f(-1.0f, 0.0f, 0.0f);
        glVertex3f(35.0f, 35.0f, fZ);
        glVertex3f(35.0f, -35.0f, fZ);
        glVertex3f(35.0f, -35.0f, bZ);
        glVertex3f(35.0f, 35.0f, bZ);
      glEnd();
      
      // Возвращаем обход сторон к значению по умолчанию
      glFrontFace(GL_CCW);    
      
      // Переключаем буфер кадра
      glutSwapBuffers();
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    // для второго упражнения Ortho
    void ChangeSizeOrtho(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 = 120; // Размеры отсекающих плоскостей
      
      // Первоначально задали куб, теперь его корректируем
      // в зависимости от искажения графического окна
      if (width <= height)
        glOrtho (-nRange,
              nRange, 
              -nRange / aspectRatio,
              nRange / aspectRatio, 
              -nRange * 2.0f,
              nRange * 2.0f);
      else 
        glOrtho (-nRange * aspectRatio, 
              nRange * aspectRatio, 
              -nRange, 
              nRange, 
              -nRange * 2.0f,
              nRange * 2.0f);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
      
    //**********************************************************
    // Устанавливаем источник света сцены
    void SetupLight(bool flag)
    {
      if(!flag)
      {
        glDisable(GL_LIGHTING);
        return;
      }
      
      // Определяем характеристики источника света для сцены
      GLfloat  whiteLight[] = { 0.45f, 0.45f, 0.45f, 1.0f };
      GLfloat  sourceLight[] = { 0.25f, 0.25f, 0.25f, 1.0f };
      GLfloat   lightPos[] = { -50.f, 25.0f, 250.0f, 0.0f };
      
      // Включаем поддержку освещения
      glEnable(GL_LIGHTING);
      
      // Устанавливаем и включаем источник 0
      glLightModelfv(GL_LIGHT_MODEL_AMBIENT,whiteLight);
      glLightfv(GL_LIGHT0,GL_AMBIENT,sourceLight);
      glLightfv(GL_LIGHT0,GL_DIFFUSE,sourceLight);
      glLightfv(GL_LIGHT0,GL_POSITION,lightPos);
      glEnable(GL_LIGHT0);
      
      // Включаем поддержку цвета материала
      glEnable(GL_COLOR_MATERIAL);
      
      // Устанавливаем свойства материала для отражаемого цвета
      glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    // для второго упражнения Ortho
    void RenderSceneOrtho(void)
    {
      // Сбрасываем буферы цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Рисуем сцену
      Ortho();
      
      // Прокачка сообщений
      glFlush();
    }
  • Запустите приложение, повращайте брусок и отметьте, что при ортогональной проекции изделие кажется неестественным
  • Попытайтесь разобраться с кодом!!!
  • Упражнение 3. Модель полого бруска в перспективной проекции (Perspect)

    Перейдем к перспективной проекции отсекающего объема.

  • Через панель Solution Explorer добавьте к проекту новый заголовочный файл с именем Perspect.h
  • Наполните этот файл следующим кодом, который будет определять перспективный отсекающий объем видимости
  • //**********************************************************
    // Прототипы
    void ChangeSizePerspect(int, int);  // При изменении размеров окна
    void Perspect(); 
    void RenderScenePerspect(void);
      
    //**********************************************************
    // Упражнение 3: "3) Брусок в перспективной проекции"
    void Perspect() 
    {
      float fZ,bZ;
      
      fZ = 100.0f;
      bZ = -100.0f;
      
      // Сбрасываем матрицу преобразования модели в значение единичной матрицы
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      
      // Отодвигаем всю сцену назад по оси z
      // чтобы видеть ее
        glTranslatef(0.0f, 0.0f, -300.0f);
      
      // Поворачиваем сцену на угол, определяемый клавишами-стрелками
      glRotatef(xRot, 1.0f, 0.0f, 0.0f);
      glRotatef(yRot, 0.0f, 1.0f, 0.0f);
      
      // Устанавливаем цвет рисования красный
      glColor3f(1.0f, 0.0f, 0.0f);
      
      glBegin(GL_QUADS); // Рисуем наружнюю сторону
        // Нормаль по оси Z
        glNormal3f(0.0f, 0.0f, 1.0f);  
      
        // Левая грань
        glVertex3f(-50.0f, 50.0f, fZ);
        glVertex3f(-50.0f, -50.0f, fZ);
        glVertex3f(-35.0f, -50.0f, fZ);
        glVertex3f(-35.0f,50.0f,fZ);
      
        // Правая грань
        glVertex3f(50.0f, 50.0f, fZ);
        glVertex3f(35.0f, 50.0f, fZ);
        glVertex3f(35.0f, -50.0f, fZ);
        glVertex3f(50.0f,-50.0f,fZ);
      
        // Верхняя грань
        glVertex3f(-35.0f, 50.0f, fZ);
        glVertex3f(-35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 50.0f,fZ);
      
        // Нижняя грань
        glVertex3f(-35.0f, -35.0f, fZ);
        glVertex3f(-35.0f, -50.0f, fZ);
        glVertex3f(35.0f, -50.0f, fZ);
        glVertex3f(35.0f, -35.0f,fZ);
      
        // Левая верхняя секция
        // Нормаль по оси Y
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-50.0f, 50.0f, fZ);
        glVertex3f(50.0f, 50.0f, fZ);
        glVertex3f(50.0f, 50.0f, bZ);
        glVertex3f(-50.0f,50.0f,bZ);
      
        // Верхняя секция
        glNormal3f(0.0f, -1.0f, 0.0f);
        glVertex3f(-50.0f, -50.0f, fZ);
        glVertex3f(-50.0f, -50.0f, bZ);
        glVertex3f(50.0f, -50.0f, bZ);
        glVertex3f(50.0f, -50.0f, fZ);
      
        // Левая секция
        glNormal3f(1.0f, 0.0f, 0.0f);
        glVertex3f(50.0f, 50.0f, fZ);
        glVertex3f(50.0f, -50.0f, fZ);
        glVertex3f(50.0f, -50.0f, bZ);
        glVertex3f(50.0f, 50.0f, bZ);
      
        // Правая секция
        glNormal3f(-1.0f, 0.0f, 0.0f);
        glVertex3f(-50.0f, 50.0f, fZ);
        glVertex3f(-50.0f, 50.0f, bZ);
        glVertex3f(-50.0f, -50.0f, bZ);
        glVertex3f(-50.0f, -50.0f, fZ);
      glEnd();
      
      glFrontFace(GL_CW);    // Лицевыми будем считать те грани, вершины
                  // которых обходятся по часовой стрелки
                  // для рисования задней стенки
      
      glBegin(GL_QUADS);    // Рисуем внутреннюю сторону
        // Нормаль по оси Z
        glNormal3f(0.0f, 0.0f, -1.0f);  
      
        // Левая грань
        glVertex3f(-50.0f, 50.0f, bZ);
        glVertex3f(-50.0f, -50.0f, bZ);
        glVertex3f(-35.0f, -50.0f, bZ);
        glVertex3f(-35.0f,50.0f,bZ);
      
        // Правая грань
        glVertex3f(50.0f, 50.0f, bZ);
        glVertex3f(35.0f, 50.0f, bZ);
        glVertex3f(35.0f, -50.0f, bZ);
        glVertex3f(50.0f,-50.0f,bZ);
      
        // Верхняя грань
        glVertex3f(-35.0f, 50.0f, bZ);
        glVertex3f(-35.0f, 35.0f, bZ);
        glVertex3f(35.0f, 35.0f, bZ);
        glVertex3f(35.0f, 50.0f,bZ);
      
        // Нижняя грань
        glVertex3f(-35.0f, -35.0f, bZ);
        glVertex3f(-35.0f, -50.0f, bZ);
        glVertex3f(35.0f, -50.0f, bZ);
        glVertex3f(35.0f, -35.0f,bZ);
      
        // Внутренняя часть
        glColor3f(0.75f, 0.75f, 0.75f); // Серый
      
        // Нормаль по оси Y
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 35.0f, fZ);
        glVertex3f(35.0f, 35.0f, bZ);
        glVertex3f(-35.0f,35.0f,bZ);
      
        // Нижняя секция
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-35.0f, -35.0f, fZ);
        glVertex3f(-35.0f, -35.0f, bZ);
        glVertex3f(35.0f, -35.0f, bZ);
        glVertex3f(35.0f, -35.0f, fZ);
      
        // Левая секция
        glNormal3f(1.0f, 0.0f, 0.0f);
        glVertex3f(-35.0f, 35.0f, fZ);
        glVertex3f(-35.0f, 35.0f, bZ);
        glVertex3f(-35.0f, -35.0f, bZ);
        glVertex3f(-35.0f, -35.0f, fZ);
      
        // Правая секция
        glNormal3f(-1.0f, 0.0f, 0.0f);
        glVertex3f(35.0f, 35.0f, fZ);
        glVertex3f(35.0f, -35.0f, fZ);
        glVertex3f(35.0f, -35.0f, bZ);
        glVertex3f(35.0f, 35.0f, bZ);
      glEnd();
      
      // Возвращаем обход сторон к значению по умолчанию
      glFrontFace(GL_CCW);
        
      // Переключаем буфер кадра
      glutSwapBuffers();
    }
      
    //**********************************************************
    void ChangeSizePerspect(int width, int height)
    {
      // Предотвращаем деление на нуль
      if(height == 0)
        height = 1;
      
      // Устанавливаем поле просмотра с размерами окна
        glViewport(0, 0, width, height);
      
      // Устанавливаем размеры перспективы (отсекающего объема)
      // (left, right, bottom, top, near, far)
      GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;// Для коррекции
      
      // Устанавливает матрицу преобразования в режим проецирования
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      
      // Устанавливаем перспективную проекцию
      gluPerspective(60.0f, aspectRatio, 0.5, 400.0);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScenePerspect(void)
    {
      // Сбрасываем буферы цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Рисуем сцену
      Perspect();
      
      // Прокачка сообщений
      glFlush();
    }
  • Скорректируйте файл ProjectionMatrix.cpp для третьего упражнения
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"  // Упражнение: "1) Простая модель атома"
    #include "Ortho.h"  // Упражнение 2: "2) Брусок в ортогональной проекции"  
    #include "Perspect.h"  // Упражнение 3: "3) Брусок в перспективной проекции"
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
        case 2:
          ChangeSizeOrtho(w, h);
          break;
        case 3:
          ChangeSizePerspect(w, h);
          break;
      }
      
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          SetupLight(false);
          RenderSceneAtom();
          break;
        case 2:
          SetupLight(true);
          RenderSceneOrtho();
          break;
        case 3:
          SetupLight(true);
          RenderScenePerspect();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
        case 2:
          ChangeSizeOrtho(width, height);
          break;
        case 3:
          ChangeSizePerspect(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      glutTimerFunc(100, TimerFunc, 1); // Заряжаем новый таймер
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками
    // для задания новых значений матрицы поворота
    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;
      
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      glutAddMenuEntry("2) Брусок в ортогональной проекции", 2);
      glutAddMenuEntry("3) Брусок в перспективной проекции", 3);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Запустите приложение, повращайте брусок и отметьте, что при перспективной проекции изделие кажется более реалистичным
  • Ортогональная проекция

    Ортогональная проекция

       

    Перспективная проекция

    Перспективная проекция

  • Попытайтесь разобраться с кодом!!!
  • Упражнение 4. Построение модели атома в перспективнойпроекции (Atom Perspect)

    Переделаем нашу модель атома, построенную в " Упражнении 1 ", с использованием перспективной проекции.

  • Через панель Solution Explorer добавьте к проекту новый заголовочный файл с именем AtomPerspect.h
  • Скопируйте в файл AtomPerspect.h содержимое файла Atom.h и откорректируйте его следующим образом
  • //**********************************************************
    // Прототипы
    void AtomPerspect();        
    void RenderSceneAtomPerspect(void);
    void ChangeSizeAtomPerspect(int, int);  // При изменении размеров окна
    void ShowOrbitPerspect(GLfloat radius);
      
    //**********************************************************
    // Упражнение 4: "4) Модель атома в перспективе"
    void AtomPerspect() 
    {
      // Угол поворота вокруг ядра
      static GLfloat fElect = 0.0f;
      
      GLfloat radius;
      
      // Сбрасываем буфер цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Сбрасываем матрицу наблюдения модели в значение единичной матрицы
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      
      // Отодвигаем всю сцену назад по оси z
      // чтобы видеть ее
      glTranslatef(0.0f, 0.0f, -250.0f);  
      
      // Управление видом
      glRotatef(xRot, 1.0f, 0.0f, 0.0f);
      glRotatef(yRot, 0.0f, 1.0f, 0.0f);
      
      // Красное ядро прямо по центру линии наблюдения
      glColor3ub(255, 0, 0);
      glutSolidSphere(10.0f, 15, 15);
      
      // Задаем желтый цвет для всех электронов
      glColor3ub(255,255,0);
      
      // ПЕРВЫЙ ЭЛЕКТРОН
      radius = 90.0f;
      // Сохраняем исходное преобразование наблюдения в стеке матриц
      glPushMatrix();
      ShowOrbit(radius);  // Рисуем орбиту 
      // Формируем точку центра первого электрона
      // поворотом на угол относительно оси 0y
      glRotatef(fElect, 0.0f, 1.0f, 0.0f);
      // Сдвинули точку по оси 0x на radius единиц
      glTranslatef(radius, 0.0f, 0.0f);
      // Рисуем электрон
      glutSolidSphere(6.0f, 15, 15);
      // Восстанавливаем исходную матрицу преобразования наблюдения
      glPopMatrix();
      
      // ВТОРОЙ ЭЛЕКТРОН
      radius = 70.0f;
      glPushMatrix();
      glRotatef(120.0f, 0.0f, 0.0f, 1.0f);
      ShowOrbit(radius);  // Рисуем орбиту 
      glRotatef(fElect + 90.0f, 0.0f, 1.0f, 0.0f);
      glTranslatef(-radius, 0.0f, 0.0f);
      glutSolidSphere(6.0f, 15, 15);
      glPopMatrix();
      
      // ТРЕТИЙ ЭЛЕКТРОН
      radius = 60.0f;
      glPushMatrix();
      glRotatef(-120.0f,0.0f, 0.0f, 1.0f);
      ShowOrbit(radius);  // Рисуем орбиту 
      glRotatef(fElect - 90.0f, 0.0f, 1.0f, 0.0f);
      glTranslatef(0.0f, 0.0f, radius);
      glutSolidSphere(6.0f, 15, 15);
      glPopMatrix();
      
      // Увеличиваем угол поворота
      fElect += 10.0f;
      fElect = fElect > 360.0f ? 0.0f : fElect;
      
      // Переключаем буферы рисования
      glutSwapBuffers();
    }
      
    //**********************************************************
    #define GL_PI 3.1415f
    #include <math.h> // Для тригонометрических функций
      
    void ShowOrbitPerspect(GLfloat radius)
    {
      // Переменные орбиты
      GLfloat x, y = 0.0f, z, angle;
      
      glBegin(GL_LINE_LOOP);
      for(angle = 0.0f; angle < 2.0f * GL_PI; angle += 0.1f)
      {
        x = radius * sin(angle);
        z = radius * cos(angle);
        glVertex3f(x, y, z);
      }
      glEnd();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderSceneAtomPerspect(void)
    {
      // Сбрасываем буфер цвета и буфер глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Рисуем сцену
      AtomPerspect();
      
      // Прокачка сообщений
      glFlush();
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSizeAtomPerspect(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;// Для коррекции
      
        gluPerspective(45.0, aspectRatio, 1.0, 500.0);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
  • Скорректируйте файл ProjectionMatrix.cpp для четвертого упражнения
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"  // Упражнение: "1) Простая модель атома"
    #include "Ortho.h"  // Упражнение 2: "2) Брусок в ортогональной проекции"  
    #include "Perspect.h"  // Упражнение 3: "3) Брусок в перспективной проекции"
    #include "AtomPerspect.h"  // Упражнение 4: "4) Модель атома в перспективе"
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
        case 2:
          ChangeSizeOrtho(w, h);
          break;
        case 3:
          ChangeSizePerspect(w, h);
          break;
        case 4:
          ChangeSizeAtomPerspect(w, h);
          break;
      }
      
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          SetupLight(false);
          RenderSceneAtom();
          break;
        case 2:
          SetupLight(true);
          RenderSceneOrtho();
          break;
        case 3:
          SetupLight(true);
          RenderScenePerspect();
          break;
        case 4:
          SetupLight(false);
          RenderSceneAtomPerspect();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
        case 2:
          ChangeSizeOrtho(width, height);
          break;
        case 3:
          ChangeSizePerspect(width, height);
          break;
        case 4:
          ChangeSizeAtomPerspect(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      glutTimerFunc(100, TimerFunc, 1); // Заряжаем новый таймер
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками
    // для задания новых значений матрицы поворота
    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;
      
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      glutAddMenuEntry("2) Брусок в ортогональной проекции", 2);
      glutAddMenuEntry("3) Брусок в перспективной проекции", 3);
      glutAddMenuEntry("4) Модель атома в перспективе", 4);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Разберитесь с кодом, запустите упражнение, поуправляйте изображением с помощью клавиш-стрелок.
  • На динамической модели атома в перспективной проекции мгновенные снимки экрана могут быть такими

    Упражнение 5. Моделирование системы с относительными вращениями (Solar)

    Рассмотрим еще один пример, демонстрирующий взаимодействие матрицы наблюдения модели и матрицы перспективной проекции. Мы смоделируем систему "Солнце-Земля-Луна", использующую вложенные преобразования, когда объекты преобразовываются относительно друг друга с использованием стека матриц. Чтобы сделать картину более эффектной, мы, пока без подробных объяснений, просто применим к модели функции освещения и затенения (текстурирования).

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

  • Добавьте к приложению новый заголовочный файл с именем Solar.h
  • Наполните файл Solar.h следующим кодом
  • //**********************************************************
    // Прототипы
    void Solar(); 
    void SetupLightSolar(bool);
    void ChangeSizeSolar(int, int);
    void RenderSceneSolar(void);
    void ShowOrbitSolar(GLfloat);
      
    //**********************************************************
    // Упражнение 5: "5) Система \"Солнце-Земля-Луна\""
    void Solar()
    {
      GLfloat   lightPos[] = { 0.0f, 0.0f, 0.0f, 1.0f };
      // Начальные значения углов поворота Землм и Луны
      static float fMoonRot = 0.0f; // Поворот Луны
      static float fEarthRot = 0.0f;// Поворот Земли
      
      GLfloat radius;
      
      // Сбрасываем буфер цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Сохраняем матрицу модели перед выполнением поворотов
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      
      // Отодвигаем всю сцену вниз оси z в поле зрения
      // и рисуем Солнце
      glTranslatef(0.0f, 0.0f, -300.0f);  
      glDisable(GL_LIGHTING);
      glColor3ub(255, 255, 0); // Желтый
      glutSolidSphere(15.0f, 30, 17); // Рисуем Солнце
      glEnable(GL_LIGHTING);
      
      // Поворачиваем сцену на угол, определяемый клавишами-стрелками
      glRotatef(xRot, 1.0f, 0.0f, 0.0f);
      
      // Помещаем в точку Солнца источник света
      glLightfv(GL_LIGHT0,GL_POSITION,lightPos);
      
      // Поворачиваем систему координат, связанную с Солнцем
      // и рисуем Землю
      radius = 105.0f;
      ShowOrbitSolar(radius);  // Рисуем орбиту 
      glRotatef(fEarthRot, 0.0f, 1.0f, 0.0f);
      glTranslatef(radius,0.0f,0.0f);
      glColor3ub(0,0,255); // Синий
      glutSolidSphere(15.0f, 30, 17);// Рисуем Землю
      
      // Поворачиваем систему координат, связанную с Землей
      // и рисуем Луну
      radius = 30.0f;
      ShowOrbitSolar(radius);  // Рисуем орбиту 
      glRotatef(fMoonRot,0.0f, 1.0f, 0.0f);
      glTranslatef(radius, 0.0f, 0.0f);
      glColor3ub(200,200,200); // Серый
      fMoonRot+= 15.0f;
      fMoonRot = fMoonRot < 360.0f ? fMoonRot : 0.0;
      glutSolidSphere(6.0f, 30, 17); // Рисуем Луну
      
      // Восстанавливаем матрицу модели
      glPopMatrix();
      
      // Увеличиваем угол поворота на орбите Земли
      fEarthRot += 5.0f;
      fEarthRot = fEarthRot < 360.0f ? fEarthRot : 0.0;
      
      // Переключить буфер и показать изображение
      glutSwapBuffers();
    }
      
    //**********************************************************
    // Устанавливаем источник света сцены
    void SetupLightSolar(bool flag)
    {
      if(!flag)
      {
        glDisable(GL_LIGHTING);
        return;
      }
      
      // Определяем характеристики источника света для сцены
      GLfloat  whiteLight[] = { 0.2f, 0.2f, 0.2f, 1.0f };
      GLfloat  sourceLight[] = { 0.8f, 0.8f, 0.8f, 1.0f };
      GLfloat   lightPos[] = { 0.0f, 0.0f, 0.0f, 1.0f };
      
      // Включаем поддержку освещения
      glEnable(GL_LIGHTING);
      
      // Устанавливаем и включаем источник 0
      glLightModelfv(GL_LIGHT_MODEL_AMBIENT,whiteLight);
      glLightfv(GL_LIGHT0,GL_DIFFUSE,sourceLight);
      glLightfv(GL_LIGHT0,GL_POSITION,lightPos);
      glEnable(GL_LIGHT0);
      
      // Включаем поддержку цвета материала
      glEnable(GL_COLOR_MATERIAL);
      
      // Устанавливаем свойства материала для отражаемого цвета
      glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    }
      
    //**********************************************************
    void ChangeSizeSolar(int width, int height)
    {
      // Предотвращаем деление на нуль
      if(height == 0)
        height = 1;
      
      // Устанавливаем поле просмотра с размерами окна
        glViewport(0, 0, width, height);
      
      // Устанавливаем размеры перспективы (отсекающего объема)
      // (left, right, bottom, top, near, far)
      GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;// Для коррекции
      
      // Устанавливает матрицу преобразования в режим проецирования
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      
      // Устанавливаем перспективную проекцию
        gluPerspective(45.0f, aspectRatio, 1.0, 425.0);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderSceneSolar(void)
    {
      // Сбрасываем буферы цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Рисуем сцену
      Solar();
      
      // Прокачка сообщений
      glFlush();
    }
      
    //**********************************************************
    #define GL_PI 3.1415f
    #include <math.h> // Для тригонометрических функций
    void ShowOrbitSolar(GLfloat radius)
    {
      // Переменные орбиты
      GLfloat x, y = 0.0f, z, angle;
      glColor3ub(255, 255, 0); // Желтый
      
      glBegin(GL_LINE_LOOP);
        for(angle = 0.0f; angle < 2.0f * GL_PI; angle += 0.1f)
        {
          x = radius * sin(angle);
          z = radius * cos(angle);
          glVertex3f(x, y, z);
        }
      glEnd();
    }
  • Скорректируйте файл ProjectionMatrix.cpp для пятого упражнения
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"  // Упражнение: "1) Простая модель атома"
    #include "Ortho.h"  // Упражнение 2: "2) Брусок в ортогональной проекции"  
    #include "Perspect.h"  // Упражнение 3: "3) Брусок в перспективной проекции"
    #include "AtomPerspect.h"  // Упражнение 4: "4) Модель атома в перспективе"
    #include "Solar.h"  // Упражнение 5: "5) Система \"Солнце-Земля-Луна\""
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
        case 2:
          ChangeSizeOrtho(w, h);
          break;
        case 3:
          ChangeSizePerspect(w, h);
          break;
        case 4:
          ChangeSizeAtomPerspect(w, h);
          break;
        case 5:
          ChangeSizeSolar(w, h);
          break;
      }
      
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          SetupLight(false);
          RenderSceneAtom();
          break;
        case 2:
          SetupLight(true);
          RenderSceneOrtho();
          break;
        case 3:
          SetupLight(true);
          RenderScenePerspect();
          break;
        case 4:
          SetupLight(false);
          RenderSceneAtomPerspect();
          break;
        case 5:
          SetupLightSolar(true);
          RenderSceneSolar();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
        case 2:
          ChangeSizeOrtho(width, height);
          break;
        case 3:
          ChangeSizePerspect(width, height);
          break;
        case 4:
          ChangeSizeAtomPerspect(width, height);
          break;
        case 5:
          ChangeSizeSolar(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      glutTimerFunc(100, TimerFunc, 1); // Заряжаем новый таймер
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками
    // для задания новых значений матрицы поворота
    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;
      
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      glutAddMenuEntry("2) Брусок в ортогональной проекции", 2);
      glutAddMenuEntry("3) Брусок в перспективной проекции", 3);
      glutAddMenuEntry("4) Модель атома в перспективе", 4);
      glutAddMenuEntry("5) Система \"Солнце-Земля-Луна\"", 5);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Разберитесь с кодом, запустите упражнение, поуправляйте изображением с помощью клавиш-стрелок
  • На динамической модели в перспективной проекции мгновенные снимки экрана могут быть такими

    Упражнение 6. Прямая загрузка матриц преобразований (Transform)

    В начале этой работы мы упоминали возможность прямой загрузки значений в текущие матрицы преобразования командами:

  • glLoadIdentity()
  • glLoadMatrix{fd}(const TYPE *m)
  • glLoadTransposeMatrix{fd}(const TYPE *m)
  • В данном упражнении Transform мы применим эти возможности для моделирования вращающегося тора.

    Мы сами будем предварительно вычислять матрицу преобразования M размером 4x4, и применять команду загрузки glLoadMatrix(), передавая в качестве аргумента указатель на M. В программном представлении матрица M должна быть одномерным массивом m, элементы которого следуют в порядке расположения столбцов. Это связано с тем, что двумерные массивы в языке C, C++ располагаются в памяти в порядке следования строк. Графическая система OpenGL рассматривает последовательность расположенных в памяти 16 элементов как следующие друг за другом 4 столбца матрицы M.

    Это значит, что имея математическое представление матрицы преобразования M для правильной ее загрузки командой glLoadMatrix() мы должны массив m сформировать в порядке следования столбцов матрицы M. Другой способ - загружать матрицу M с помощью команды glLoadTransposeMatrix() как транспонированную.

  • Добавьте к приложению новый заголовочный файл с именем Transform.h
  • Наполните файл Transform.h следующим кодом
  • //**********************************************************
    // Упражнение 6: "6) Каркасный тор - своя матрица"
    #include <math.h>
    #define GLT_PI  3.14159265358979323846
    #define GLT_PI_DIV_180 0.017453292519943296
    typedef GLfloat GLTVector3[3];      // Вещественная точка
    typedef GLfloat GLTMatrix[16]; 
      
    //**********************************************************
    // Прототипы
    void DrawTorus(void);
    void RenderSceneTransform(void);
    void ChangeSizeTransform(int, int);
    void RotationMatrix(float, float, float, float, GLTMatrix);
    void LoadIdentityMatrix(GLTMatrix);
    void TransformPoint(const GLTVector3, const GLTMatrix, GLTVector3);
      
    //**********************************************************
    void DrawTorus(void)
    {
      GLTMatrix   mTransform;    // Объявление матрицы поворота
      static GLfloat yRot = 0.0f;  // Угол поворота для анимации в градусах
      
      // Строим матрицу преобразования
      // Угол преобразуем в радианы
      RotationMatrix(GLT_PI_DIV_180 * yRot, 0.0f, 1.0f, 0.0f, mTransform);
      mTransform[12] = 0.0f;
      mTransform[13] = 0.0f;
      mTransform[14] = -2.5f;
      
      GLfloat majorRadius = 0.35f;
      GLfloat minorRadius = 0.15f;
      GLint   numMajor = 40;
      GLint   numMinor = 20;
      GLTVector3 objectVertex;         // Вершины в исходном состоянии
      GLTVector3 transformedVertex;    // Преобразованные вершины
      double majorStep = 2.0f * GLT_PI / numMajor;
      double minorStep = 2.0f * GLT_PI / numMinor;
      int i, j;
      
      for (i=0; i<numMajor; ++i) 
      {
        double a0 = i * majorStep;
        double a1 = a0 + majorStep;
        GLfloat x0 = (GLfloat) cos(a0);
        GLfloat y0 = (GLfloat) sin(a0);
        GLfloat x1 = (GLfloat) cos(a1);
        GLfloat y1 = (GLfloat) sin(a1);
      
        glBegin(GL_TRIANGLE_STRIP);
          for (j=0; j<=numMinor; ++j) 
          {
            double b = j * minorStep;
            GLfloat c = (GLfloat) cos(b);
            GLfloat r = minorRadius * c + majorRadius;
            GLfloat z = minorRadius * (GLfloat) sin(b);
      
            // Первая точка
            objectVertex[0] = x0 * r;
            objectVertex[1] = y0 * r;
            objectVertex[2] = z;
            TransformPoint(objectVertex, mTransform, transformedVertex);
            glVertex3fv(transformedVertex);
      
            // Вторая точка
            objectVertex[0] = x1 * r;
            objectVertex[1] = y1 * r;
            objectVertex[2] = z;
            TransformPoint(objectVertex, mTransform, transformedVertex);
            glVertex3fv(transformedVertex);
          }
            glEnd();
      }
      
      // Наращиваем анимацию
      yRot += 1.5f;
      
      // Переключаем буферы
      glutSwapBuffers();
    }
      
    //**********************************************************
    void RenderSceneTransform(void)
    {
      // Сохраняем атрибуты в стеке атрибутов!!!
      glPushAttrib(GL_COLOR_BUFFER_BIT | GL_POLYGON_BIT);
      
      // Устанавливаем белый цвет и голубой фон
      glColor3ub(255, 255, 255); // Белый
      glClearColor(0.0f, 0.0f, .50f, 1.0f );
      // Сбрасываем буферы цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      // Рисовать в каркасном режиме
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      
      DrawTorus();
      
      // Восстанавливаем атрибуты из стека атрибутов!!!
      glPopAttrib();
      
      // Прокачка сообщений
      glFlush();
    }
      
    //**********************************************************
    void ChangeSizeTransform(int width, int height)
    {
      // Предотвращаем деление на нуль
      if(height == 0)
        height = 1;
      
      // Устанавливаем поле просмотра с размерами окна
        glViewport(0, 0, width, height);
      
      // Характеристическое число для соблюдения пропорций
      GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;// Для коррекции
      
      // Устанавливает матрицу преобразования в режим проецирования
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      
      // Устанавливаем перспективную проекцию
      gluPerspective(35.0f, aspectRatio, 1.0f, 50.0f);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
      
    //**********************************************************
    // Создание матрицы поворота 4x4
    void RotationMatrix(float angle, float x, float y, float z, GLTMatrix mMatrix)
    {
      float vecLength, sinSave, cosSave, oneMinusCos;
      float xx, yy, zz, xy, yz, zx, xs, ys, zs;
      
      if(x == 0.0f  y == 0.0f  z == 0.0f)
      {
        LoadIdentityMatrix(mMatrix);
        return;
      }
      
      // Нормализуем вектор
      vecLength = (float)sqrt( x*x + y*y + z*z );
      x /= vecLength;
      y /= vecLength;
      z /= vecLength;
      
      sinSave = (float)sin(angle);
      cosSave = (float)cos(angle);
      oneMinusCos = 1.0f - cosSave;
      
      xx = x * x;
      yy = y * y;
      zz = z * z;
      xy = x * y;
      yz = y * z;
      zx = z * x;
      xs = x * sinSave;
      ys = y * sinSave;
      zs = z * sinSave;
      
      mMatrix[0] = (oneMinusCos * xx) + cosSave;
      mMatrix[4] = (oneMinusCos * xy) - zs;
      mMatrix[8] = (oneMinusCos * zx) + ys;
      mMatrix[12] = 0.0f;
      
      mMatrix[1] = (oneMinusCos * xy) + zs;
      mMatrix[5] = (oneMinusCos * yy) + cosSave;
      mMatrix[9] = (oneMinusCos * yz) - xs;
      mMatrix[13] = 0.0f;
      
      mMatrix[2] = (oneMinusCos * zx) - ys;
      mMatrix[6] = (oneMinusCos * yz) + xs;
      mMatrix[10] = (oneMinusCos * zz) + cosSave;
      mMatrix[14] = 0.0f;
      
      mMatrix[3] = 0.0f;
      mMatrix[7] = 0.0f;
      mMatrix[11] = 0.0f;
      mMatrix[15] = 1.0f;
    }
      
    //**********************************************************
    // Загрузка матрицы как единичной
    void LoadIdentityMatrix(GLTMatrix m)
    {
      static GLTMatrix identity = { 1.0f, 0.0f, 0.0f, 0.0f,
                                     0.0f, 1.0f, 0.0f, 0.0f,
                                     0.0f, 0.0f, 1.0f, 0.0f,
                                     0.0f, 0.0f, 0.0f, 1.0f };
      
      memcpy(m, identity, sizeof(GLTMatrix));
    }
      
    //**********************************************************
    void TransformPoint(const GLTVector3 vSrcVector, const GLTMatrix mMatrix, GLTVector3 vOut)
    {
      vOut[0] = mMatrix[0] * vSrcVector[0] + mMatrix[4] * vSrcVector[1] + mMatrix[8] *  vSrcVector[2] + mMatrix[12];
      vOut[1] = mMatrix[1] * vSrcVector[0] + mMatrix[5] * vSrcVector[1] + mMatrix[9] *  vSrcVector[2] + mMatrix[13];
      vOut[2] = mMatrix[2] * vSrcVector[0] + mMatrix[6] * vSrcVector[1] + mMatrix[10] * vSrcVector[2] + mMatrix[14];    
    }
  • Скорректируйте файл ProjectionMatrix.cpp для шестого упражнения
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"  // Упражнение: "1) Простая модель атома"
    #include "Ortho.h"  // Упражнение 2: "2) Брусок в ортогональной проекции"  
    #include "Perspect.h"  // Упражнение 3: "3) Брусок в перспективной проекции"
    #include "AtomPerspect.h"  // Упражнение 4: "4) Модель атома в перспективе"
    #include "Solar.h"  // Упражнение 5: "5) Система \"Солнце-Земля-Луна\""
    #include "Transform.h"  // Упражнение 6: "6) Каркасный тор - своя матрица"
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
        case 2:
          ChangeSizeOrtho(w, h);
          break;
        case 3:
          ChangeSizePerspect(w, h);
          break;
        case 4:
          ChangeSizeAtomPerspect(w, h);
          break;
        case 5:
          ChangeSizeSolar(w, h);
          break;
        case 6:
          ChangeSizeTransform(w, h);
          break;
      }
      
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          SetupLight(false);
          RenderSceneAtom();
          break;
        case 2:
          SetupLight(true);
          RenderSceneOrtho();
          break;
        case 3:
          SetupLight(true);
          RenderScenePerspect();
          break;
        case 4:
          SetupLight(false);
          RenderSceneAtomPerspect();
          break;
        case 5:
          SetupLightSolar(true);
          RenderSceneSolar();
          break;
        case 6:
          SetupLight(false);
          RenderSceneTransform();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
        case 2:
          ChangeSizeOrtho(width, height);
          break;
        case 3:
          ChangeSizePerspect(width, height);
          break;
        case 4:
          ChangeSizeAtomPerspect(width, height);
          break;
        case 5:
          ChangeSizeSolar(width, height);
          break;
        case 6:
          ChangeSizeTransform(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      glutTimerFunc(100, TimerFunc, 1); // Заряжаем новый таймер
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками
    // для задания новых значений матрицы поворота
    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;
      
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      glutAddMenuEntry("2) Брусок в ортогональной проекции", 2);
      glutAddMenuEntry("3) Брусок в перспективной проекции", 3);
      glutAddMenuEntry("4) Модель атома в перспективе", 4);
      glutAddMenuEntry("5) Система \"Солнце-Земля-Луна\"", 5);
      glutAddMenuEntry("6) Каркасный тор - своя матрица", 6);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Разберитесь с кодом, запустите упражнение
  • На динамической модели мгновенные снимки экрана вращающегося вокруг оси 0y тора могут быть такими

    Упражнение 7. Прямая загрузка матриц преобразований (SphereWorld)

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

  • Добавьте к приложению новый заголовочный файл с именем SphereWorld.h
  • Наполните файл SphereWorld.h следующим кодом
  • //**********************************************************
    // Упражнение 7: "7) Фантастический мир со сферами"
    #include <math.h>
    #define GLT_PI  3.14159265358979323846
    #define GLT_PI_DIV_180 0.017453292519943296
    #define gltDegToRad(x)  ((x)*GLT_PI_DIV_180)
    #define NUM_SPHERES      50 // Количество сфер
    typedef GLfloat GLTVector3[3];      // Вектор
    typedef GLfloat GLTMatrix[16];      // Матрица 4x4
    typedef struct{                     // Фрейм 3x3
            GLTVector3 vLocation;
            GLTVector3 vUp;
            GLTVector3 vForward;
             } GLTFrame;
    GLTFrame    spheres[NUM_SPHERES];
    GLTFrame    frameCamera;
      
    //**********************************************************
    // Прототипы
    void SphereWorld();
    void DrawGround();
    void RenderSceneSphereWorld();
    void ChangeSizeSphereWorld(int, int);
    void SpecialKeysSphereWorld(int);
    void SetupSphereWorld();
      
    void gltApplyCameraTransform(GLTFrame *);
    void gltApplyActorTransform(GLTFrame *);
    void gltGetMatrixFromFrame(GLTFrame *, GLTMatrix);
    void gltDrawTorus(GLfloat, GLfloat, GLint, GLint);
    void gltNormalizeVector(GLTVector3);
    void gltScaleVector(GLTVector3, const GLfloat);
    GLfloat gltGetVectorLength(const GLTVector3);
    GLfloat gltGetVectorLengthSqrd(const GLTVector3);
    void gltInitFrame(GLTFrame *);
    void gltMoveFrameForward(GLTFrame *, GLfloat);
    void gltRotateFrameLocalY(GLTFrame *, GLfloat);
    void gltRotationMatrix(float, float, float, float, GLTMatrix);
    void gltRotateVector(const GLTVector3, const GLTMatrix, GLTVector3);
    void gltVectorCrossProduct(const GLTVector3, const GLTVector3, GLTVector3);
    void gltLoadIdentityMatrix(GLTMatrix);
      
    //**********************************************************
    // Формировать сцену
    void SphereWorld(void)
    {
      static GLfloat yRot = 0.0f;  // Угол поворота для анимации в градусах
      
      glPushMatrix();
        gltApplyCameraTransform(frameCamera);
        // Рисуем сетку
        DrawGround();
        // Рисовать сферы по ранее сгенерированным случайным координатам
        for(int i = 0; i < NUM_SPHERES; i++)
        {
          glPushMatrix();
            gltApplyActorTransformspheres[i]);
            glutSolidSphere(0.1f, 13, 26);
          glPopMatrix();
        }
        glPushMatrix();
          glTranslatef(0.0f, 0.0f, -2.5f);
          glPushMatrix();
            glRotatef(-yRot * 2.0f, 0.0f, 1.0f, 0.0f);
            glTranslatef(1.0f, 0.0f, 0.0f);
            glutSolidSphere(0.1f, 13, 26);
          glPopMatrix();
          glRotatef(yRot, 0.0f, 1.0f, 0.0f);
          gltDrawTorus(0.35, 0.15, 40, 20);
        glPopMatrix();
      glPopMatrix();
      
      // Наращиваем анимацию
      yRot += 0.5f;
      
      // Переключаем буферы
      glutSwapBuffers();
    }
      
    //**********************************************************
    // Рисовать сетку основания
    void DrawGround(void)
    {
      GLfloat fExtent = 20.0f;
      GLfloat fStep = 1.0f;
      GLfloat y = -0.4f;
      GLint iLine;
      
      glBegin(GL_LINES);
        for(iLine = -fExtent; iLine <= fExtent; iLine += fStep)
        {
          glVertex3f(iLine, y, fExtent);    // Рисовать Z-линии
          glVertex3f(iLine, y, -fExtent);
      
          glVertex3f(fExtent, y, iLine);
          glVertex3f(-fExtent, y, iLine);
        }
      glEnd();
    }
      
    //**********************************************************
    // Рендеринг
    void RenderSceneSphereWorld()
    {
      // Сохраняем атрибуты в стеке атрибутов!!!
      glPushAttrib(GL_COLOR_BUFFER_BIT | GL_POLYGON_BIT);
      
      // Устанавливаем белый цвет и голубой фон
      glColor3ub(255, 255, 255); // Белый
      glClearColor(0.0f, 0.0f, .50f, 1.0f );
      // Сбрасываем буферы цвета и глубины
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      
      // Рисовать с двух сторон в каркасном режиме
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      
      SphereWorld(); // Рисуем сцену
      
      // Восстанавливаем атрибуты из стека атрибутов!!!
      glPopAttrib();
      
      // Прокачка сообщений
      glFlush();
    }
      
    //**********************************************************
    void ChangeSizeSphereWorld(int width, int height)
    {
      // Предотвращаем деление на нуль
      if(height == 0)
        height = 1;
      
      // Устанавливаем поле просмотра с размерами окна
      glViewport(0, 0, width, height);
      
      // Характеристическое число для соблюдения пропорций
      GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;// Для коррекции
      
      // Устанавливает матрицу преобразования в режим проецирования
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      
      // Устанавливаем перспективную проекцию
      gluPerspective(35.0f, aspectRatio, 1.0f, 50.0f);
      
      // Восстановливает матрицу преобразования в исходный режим вида
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
      
    //**********************************************************
    // Управление фреймом камеры
    void SpecialKeysSphereWorld(int key)
    {
      if(key == GLUT_KEY_UP)
        gltMoveFrameForward(frameCamera, 0.1f);
      
      if(key == GLUT_KEY_DOWN)
        gltMoveFrameForward(frameCamera, -0.1f);
      
      if(key == GLUT_KEY_LEFT)
        gltRotateFrameLocalY(frameCamera, 0.1);
          
      if(key == GLUT_KEY_RIGHT)
        gltRotateFrameLocalY(frameCamera, -0.1);
    }
      
    //**********************************************************
    // Формируем матрицу проекции
    void gltApplyCameraTransform(GLTFrame *pCamera)
    {
      GLTMatrix mMatrix;
      GLTVector3 vAxisX;
      GLTVector3 zFlipped;
      
      zFlipped[0] = -pCamera->vForward[0];
      zFlipped[1] = -pCamera->vForward[1];
      zFlipped[2] = -pCamera->vForward[2];
      
      // Получим вектор X
      gltVectorCrossProduct(pCamera->vUp, zFlipped, vAxisX);
      
      // Формируем матрицу поворота и транспонируем ее
      mMatrix[0] = vAxisX[0];
      mMatrix[4] = vAxisX[1];
      mMatrix[8] = vAxisX[2];
      mMatrix[12] = 0.0f;
      
      mMatrix[1] = pCamera->vUp[0];
      mMatrix[5] = pCamera->vUp[1];
      mMatrix[9] = pCamera->vUp[2];
      mMatrix[13] = 0.0f;
      
      mMatrix[2] = zFlipped[0];
      mMatrix[6] = zFlipped[1];
      mMatrix[10] = zFlipped[2];
      mMatrix[14] = 0.0f;
      
      mMatrix[3] = 0.0f;
      mMatrix[7] = 0.0f;
      mMatrix[11] = 0.0f;
      mMatrix[15] = 1.0f;
      
      // Выполним поворот
      glMultMatrixf(mMatrix);
      
      // Теперь отодвигаемся назад
      glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
    }
      
    //**********************************************************
    void gltApplyActorTransform(GLTFrame *pFrame)
    {
      GLTMatrix mTransform;
      gltGetMatrixFromFrame(pFrame, mTransform);
      glMultMatrixf(mTransform);
    }
      
    //**********************************************************
    void gltGetMatrixFromFrame(GLTFrame *pFrame, GLTMatrix mMatrix)
    {
      GLTVector3 vXAxis;
      
      gltVectorCrossProduct(pFrame->vUp, pFrame->vForward, vXAxis);
      
      memcpy(mMatrix, vXAxis, sizeof(GLTVector3));
      mMatrix[3] = 0.0f;
      
      memcpy(mMatrix+4, pFrame->vUp, sizeof(GLTVector3));
      mMatrix[7] = 0.0f;
      
      memcpy(mMatrix+8, pFrame->vForward, sizeof(GLTVector3));
      mMatrix[11] = 0.0f;
      
      memcpy(mMatrix+12, pFrame->vLocation, sizeof(GLTVector3));
      mMatrix[15] = 1.0f;
    }
      
    //**********************************************************
    void gltDrawTorus(GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)
    {
      GLTVector3 vNormal;
      double majorStep = 2.0f*GLT_PI / numMajor;
      double minorStep = 2.0f*GLT_PI / numMinor;
      
      for (int i=0; i<numMajor; ++i) 
      {
        double a0 = i * majorStep;
        double a1 = a0 + majorStep;
        GLfloat x0 = (GLfloat) cos(a0);
        GLfloat y0 = (GLfloat) sin(a0);
        GLfloat x1 = (GLfloat) cos(a1);
        GLfloat y1 = (GLfloat) sin(a1);
      
        glBegin(GL_TRIANGLE_STRIP);
          for (int j=0; j<=numMinor; ++j) 
          {
            double b = j * minorStep;
            GLfloat c = (GLfloat) cos(b);
            GLfloat r = minorRadius * c + majorRadius;
            GLfloat z = minorRadius * (GLfloat) sin(b);
      
            glTexCoord2f((float)(i)/(float)(numMajor), (float)(j)/(float)(numMinor));
            vNormal[0] = x0*c;
            vNormal[1] = y0*c;
            vNormal[2] = z/minorRadius;
            gltNormalizeVector(vNormal);
            glNormal3fv(vNormal);
            glVertex3f(x0*r, y0*r, z);
      
            glTexCoord2f((float)(i+1)/(float)(numMajor), (float)(j)/(float)(numMinor));
            vNormal[0] = x1*c;
            vNormal[1] = y1*c;
            vNormal[2] = z/minorRadius;
            glNormal3fv(vNormal);
            glVertex3f(x1*r, y1*r, z);
          }
        glEnd();
      }
    }
      
    //**********************************************************
    void gltNormalizeVector(GLTVector3 vNormal)
    { 
      GLfloat fLength = 1.0f / gltGetVectorLength(vNormal);
      gltScaleVector(vNormal, fLength); 
    }
      
    //**********************************************************
    void gltScaleVector(GLTVector3 vVector, const GLfloat fScale)
    { 
      vVector[0] *= fScale; vVector[1] *= fScale; vVector[2] *= fScale; 
    }
      
    //**********************************************************
    GLfloat gltGetVectorLength(const GLTVector3 vVector)
    { 
      return (GLfloat)sqrt(gltGetVectorLengthSqrd(vVector)); 
    }
      
    //**********************************************************
    GLfloat gltGetVectorLengthSqrd(const GLTVector3 vVector)
    { 
      return (vVector[0]*vVector[0]) + (vVector[1]*vVector[1]) + (vVector[2]*vVector[2]); 
    }
      
    //**********************************************************
    void gltInitFrame(GLTFrame *pFrame)
    {
      pFrame->vLocation[0] = 0.0f;
      pFrame->vLocation[1] = 0.0f;
      pFrame->vLocation[2] = 0.0f;
      
      pFrame->vUp[0] = 0.0f;
      pFrame->vUp[1] = 1.0f;
      pFrame->vUp[2] = 0.0f;
      
      pFrame->vForward[0] = 0.0f;
      pFrame->vForward[1] = 0.0f;
      pFrame->vForward[2] = -1.0f;
    }
      
    //**********************************************************
    void gltMoveFrameForward(GLTFrame *pFrame, GLfloat fStep)
    {
      pFrame->vLocation[0] += pFrame->vForward[0] * fStep;
      pFrame->vLocation[1] += pFrame->vForward[1] * fStep;
      pFrame->vLocation[2] += pFrame->vForward[2] * fStep;
    }
      
    //**********************************************************
    void gltRotateFrameLocalY(GLTFrame *pFrame, GLfloat fAngle)
    {
      GLTMatrix mRotation;
      GLTVector3 vNewForward;
      
      gltRotationMatrix((float)gltDegToRad(fAngle), 0.0f, 1.0f, 0.0f, mRotation);
      gltRotationMatrix(fAngle, pFrame->vUp[0], pFrame->vUp[1], pFrame->vUp[2], mRotation);
      
      gltRotateVector(pFrame->vForward, mRotation, vNewForward);
      memcpy(pFrame->vForward, vNewForward, sizeof(GLTVector3));
    }
      
    //**********************************************************
    // Создание матрицы поворота 4x4
    void gltRotationMatrix(float angle, float x, float y, float z, GLTMatrix mMatrix)
    {
      float vecLength, sinSave, cosSave, oneMinusCos;
      float xx, yy, zz, xy, yz, zx, xs, ys, zs;
      
      if(x == 0.0f  y == 0.0f  z == 0.0f)
      {
        gltLoadIdentityMatrix(mMatrix);
        return;
      }
      
      // Нормализуем вектор
      vecLength = (float)sqrt( x*x + y*y + z*z );
      x /= vecLength;
      y /= vecLength;
      z /= vecLength;
      
      sinSave = (float)sin(angle);
      cosSave = (float)cos(angle);
      oneMinusCos = 1.0f - cosSave;
      
      xx = x * x;
      yy = y * y;
      zz = z * z;
      xy = x * y;
      yz = y * z;
      zx = z * x;
      xs = x * sinSave;
      ys = y * sinSave;
      zs = z * sinSave;
      
      mMatrix[0] = (oneMinusCos * xx) + cosSave;
      mMatrix[4] = (oneMinusCos * xy) - zs;
      mMatrix[8] = (oneMinusCos * zx) + ys;
      mMatrix[12] = 0.0f;
      
      mMatrix[1] = (oneMinusCos * xy) + zs;
      mMatrix[5] = (oneMinusCos * yy) + cosSave;
      mMatrix[9] = (oneMinusCos * yz) - xs;
      mMatrix[13] = 0.0f;
      
      mMatrix[2] = (oneMinusCos * zx) - ys;
      mMatrix[6] = (oneMinusCos * yz) + xs;
      mMatrix[10] = (oneMinusCos * zz) + cosSave;
      mMatrix[14] = 0.0f;
      
      mMatrix[3] = 0.0f;
      mMatrix[7] = 0.0f;
      mMatrix[11] = 0.0f;
      mMatrix[15] = 1.0f;
    }
      
    //**********************************************************
    void gltRotateVector(const GLTVector3 vSrcVector, const GLTMatrix mMatrix, GLTVector3 vOut)
    {
      vOut[0] = mMatrix[0] * vSrcVector[0] + mMatrix[4] * vSrcVector[1] + mMatrix[8] *  vSrcVector[2];
      vOut[1] = mMatrix[1] * vSrcVector[0] + mMatrix[5] * vSrcVector[1] + mMatrix[9] *  vSrcVector[2];
      vOut[2] = mMatrix[2] * vSrcVector[0] + mMatrix[6] * vSrcVector[1] + mMatrix[10] * vSrcVector[2];      
    }
      
    //**********************************************************
    void gltVectorCrossProduct(const GLTVector3 vU, const GLTVector3 vV, GLTVector3 vResult)
    {
      vResult[0] = vU[1]*vV[2] - vV[1]*vU[2];
      vResult[1] = -vU[0]*vV[2] + vV[0]*vU[2];
      vResult[2] = vU[0]*vV[1] - vV[0]*vU[1];
    }
      
    //**********************************************************
    // Загрузка матрицы как единичной
    void gltLoadIdentityMatrix(GLTMatrix m)
    {
      static GLTMatrix identity = { 1.0f, 0.0f, 0.0f, 0.0f,
                                     0.0f, 1.0f, 0.0f, 0.0f,
                                     0.0f, 0.0f, 1.0f, 0.0f,
                                     0.0f, 0.0f, 0.0f, 1.0f };
      
      memcpy(m, identity, sizeof(GLTMatrix));
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupSphereWorld(void)
    {
      gltInitFrame(frameCamera);  // Инициализируем камеру
      
      // Генерируем центры 50 сфер случайным образом
      // в плоскости x0z внутри рамки от -20 до 20
      for(int iSphere = 0; iSphere < NUM_SPHERES; iSphere++)
      {
        gltInitFramespheres[iSphere]);    // Инициализируем фрейм
      
        // На плоскости x0z размещаем случайно центры
        // сфер в диапазоне от -20 до 20 с шагом .1
        spheres[iSphere].vLocation[0] = (float)((rand() % 400) - 200) * 0.1f;
        spheres[iSphere].vLocation[1] = 0.0f; 
        spheres[iSphere].vLocation[2] = (float)((rand() % 400) - 200) * 0.1f;
      }
    }
  • Скорректируйте файл ProjectionMatrix.cpp для седьмого упражнения. В листинге выделены места внесения изменений
  • //**********************************************************
    // Подключение стандартного файла с библиотекой OpenGL
    #include "stdafx.h"
      
    //**********************************************************
    // Прототипы функций
    void ExecuteMenu(int);  // Контекстное меню первого уровня
    void TimerFunc(int);    // Обработчик события таймера
    void SetupRC(void);      // Начальные настройки OpenGL
    void SpecialKeys(int, int, int);  // Обработка нажатия клавиш
    void RenderScene(void);
    void ChangeSize(int, int);
      
    // Глобальная переменная выбранного варианта основного меню
    int choice = 1;
      
    // Глобальные переменные для создания вращения
    // в градусах
    GLfloat xRot = 0.0f;
    GLfloat yRot = 0.0f;
    GLint w, h; // Ширина и высота экрана
      
    //**********************************************************
    // Подключение файлов с упражнениями
    #include "Atom.h"      // Упражнение 1: "1) Простая модель атома"
    #include "Ortho.h"      // Упражнение 2: "2) Брусок в ортогональной проекции"  
    #include "Perspect.h"    // Упражнение 3: "3) Брусок в перспективной проекции"
    #include "AtomPerspect.h"  // Упражнение 4: "4) Модель атома в перспективе"
    #include "Solar.h"      // Упражнение 5: "5) Система \"Солнце-Земля-Луна\""
    #include "Transform.h"    // Упражнение 6: "6) Каркасный тор - своя матрица" 
    #include "SphereWorld.h"  // Упражнение 7: "7) Фантастический мир со сферами"
      
    //**********************************************************
    // Функция обратного вызова обработки выбора пользователя
    void ExecuteMenu(int choice)
    {
      // Сбрасываем углы вращения прежнего варианта
      xRot = yRot = 0;
      
      // Запоминаем выбор в глобальную переменную
      ::choice = choice; 
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(w, h);
          break;
        case 2:
          ChangeSizeOrtho(w, h);
          break;
        case 3:
          ChangeSizePerspect(w, h);
          break;
        case 4:
          ChangeSizeAtomPerspect(w, h);
          break;
        case 5:
          ChangeSizeSolar(w, h);
          break;
        case 6:
          ChangeSizeTransform(w, h);
          break;
        case 7:
          ChangeSizeSphereWorld(w, h);
          break;
      }
      
      // Вызвать принудительно визуализацию
      glutPostRedisplay();
    }
      
    //**********************************************************
    // Функция обратного вызова для рисования сцены
    void RenderScene(void)
    {
      switch(::choice)
      {
        case 1:
          SetupLight(false);
          RenderSceneAtom();
          break;
        case 2:
          SetupLight(true);
          RenderSceneOrtho();
          break;
        case 3:
          SetupLight(true);
          RenderScenePerspect();
          break;
        case 4:
          SetupLight(false);
          RenderSceneAtomPerspect();
          break;
        case 5:
          SetupLightSolar(true);
          RenderSceneSolar();
          break;
        case 6:
          SetupLight(false);
          RenderSceneTransform();
          break;
        case 7:
          SetupLight(false);
          RenderSceneSphereWorld();
          break;
      }
    }
      
    //**********************************************************
    // Вызывается библиотекой GLUT при изменении размеров окна
    void ChangeSize(int width, int height)
    {  
      w = width;
      h = height;
      
      switch(::choice)
      {
        case 1:
          ChangeSizeAtom(width, height);
          break;
        case 2:
          ChangeSizeOrtho(width, height);
          break;
        case 3:
          ChangeSizePerspect(width, height);
          break;
        case 4:
          ChangeSizeAtomPerspect(width, height);
          break;
        case 5:
          ChangeSizeSolar(width, height);
          break;
        case 6:
          ChangeSizeTransform(width, height);
          break;
        case 7:
          ChangeSizeSphereWorld(width, height);
          break;
      }
    }
      
    //**********************************************************
    // Обработчик события таймера
    void TimerFunc(int value)
    {
      glutPostRedisplay(); // Перерисовка сцены
      
      switch(::choice)
      {
        case 6:
          glutTimerFunc(33, TimerFunc, 1);  // Заряжаем новый таймер
          break;
        case 7:
          glutTimerFunc(3, TimerFunc, 1);    // Заряжаем новый таймер
          break;
        default:
          glutTimerFunc(100, TimerFunc, 1);  // Заряжаем новый таймер
      }
    }
      
    //**********************************************************
    // Устанавливается состояние инициализации
    void SetupRC(void)
    {
      glClearColor(0.0F, 0.0F, 0.0F, 1.0F);// Фон черный непрозрачный
      
      glEnable(GL_DEPTH_TEST);  // Включили проверку глубины
      glFrontFace(GL_CCW);    // Лицевыми будем считать те грани, вершины
                    // которых обходятся против часовой стрелки
      glEnable(GL_CULL_FACE);    // Включили режим отсечения сторон
      
      SetupSphereWorld();      // Формируем сферы для упражнения 7
    }
      
    //**********************************************************
    // Управление с клавиатуры стрелками для
    // задания новых значений матрицы поворота
    void SpecialKeys(int key, int x, int y)
    {
      switch(::choice)
      {
        default:
          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;
      
          break;
        case 7:
          SpecialKeysSphereWorld(key);
          break;
      }
       
      xRot = (GLfloat)((const int)xRot % 360);
      yRot = (GLfloat)((const int)yRot % 360);
      
      // Вызвать принудительно визуализацию с помощью RenderScene()
      glutPostRedisplay();
    }
      
    //**********************************************************
    void main(int argc, char* argv[])
    {
      glutInit(argc, argv);
      // Двойная буферизация, цветовая модель RGB, буфер глубины
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
      glutInitWindowSize(300, 300);    // Начальные размеры окна
      glutCreateWindow("Матричные преобразования");  // Заголовок окна
      glutDisplayFunc(RenderScene);  // Обновление сцены при разрушении окна
      glutReshapeFunc(ChangeSize);  // При изменении размера окна
      glutTimerFunc(100, TimerFunc, 1);  // Создали таймер
      SetupRC();
      glutSpecialFunc(SpecialKeys);    // Для управления с клавиатуры
        
      // Создание меню и добавление опций выбора
      glutCreateMenu(ExecuteMenu);
      glutAddMenuEntry("1) Простая модель атома", 1);
      glutAddMenuEntry("2) Брусок в ортогональной проекции", 2);
      glutAddMenuEntry("3) Брусок в перспективной проекции", 3);
      glutAddMenuEntry("4) Модель атома в перспективе", 4);
      glutAddMenuEntry("5) Система \"Солнце-Земля-Луна\"", 5);
      glutAddMenuEntry("6) Каркасный тор - своя матрица", 6);
      glutAddMenuEntry("7) Фантастический мир со сферами", 7);
      
      glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
      // Конец создания меню
      
      glutMainLoop();  // Цикл сообщений графического окна
    }
  • Попробуйте разобраться с кодом, запустите упражнение, поуправляйте сценой с помощью стрелок клавиатуры
  • На динамической модели мгновенные снимки экрана мира сфер могут быть такими

  • Сдайте работу преподавателю
  • Вернуться к учебному плану