Небольшое введение в OpenGL — Архив WASM.RU

Все статьи

Небольшое введение в OpenGL — Архив WASM.RU

Я думаю, вы, как и я, смотрели на эти OpenGL'ные демки, как двигаются по экрану полигоны, меняются различные эффекты и так далее. Также, вполне вероятно, вы не очень сильны в математике и не хотите самостоятельно выводить все эти математические синусоидальные процедуры. OpenGL - это классная библиотека, которая позволит вам создать 3D-вселенную очень быстро, двигать ее и наложить серию спецэффектов, используя простую концепцию API.

В наши дни есть два основных вида программирования под OpenGL, в зависимости от операционной системы, которую вы используете. Обычный путь состоит в использовании glut-библиотеки, чтобы задавать анимацию, которая совместима с linux, win32, sgx-станциями и т.д... Другой путь - эт чистый win32. В последнем случае используются обратный вызов win32-клинта, чтобы переключиться на следующее изображение. Как бы то ни было, результат одинаков. Мы будем рассматривать программирование под win32. Поэтому для использования OpenGL вам сначала нужно инициализровать процедуру окна.

1 Процедура окна

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

1.1 Класс окна

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

        mov     edx,esp                 ; сохраняем стек

        push    offset classname
        push    0                       ; меню окна
        push    0                       ; цвет бэкграунда (черный)
        push    0                       ; наш курсор
        push    0                       ; наша иконка

        push    edx                     ; сохраняем стек
        push    0
        call    GetModuleHandleA        ; загружаем текущую программу
        pop     edx

        push    eax                     ; равен hinstance

        push    0                       ; дополнительно резервирующаяся память
        push    0                       ; дополнительный размер структуры
        push    offset Window_proc      ; адрес процедуры окна
        push    0                       ; we don't care of style

        mov     eax,esp

        push    edx
        push    eax
        call    RegisterClassA          ; регистрируем класс
        pop     esp                     ; восстанавливаем стек

Как только вы зарегистрировали класс, вам необходимо создать свою процедуру окна

1.2 Создание окна

Это просто: один вызов функции API

        push    0

        call    GetModuleHandleA
        push    eax

        push    0
        push    0
        push    480
        push    640
        push    0
        push    0
        push    090000000h                      ; WS_VISIBLE
        push    offset windowname
        push    offset classname
        push    0400000h                        ; WS_EX_CLIENTEDGE

        call    CreateWindowExA

Это правильно, но если вы вызовите его таким образом, приложение может повиснуть, почему? Потому что мы еще не создали процедуру окна.

1.3 Процедура окна

Процедура окна получает 4 аргумента. Первый - это хэндл окна. Второй - это сообщение. Третий - это нижний параметр, а четвертый - это верхний параметр.

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

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

        lParam          equ     16
        wParam          equ     12
        uMsg            equ     8
        hWnd            equ     4

window_proc:

        cmp     dword ptr [esp+uMsg], 0Fh                 ; WM_PAINT
        jne     not_event1

        ... здесь какой-то код по обработке WM_PAINT

not_event1:

        ... здесь вы можете поместить обработку других событий

not_event99:

        cmp     dword ptr [esp+uMsg], WM_CLOSE
        jne     not_quiting

        push    dword ptr [esp+lParam]
        push    dword ptr [esp+4+wParam]
        push    dword ptr [esp+4+4+uMsg]
        push    dword ptr [esp+4+4+4+hWnd]
        call    DefWindowProc

        ret     16

1.4 Обработка события

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

        mov     edx,esp
        sub     esp,44
        mov     ebx,esp

        push    ebx
        push    eax

loopit:

        push    ebx

        push    0
        push    0
        push    eax
        push    ebx
        call    GetMessageA

        pop     ebx

        cmp     eax,0
        je      goodbye

        push    ebx
        push    ebx
        call    TranslateMessage
        call    DispatchMessageA

        pop     eax
        pop     ebx

        push    ebx
        push    eax
        jmp     loopit

goodbye:

program_error:

        add     esp,44+4+4

        push    0
        call    ExitProcess

Как только ваша процедура окна инициализирована, вам требуется синхронизировать ее с OpenGL.

2 OpenGL

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

2.1 При создании окна

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

        mov     ecx,40
        sub     esp,ecx
        mov     edi,esp
        push    edi

        xor     eax,eax
        repz    stosb

        pop     edi

        mov     word ptr [edi], 40              ; размер структуры
        mov     word ptr [edi+2], 1             ; заполняем версию
        mov     dword ptr [edi+4], 37           ; PFD_SUPPORT_OPENGL
                                                ; PFD_DRAW_TO_WINDOW
                                                ; PFD_DOUBLE_BUFFER
        mov     byte ptr [edi+9], 16            ; биты цвета
        mov     byte ptr [edi+17], 16           ; биты глубины

        push    edi
        push    dword ptr [currentDC]           ; загружаем DC
        call    ChoosePixelFormat

Если результат равен 0, вызов API не удавлся, и пользователь не может отобразить разрешение. В противном случае вы можете напрямую установить пиксельный формат.

        push    edi
        push    eax
        push    dword ptr [currentDC]           ; загружаем DC
        call    SetPixelFormat

