Хождение по мукам

или

Юзаем OpenGL

 

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

            Считается, что программировать 3D сложно и требует специальных математических знаний. Это не совсем так. Да, действительно нужно знать хотя бы азы высшей математики, но освоить эти азы можно за 15 минут. А вот без чего здесь не обойтись, так это без объемного мышления. Если ты всегда мог легко представить как будет выглядеть пирамида стоящая на сфере, то все в порядке. Я знаю нескольких людей, которые совсем не могут представить объекты в пространстве. С этим ничего не поделать, абстрактное мышление есть не у всех.

            В этой статье я не буду вдаваться в теорию. Кого интересует теория, читайте книжки. Я просто подробно прокоментирую один из примеров OpenGL приложения на Дельфи.

            И так, приступим.

            Что нам понадобится:

                        Комп с 3D-акселератором – 1 штука.(лучше NV не ниже TNT2)

                        Delphi любой версии, установленный – штука.

                        Внимания – четочку.

                        Терпение – много J.

            Запускаем Дельфи.(в моем случае это шестая версия). Дельфи сразу создаст новый проект.

            Теперь по порядку:

Сначала нам нужно подключить в юзесах(uses) OpenGL.(просто допиши там opengl).

Теперь добавим необходимые переменные. В public класса Tform1 добавим:

     DC:HDC;   // это ссылка на контекст устройства

     HRC:HGLRC;  // ссылка на контекст воспроизведения

                В var добавим переменную k типа integer

Теперь добавим процедуру SetDCPixelFormat , она нам понадобится для задания формата пикселя. Вот как она выглядит:

 

Procedure SetDCPixelFormat(hdc:HDC);

var pfd:TPixelFormatDescriptor;

    nPixelFormat: Integer;

begin

    FillChar(pfd,SizeOf(pfd),0);

    pfd.dwFlags  := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;

    nPixelFormat:=ChoosePixelFormat(hdc,@pfd);

    SetPixelFormat(hdc,nPixelFormat,@pfd);

end;

 

Эту процедуру я целиком взял из книги М.Краснова «OpenGL. Графика в проектах Delphi»

Единственное на что здесь стоит обратить внимание, так это на задание флагов:

PFD_DRAW_TO_WINDOW // комментарии излишни

PFD_SUPPORT_OPENGL // включаем хардварную поддержку OpenGL

и самое главное

PFD_DOUBLEBUFFER // двойной буфер нужен нам для правильной смены кадров.(отрисовка кадра в памяти и быстрая замена им текущего)

Теперь перейдем к заданию стандартных процедур работы оконного приложения.

В событии OnCreate создадим процедуру по умолчанию, и приведем ее к следующему виду:

procedure TForm1.FormCreate(Sender: TObject);

begin

    DC:=GetDC(handle);                                  // получаем ссылку на контекст устройства

    SetDCPixelFormat(DC);                             // устанавливаем формат пикселя

    hrc:= wglCreateContext(DC);                   // контекст воспроизведения

    wglMakeCurrent(DC,HRC);                       // устанавливаем текущий контекст

    glEnable(GL_DEPTH_TEST);                    //включаем тест глубины (для правельного отобродения объектов по оси Z)

    glEnable (GL_COLOR_MATERIAL);        // тут все понятно

    glEnable(GL_LIGHTING);                          // разрешаем освещение

    glEnable(GL_LIGHT0);                               // Включаем источник света за именем GL_LIGHT0(все настройки по умолчанию)

    glEnable(GL_NORMALIZE);                      // нужно для нормализации векторов нормалей(я их криво задал, OGL сам пересчитает)

    glNewList(1,GL_COMPILE);                      // очень важный момент. Подготовка экранных списков. Чуть позже об этом подробнее

       glBegin(GL_TRIANGLES);                      // открываем блок рисования (в донном случае треугольников)

           glNormal3f(   0,  -1,   0);                        // объявляем нормаль. Чуть ниже подробнее

           glVertex3f(-0.5,-0.5,-0.5);                     // 1я вершина

           glVertex3f(   0,-0.5, 0.5);                       // 2я вершина

           glVertex3f( 0.5,-0.5,-0.5);                     // 3я вершина

 

           glNormal3f(-0.5, 0.5, 0.5);                    //Следующая нормаль для следующего треугольника

           glVertex3f(-0.5,-0.5,-0.5);

           glVertex3f(   0,-0.5, 0.5);

           glVertex3f(   0, 0.5,   0);

 

           glNormal3f( 0.5, 0.5, 0.5);

           glVertex3f(   0,-0.5, 0.5);

           glVertex3f( 0.5,-0.5,-0.5);

           glVertex3f(   0, 0.5,   0);

 

           glNormal3f(   0, 0.5,-0.5);

           glVertex3f(-0.5,-0.5,-0.5);

           glVertex3f(   0, 0.5,   0);

           glVertex3f( 0.5,-0.5,-0.5);

       glEnd;

    glEndList;

