Войти
ПрограммированиеСтатьиГрафика

OpenGL на Qt 4. Это просто! (часть 2) (4 стр)

Автор:

Пример: наложение текстуры на шар

texture | OpenGL на Qt 4. Это просто! (часть 2)

Вот отличный пример: наложение текстуры на шар. Поверхность шара — это классический пример искривлённой поверхности. Нарисованная текстура тоже должна учитывать искривление так, чтобы можно было без особых затруднений вычислить текстурные координаты. К тому же поверхность шара замкнутая, поэтому нужно уточнить, как замыкать текстуру на такой поверхности. Сейчас разберём, как наложить текстуру поверхности Земли на шар.

map | OpenGL на Qt 4. Это просто! (часть 2) karkas | OpenGL на Qt 4. Это просто! (часть 2)

Текстура размером 512x256 позаимствована из CD, распространяющегося вместе с книгой: Евченко А.И. «OpenGL и DirectX. Программирование графики. Для профессионалов», и преобразована в формат .png. Прежде всего, нужно разобраться с геометрией фигуры и проекцией текстуры. В данном случае свяжем текстурную координату x с координатой вершины «фи» (угол фи), а текстурную координату y с координатой вершины «тэта» (угол тэта). Углы тэта и фи есть просто вторая и третья координаты в сферической системе координат; а первая координата для всех вершин одинакова и равна радиусу сферы (шара) R. Посмотрите на рисунки, на которых показано, как связать между собой текстурные координаты и координаты вершин в пространстве.

demo1 | OpenGL на Qt 4. Это просто! (часть 2) demo2 | OpenGL на Qt 4. Это просто! (часть 2)

Немного поразмыслив, мы определяем, что текстурные координаты равны:

x=phi/(2*pi); // x=s, pi=3.141593
y=(pi-theta)/pi; // y=t

Но сразу же возникают два важных вопроса:

1) Какие текстурные координаты дать полюсам: северному и южному? Ведь, вообще говоря, вся полоска верхних пикселей текстуры относится к одной вершине: северному полюсу. Та же самая ситуация и с южным полюсом.

2) Как замкнуть текстуру между её правой и левой границами? Ведь одна вершина может иметь только одну текстурную координату.

Ответ в обоих случаях: создать дополнительные вершины в местах сшивания поверхности фигуры. Таким образом, некоторые вершины будут совпадать по пространственным координатам, но отличаться текстурными координатами. Теперь вершин, соответствующих полюсам, станет значительно больше, но у каждой из них будет своя текстурная координата — свой пиксель в самой верхней или самой нижней полоске пикселей. Количество вершин на границе сшивания сферы увеличится в два раза; текстурные координаты одной части вершин будут лежать на правой границе текстуры, другой части — на левой. Т.е. как бы вершины продублируются и обеспечат стыковку текстуры на правой и левой границах. Посмотрите на иллюстрирующий рисунок:

demo3 | OpenGL на Qt 4. Это просто! (часть 2)

Вот возможный вариант, как процедурно определить массивы вершин, текстурных координат и индексов вершин для рассматриваемой задачи:

//...

// глобальные переменные
const GLfloat pi=3.141593, k=pi/180; 
const GLuint np=36; // число частей, на которое делится полуокружность
const GLfloat step=pi/np; // шаг изменения углов

//...

QVector<GLfloat> vecVertices; // вектор вершин
QVector<GLfloat> vecTextures; // вектор текстурных координат
QVector<GLuint> vecIndices; // вектор индексов вершин

//...

