Небольшое введение в OpenGL

Дата публикации 27 июн 2002

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

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

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

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

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

1.1 Класс окна

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

Код (Text):
  1.  
  2.         mov     edx,esp                 ; сохраняем стек
  3.  
  4.         push    offset classname
  5.         push    0                       ; меню окна
  6.         push    0                       ; цвет бэкграунда (черный)
  7.         push    0                       ; наш курсор
  8.         push    0                       ; наша иконка
  9.  
  10.         push    edx                     ; сохраняем стек
  11.         push    0
  12.         call    GetModuleHandleA        ; загружаем текущую программу
  13.         pop     edx
  14.  
  15.         push    eax                     ; равен hinstance
  16.  
  17.         push    0                       ; дополнительно резервирующаяся память
  18.         push    0                       ; дополнительный размер структуры
  19.         push    offset Window_proc      ; адрес процедуры окна
  20.         push    0                       ; we don't care of style
  21.  
  22.         mov     eax,esp
  23.  
  24.         push    edx
  25.         push    eax
  26.         call    RegisterClassA          ; регистрируем класс
  27.         pop     esp                     ; восстанавливаем стек

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

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

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

Код (Text):
  1.  
  2.         push    0
  3.  
  4.         call    GetModuleHandleA
  5.         push    eax
  6.  
  7.         push    0
  8.         push    0
  9.         push    480
  10.         push    640
  11.         push    0
  12.         push    0
  13.         push    090000000h                      ; WS_VISIBLE
  14.         push    offset windowname
  15.         push    offset classname
  16.         push    0400000h                        ; WS_EX_CLIENTEDGE
  17.  
  18.         call    CreateWindowExA

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

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

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

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

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

Код (Text):
  1.  
  2.         lParam          equ     16
  3.         wParam          equ     12
  4.         uMsg            equ     8
  5.         hWnd            equ     4
  6.  
  7. window_proc:
  8.  
  9.         cmp     dword ptr [esp+uMsg], 0Fh                 ; WM_PAINT
  10.         jne     not_event1
  11.  
  12.         ... здесь какой-то код по обработке WM_PAINT
  13.  
  14. not_event1:
  15.  
  16.         ... здесь вы можете поместить обработку других событий
  17.  
  18. not_event99:
  19.  
  20.         cmp     dword ptr [esp+uMsg], WM_CLOSE
  21.         jne     not_quiting
  22.  
  23.         push    dword ptr [esp+lParam]
  24.         push    dword ptr [esp+4+wParam]
  25.         push    dword ptr [esp+4+4+uMsg]
  26.         push    dword ptr [esp+4+4+4+hWnd]
  27.         call    DefWindowProc
  28.  
  29.         ret     16

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

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

Код (Text):
  1.  
  2.         mov     edx,esp
  3.         sub     esp,44
  4.         mov     ebx,esp
  5.  
  6.         push    ebx
  7.         push    eax
  8.  
  9. loopit:
  10.  
  11.         push    ebx
  12.  
  13.         push    0
  14.         push    0
  15.         push    eax
  16.         push    ebx
  17.         call    GetMessageA
  18.  
  19.         pop     ebx
  20.  
  21.         cmp     eax,0
  22.         je      goodbye
  23.  
  24.         push    ebx
  25.         push    ebx
  26.         call    TranslateMessage
  27.         call    DispatchMessageA
  28.  
  29.         pop     eax
  30.         pop     ebx
  31.  
  32.         push    ebx
  33.         push    eax
  34.         jmp     loopit
  35.  
  36. goodbye:
  37.  
  38. program_error:
  39.  
  40.         add     esp,44+4+4
  41.  
  42.         push    0
  43.         call    ExitProcess

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

2 OpenGL

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

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

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

Код (Text):
  1.  
  2.         mov     ecx,40
  3.         sub     esp,ecx
  4.         mov     edi,esp
  5.         push    edi
  6.  
  7.         xor     eax,eax
  8.         repz    stosb
  9.  
  10.         pop     edi
  11.  
  12.         mov     word ptr [edi], 40              ; размер структуры
  13.         mov     word ptr [edi+2], 1             ; заполняем версию
  14.         mov     dword ptr [edi+4], 37           ; PFD_SUPPORT_OPENGL
  15.                                                 ; PFD_DRAW_TO_WINDOW
  16.                                                 ; PFD_DOUBLE_BUFFER
  17.         mov     byte ptr [edi+9], 16            ; биты цвета
  18.         mov     byte ptr [edi+17], 16           ; биты глубины
  19.  
  20.         push    edi
  21.         push    dword ptr [currentDC]           ; загружаем DC
  22.         call    ChoosePixelFormat

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