тогда это равно пиксельному формату. Получить текущий DC очень просто:

        push    dword ptr [esp+hWnd]
        call    GetDC

и eax равен текущему DC. Нам также требуется создать контекст OpenGL из этого DC.

        push    eax
        call    wglCreateContext

Если eax == 0, то вызов не удался и лучшее, что мы можем сделать - это послать WM_CLOSE, чтобы благополучно закрыть приложение.

Далее мы синхронизируем оба контекста:

        push    eax             ; контекст OpenGL
        push    ebx             ; текущий DC
        call    wglMakeCurrent

Сделано, OpenGL был проинициализирован, теперь вы можете наложить ряд эффектов, но прежде, чем сделать это, вы должны из разрешить. Делается это так:

        push    0B57h           ; CL_COLOR_MATERIAL
        call    glEnable        ; damn easy isn't ?

Вы также можете попробовать другие значения: GL_DEPTH, G_LIGHTING.

2.2 Как только был изменен размер окна

Когда Windows закончила с инициализацией, она шлет сообщение WM_SIZE, которое озанчает, что настало время пофиксить все, связанное с графикой. Разрешение экрана посылается в lParam. Поэтому сначала устанавливаем порт просмотра.

        mov     ecx,dword ptr [esp+lParam]
        movzx   edx,cx
        shr     ecx,16

        push    ecx
        push    edx                             ; длина
        push    0
        push    0                               ; начало
        call    glViewport                      ; определяем порт просмотра

После этого нам нужно определить модель матрицы, я не буду давать урок математики о матрицах и трансформациях, но как обычно это очень легко:

        push    1701h           ; GL_PROJECTION
        call    glMatrixMode

После этого мы вызываем функцию glLoadIndentity:

        call    glLoadIdentity

Затем мы вызываем библиотеку GL-эффектов, чтобы добавить перспективу порту просмотра. Эта функция принимает только числа двойной точности. Компилер может инициализировать такое число так:

double1         dq      1.0f

А вот макрос, облегчающий загонку таких чисел в стек:

pushdl  macro   double1

        fld     qword ptr [&double1&]
        sub     esp,8
        fstp    qword ptr [esp]

endm

Теперь передадим необходимые параметры порту просмотра.

        pushdl  farclip
        pushdl  nearclip
        pushdl  XYration
        pushdl  FovAngle

        call    gluPerpective

Перспектива - это необходимый аспект нашего 3D-мира. Перспектива бывает разных видов, например как при обзоре через камеру наблюдения или обзор сцены в формате 16/9.

И, опционально, вы можете установить модель теней для сложной 3D-сцены, установите ее в GL_F:AT, это может ускорить рендеринг изображения.

                push    01D01h
                call    glShadeModel

Вот и все с этим.

3 Пришло время показать ваши умения демокодера

Windows говорит процедуре окна, что настало время обновить текущее изображение с помощью сообщения WM_PAINT.

Первое, что нужно сделать - это обновить текущее изображение с помощью сообщения WM_PAINT.

        push    04100h                  ; GL_COLOR_BUFFER_BIT или
        call    glClear                 ; GL_DEPTH_BUFFER_BIT

        push    GL_MODEL_VIEW
        call    glMatrixMode

        call    glLoadIdentity

Теперь мы можем установить порт просмотра в виде камеры. У этой камеры есть 9 операндов.

        3 первых - это начало камеры
        3 следующих - это назанчение камеры
        3 последних - это координаты верхнего вектора камеры, ее вращение

Эти операнды также двойной точности, поэтому используйте pushdl, чтобы использовать эту функцию.

        pushf...
        pushf...
        call    gluLookAt

А теперь надо отрисовать окружение. Можно сделать много вещей: установить свет, тени, прозрачность, искажения и так далее.

Давайте сделаем такую простую вещь:

        push    GL_POINTS
        call    glBegin

        push    0.5
        push    0.5
        push    0.5
        call    glVertex3f

        call    glEnd

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

Когда окружение построено, вы можете закончить это так:

        push    DC
        call    SwapBuffers

И буфер окажется на экране.

4 Небольшое приложение

Часто люди удивляются, почему их продукция отображается на экране очень странно. Причина проста: OpenGL не интересуется другими элементами, расположенными на экране, поэтому н может перерисовать их... Вы можете установить тест глубины во время создания окна:

        push    0B71h           ; DEPTH_TEST
        call    glEnable        ;

Некоторые люди хотят полноэкранное разрешение, это можно сделать в два шага, меняя разрешение дисплея:

        mov     ecx,148
        mov     esp,ecx
        mov     edi,esp
        xor     eax,eax

        repz    stosb

        mov     dword ptr [esp+36],148          ; dmSize
        mov     dword ptr [esp+104],16          ; dmBitsPerPixel
        mov     dword ptr [esp+108],640         ; dmWidth
        mov     dword ptr [esp+112], 480        ; dmHeight
        mov     dword ptr [esp+40], 1C0000h
        ; DM_BITSPERPEL DM_PELSWIDTH DM_PELSHEIGHT

        mov     edx,esp

        push    4                       ; CDS_FULLSCREEN
        push    edx                     ; dmScreenSettings
        call    ChangeDisplay

        add     esp,148

Не правда ли, это очень легко?

2002-2013 (c) wasm.ru