end;

 

Ну вот теперь по подробнее. Нормаль это вектор, как правило, перпендикулярный к поверхности. Но в 3D нормаль может быть и не перпендикулярной. Это связано с тем, что нормали используются для расчета освещенности полигона. Посмотрим на иллюстрации:

             

На рисунке конус, у которого нормали строго перпендикулярны к полигонам. Такие нормали назаваются фасетными(faset). 

А теперь посмотрите на тот же конус, но с не перпендикулярными нормалями:

Так модель выглядит сглаженной. И такой тип нормалей называется гладкими.(SmoothNormals) Как видно из иллюстраций, нормали присваиваются вершинам, а не полигону. И запись вида:

glNormal3f( 0.5, 0.5, 0.5);

glVertex3f(   0,-0.5, 0.5);

glVertex3f( 0.5,-0.5,-0.5);

glVertex3f(   0, 0.5,   0);

говорит о присвоении всем вершинам одного треугольника одной и той же нормали.

Как рассчитывать нормали я говорить не буду, это можно прочесть в любой книге по 3D.

Теперь о экранных списках(или дисплейных). Экранные списки представляют собой последовательность команд, запоминаемых для последующего вызова. Они подобны подпрограммам и являются удобным средством для кодирования больших систем со сложной иерархией. Использование экранных списков приводит к более быстрой работе приложения и повышает читабельность кода. Формат работы экранных списков таков:

glNewList(1,GL_COMPILE);

первый параметр это идентификатор экранного списка(сколько максимально может быть экранных списков я не знаю, это в доке не написано)

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

////////////

……

все что угодно

///////////

glEndList;  // конец списка.

Теперь в событии OnResize созданим еще одну процедурку.

procedure TForm1.FormResize(Sender: TObject);

begin

    glViewport(0, 0, ClientWidth, ClientHeight );                       // зона видимости(в данном случае все окно)

    glMatrixMode(GL_PROJECTION);                                         // Делаем текущей матрицу проекции

    glLoadIdentity;                                                                             // загружаем единичную матрицу

    gluPerspective(30,ClientWidth/ClientHeight,1,35);            // задаем перспективу

    glMatrixMode(GL_MODELVIEW);                                          // матрица вида

    glLoadIdentity;

end;

 

Если что то не понятно, то читайте книгу все того же М.Красного.

И последняя, самая главная процедура, подвяжем ее на системное событие WM_PAINT.

Для этого в public класса Tform1 создадим процедуру следующего вида:

procedure Render(var Msg: TWMPaint); message WM_PAINT;

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

procedure TForm1.Render(var Msg: TWMPaint);

begin

    k:=k+1;                                                                                                                         // для анимации

    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);               // Очищаем буфер глубины и содержимое фрейма

    glPushMatrix();                                                                                                           // сохраняем состояние матрицы

        glTranslatef (0, 0, -4);                                                                                             // немного сдвигаем по оси Z(глубина)

        glDisable(GL_LIGHTING);                                                                                    // Отключам освещение, это нужно для правильного отоброжения осей.

        glBegin(GL_LINES);                                                                                               // это блок отрисовки осей

            glColor3f(1,0,0);                                                                                                  // задание цвета RGB

            glVertex3f(-1,-1,0);                                                                                             

            glVertex3f(-0.5,-1,0);

            glColor3f(0,1,0);

            glVertex3f(-1,-1,0);

            glVertex3f(-1,-0.5,0);

            glColor3f(0,0,1);

            glVertex3f(-1,-1,0);

            glVertex3f(-1,-1,-1);

        glEnd;

        glEnable(GL_LIGHTING);                                                                                     // Включаем освещение

        glRotate (k,0,1,0);                                                                                                   // поворот на угол k вокруг вектора 0,1,0

        glRotate (k,1,0,0);

        glRotate (k,0,0,1);

        glColor3f(0,1,0);

        glCallList(1);                                                                                                            // вызываем заранее созданный экранный список

        glRotate (180,1,0,0);

        glTranslatef (-0.5,0.5,0.25);

        glColor3f(1,0,0);

        glCallList(1);

        glTranslatef (1,0,0);

        glColor3f(0,0,1);

        glCallList(1);

    glPopMatrix();                                                                                                             // восстанавливаем состояние матрицы

    SwapBuffers(DC);                                                                                                        // отображаем на экран наше художество

end;

 

Теперь, после компиляции должно получится что то похожее на это:

 

Если что то не получилось, то качай исходник.

Вывод из запоя за 1 день Звоните: вывод из запоя.