Код (Text):
  1.  
  2.         push    edi
  3.         push    eax
  4.         push    dword ptr [currentDC]           ; загружаем DC
  5.         call    SetPixelFormat

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

Код (Text):
  1.  
  2.         push    dword ptr [esp+hWnd]
  3.         call    GetDC

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

Код (Text):
  1.  
  2.         push    eax
  3.         call    wglCreateContext

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

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

Код (Text):
  1.  
  2.         push    eax             ; контекст OpenGL
  3.         push    ebx             ; текущий DC
  4.         call    wglMakeCurrent

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

Код (Text):
  1.  
  2.         push    0B57h           ; CL_COLOR_MATERIAL
  3.         call    glEnable        ; damn easy isn't ?

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

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

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

Код (Text):
  1.  
  2.         mov     ecx,dword ptr [esp+lParam]
  3.         movzx   edx,cx
  4.         shr     ecx,16
  5.  
  6.         push    ecx
  7.         push    edx                             ; длина
  8.         push    0
  9.         push    0                               ; начало
  10.         call    glViewport                      ; определяем порт просмотра

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

Код (Text):
  1.  
  2.         push    1701h           ; GL_PROJECTION
  3.         call    glMatrixMode

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

Код (Text):
  1.  
  2.         call    glLoadIdentity

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

Код (Text):
  1.  
  2. double1         dq      1.0f

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

Код (Text):
  1.  
  2. pushdl  macro   double1
  3.  
  4.         fld     qword ptr [&double1&]
  5.         sub     esp,8
  6.         fstp    qword ptr [esp]
  7.  
  8. endm

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

Код (Text):
  1.  
  2.         pushdl  farclip
  3.         pushdl  nearclip
  4.         pushdl  XYration
  5.         pushdl  FovAngle
  6.  
  7.         call    gluPerpective

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

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

Код (Text):
  1.  
  2.                 push    01D01h
  3.                 call    glShadeModel

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

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

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

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

Код (Text):
  1.  
  2.         push    04100h                  ; GL_COLOR_BUFFER_BIT или
  3.         call    glClear                 ; GL_DEPTH_BUFFER_BIT
  4.  
  5.         push    GL_MODEL_VIEW
  6.         call    glMatrixMode
  7.  
  8.         call    glLoadIdentity

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

Код (Text):
  1.  
  2.         3 первых - это начало камеры
  3.         3 следующих - это назанчение камеры
  4.         3 последних - это координаты верхнего вектора камеры, ее вращение

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

Код (Text):
  1.  
  2.         pushf...
  3.         pushf...
  4.         call    gluLookAt

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

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

Код (Text):
  1.  
  2.         push    GL_POINTS
  3.         call    glBegin
  4.  
  5.         push    0.5
  6.         push    0.5
  7.         push    0.5
  8.         call    glVertex3f
  9.  
  10.         call    glEnd

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

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

Код (Text):
  1.  
  2.         push    DC
  3.         call    SwapBuffers

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

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

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

Код (Text):
  1.  
  2.         push    0B71h           ; DEPTH_TEST
  3.         call    glEnable        ;

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

Код (Text):
  1.  
  2.         mov     ecx,148
  3.         mov     esp,ecx
  4.         mov     edi,esp
  5.         xor     eax,eax
  6.  
  7.         repz    stosb
  8.  
  9.         mov     dword ptr [esp+36],148          ; dmSize
  10.         mov     dword ptr [esp+104],16          ; dmBitsPerPixel
  11.         mov     dword ptr [esp+108],640         ; dmWidth
  12.         mov     dword ptr [esp+112], 480        ; dmHeight
  13.         mov     dword ptr [esp+40], 1C0000h
  14.         ; DM_BITSPERPEL DM_PELSWIDTH DM_PELSHEIGHT
  15.  
  16.         mov     edx,esp
  17.  
  18.         push    4                       ; CDS_FULLSCREEN
  19.         push    edx                     ; dmScreenSettings
  20.         call    ChangeDisplay
  21.  
  22.         add     esp,148

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


0 1.163
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532