void Scene3D::getVerTexArrays() // определить массив вершин и массив текстурных координат
{
   GLfloat R=0.75f; // радиус сферы
   GLfloat phi, theta; // углы фи и тэта

   // двойной цикл по углам
   for (GLuint i=0; i < 2*np+1; i++)
   {
      phi=i*step; // изменение угла фи 

      for (GLuint j=0; j < np+1; j++)
      {
         theta=j*step; // изменение угла тэта

         if (j==0) // вершины "северный полюс"
         {
            if (i<2*np)
            {
               // добавляем в конец вектора:
               vecVertices.push_back(0.0f); // пространственная x-координата вершины
               vecVertices.push_back(0.0f); // пространственная y-координата вершины
               vecVertices.push_back(R);    // пространственная z-координата вершины

               vecTextures.push_back((phi+step/2)/(2*pi)); // текстурная x-координата вершины
               vecTextures.push_back(1.0f);                // текстурная y-координата вершины
            }
         }
         else
         {
            if (j<np) // вершины между северными и южными полюсами
            {
               if (i<2*np)
               {
                  vecVertices.push_back(R*sin(theta)*cos(phi));
                  vecVertices.push_back(R*sin(theta)*sin(phi));
                  vecVertices.push_back(R*cos(theta));

                  vecTextures.push_back(phi/(2*pi));
               }
               else
               {
                  vecVertices.push_back(R*sin(theta));
                  vecVertices.push_back(0.0f);
                  vecVertices.push_back(R*cos(theta));

                  vecTextures.push_back(1.0f);
               }
               vecTextures.push_back((pi-theta)/pi);
            }
            else // вершины "южный полюс"
            {
               if (i<2*np)
               {
                  vecVertices.push_back(0.0f);
                  vecVertices.push_back(0.0f);
                  vecVertices.push_back(-R);

                  vecTextures.push_back((phi+step/2)/(2*pi));
                  vecTextures.push_back(0.0f);
               }
            }
         }
      }
   }
}

void Scene3D::getIndexArray() // определить массив индексов
{
   for (GLuint i=0; i < 2*np; i++)
   {
      for (GLuint j=0; j < np; j++)
      {
         if (j==0)
         {
            vecIndices.push_back(i*(np+1));
            vecIndices.push_back(i*(np+1)+1);
            if (i<(2*np-1))
               vecIndices.push_back((i+1)*(np+1)+1);
            else
               vecIndices.push_back((i+1)*(np+1));
         }
         else
         {
            if (j<(np-1))
            {
               if (i<(2*np-1))
               {
                  vecIndices.push_back(i*(np+1)+j);
                  vecIndices.push_back((i+1)*(np+1)+(j+1));
                  vecIndices.push_back((i+1)*(np+1)+j);

                  vecIndices.push_back(i*(np+1)+j);
                  vecIndices.push_back(i*(np+1)+(j+1));
                  vecIndices.push_back((i+1)*(np+1)+(j+1));
               }
               else
               {
                  vecIndices.push_back(i*(np+1)+j);
                  vecIndices.push_back((i+1)*(np+1)+j);
                  vecIndices.push_back((i+1)*(np+1)+(j-1));

                  vecIndices.push_back(i*(np+1)+j);
                  vecIndices.push_back(i*(np+1)+(j+1));
                  vecIndices.push_back((i+1)*(np+1)+j);
               }
            }
            else
            {

               vecIndices.push_back((i+1)*(np+1)-1);
               if (i<(2*np-1))
                  vecIndices.push_back((i+2)*(np+1)-2);
               else
                  vecIndices.push_back((i+2)*(np+1)-3);
               vecIndices.push_back((i+1)*(np+1)-2);
            }
         }
      }
   }
}

//...

void Scene3D::drawFigure() // построить фигуру
{
   // изображаем фигуру в виде каркаса
   // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 
   // glColor4f(0.00f, 0.00f, 1.00f, 1.0f); // синий цвет

   // указываем, откуда нужно извлечь данные о массиве вершин
   glVertexPointer(3, GL_FLOAT, 0, vecVertices.begin());
   // указываем, откуда нужно извлечь данные о массиве текстурных координат
   glTexCoordPointer(2, GL_FLOAT, 0, vecTextures.begin());
   // строим поверхности
   glDrawElements(GL_TRIANGLES, vecIndices.size(), GL_UNSIGNED_INT, vecIndices.begin()); 
   // glPointSize(3.0f); // размер точки примерно в пикселях
   // glDrawArrays(GL_POINTS, 0, vecVertices.size()/3); // изобразить массив в виде точек
}

В примере мы воспользовались контейнерами-векторами QVector класса QVector (входит в состав модуля QtCore) вместо обычных массивов. QVector полностью аналогичен контейнеру-вектору vector из STL (Standard Template Library — стандартная библиотека шаблонов). Удобство контейнеров заключается в том, что изначально мы можем не указывать число его элементов и добавлять их в контейнер по мере необходимости. Функции для QVector совпадают по названию с аналогичными функциями для vector. Например, push_back() — добавить в конец контейнера;  begin() — вернуть указатель на начало контейнера; size() — вернуть число элементов контейнера; clear() — очистить контейнер и т.д.

