Я думаю, вы, как и я, смотрели на эти 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
Не правда ли, это очень легко?
[C] Star0, пер. Aquila