Закомментируйте glTexCoordPointer() и glEnableClientState(GL_TEXTURE_COORD_ARRAY), или отключите последнюю с помощью glDisableClientState(GL_TEXTURE_COORD_ARRAY), и раскомментируйте последовательные команды glPolygonMode() и glColor4f() и увидите каркас, на который наложена текстура. При вычислениях массивов вершин иногда бывает полезно посмотреть на сами вершины, не заморачиваясь с массивом индексов. Для этого используйте glDrawArrays() вместо glDrawElements().

Рисуем два интерактивных октаэдра

lecture2 | OpenGL на Qt 4. Это просто! (часть 2)

Теперь мы рассмотрим листинг полной программы с анимацией и текстурами. Для этого мы создадим главное окно и меню (menu bar), чтобы управлять некоторыми процессами на сцене. Для большего эффекта мы сделаем интерактивную графику с помощью режима выбора OpenGL, благодаря которому сможем выбирать мышью трёхмерные объекты на сцене. В качестве трёхмерных объектов мы возьмём два октаэдра, наложим на каждый из них по разной текстуре и заставим их вращаться и перемещаться. (Для общего сведения, октаэдр — это такая фигура, которая имеет 6 вершин и строится из 8 (равносторонних) треугольников.) С помощью опций меню мы сможем менять текстуры октаэдров и останавливать или запускать таймер. Дополнительно мышью мы сможем выбирать октаэдры на сцене, чтобы остановить или возобновить их движения, т.е. поставлена такая задача: щёлкаем мышкой по фигуре и фигура должна остановиться; щёлкаем ещё раз и она опять должна двигаться и т.д. Для этого используется режим выбора и буфер выбора OpenGL. Программа тестировалась на Windows XP + Qt 4.7.1 + OpenGL 2.0 и openSUSE 11.2 + Qt 4.5 + OpenGL 1.5. Листинг состоит из семи файлов: scene3D.h, scene3D.cpp, mainwindow.h, mainwindow.cpp и main.cpp, а также файла ресурсов pictures.qrc и файла-проекта lecture2.pro.

scene3D.h

#ifndef SCENE3D_H 
#define SCENE3D_H

#include <QGLWidget> // подключаем класс QGLWidget

class Scene3D : public QGLWidget // класс Scene3D наследует встроенный класс QGLWidget
{ 
   Q_OBJECT          // макрос, который нужно использовать при работе с сигналами и слотами

   private:   
      GLfloat ratio; // отношение высоты окна виджета к его ширине

      // для первой фигуры
      GLfloat xRot1; // угол поворота вокруг оси X
      GLfloat yRot1; // угол поворота вокруг оси Y
      GLfloat zRot1; // угол поворота вокруг оси Z
      GLfloat zTra1; // величина трансляции по оси Z

      // для второй фигуры
      GLfloat xRot2; // угол поворота вокруг оси X
      GLfloat yRot2; // угол поворота вокруг оси Y
      GLfloat zRot2; // угол поворота вокруг оси Z
      GLfloat zTra2; // величина трансляции по оси Z

      QTimer *timer; // декларируем таймер

      void getVerTexArrays(); // определить массив вершин
      void getIndexArray();   // определить массив индексов вершин
      void genTextures();     // создать текстуры  
      void drawFigure();      // построить фигуру

      void selectFigures(QPoint mp); // выбрать фигуру      
        
   protected:
      // метод для проведения инициализаций, связанных с OpenGL:
      void initializeGL();   
      // метод вызывается при изменении размеров окна виджета                 
      void resizeGL(int nWidth, int nHeight); 
      // метод, чтобы заново перерисовать содержимое виджета
      void paintGL();            
      // метод обработки события мыши при нажатии клавиши мыши             
      void mousePressEvent(QMouseEvent* pe);   

   private slots: // слоты
      void change(); // изменение углов поворота и величины трансляции
      void changeTex(); // поменять текстуры местами
      void stopTmr(); // остановить таймер
      void startTmr(); // запустить таймер

   public: 
      Scene3D(QWidget* parent = 0); // конструктор класса
      ~Scene3D();                   // деструктор
}; 
#endif 

В заголовочном файле scene3D.h мы декларируем наш класс Scene3D. В функции selectFigures() будет использоваться режим выбора. Параметром этой функции является объект типа QPoint, который хранит в себе x и y координату экрана. В классе мы декларируем четыре слота, которые будут выполняться при эмитировании связанных с ними сигналов.

Страницы: 1 2 3 4 5 6 Следующая »

#3D, #графика, #OpenGL, #Qt, #Qt4

10 августа 2011 (Обновление: 2 янв. 2013)

Комментарии [17]