FASM OpenGL tutorial

Дата публикации 26 мар 2005

FASM OpenGL tutorial — Архив WASM.RU

Глава 1: Бредисловие (вместо педисловия)

Привет всем (уважаемым читателям/телезрителям/"а также их родителям" и пр ;) Сразу хочу предупредить, что это моя первая статья, поэтому просьба не пинать меня ногами, никакого заме-е-ечательного Набоковского слога и живописных эпитетов вы тут не увидите :( Все выдержано в сухом "преподавательском" тоне без малейшего намека на "программерские" шутки про "юзеров", телепузиков, "Microsoft" и "г-на Била Гейтса" (а ровно и пауз на неопределенный срок фразой "я ушел бухать", так как я вовсе не "бухаю". Да да, не смейтесь, я спротЦмен ;) ) Разочаровавшись (многие уже потянулись к "Esc"/бутылке/соске) начнем собсен-н-но...

Глава 2: Немного истории

Как говорил товарищ Берлага, если приподнять завесу, то становится весьма очевидным что стандарт OpenGL разрабатывается фирмой Silicon Graphics (теперь SGI) с лета 1992 года. Этот программный интерфейс был во многом основан на библиотеке IRIS GL, поддерживаемой с 1982 года. Для развития нового интерфейса был организован консорциум The OpenGL Architecture Review Board (ARB). На данный момент OpenGL является стандартной библиотекой для всех 32 разрядных операционных систем (Unix, Mac, Windows). Ну и хватит на этом. В конце концов это вам не лезгинка, а твист... в смысле не "Большая Советская Энциклопедия".

Глава 3: Инструментарий

Сразу скажу что писать мы будем на ассемблере (вообще говоря, все следует писать на ассемблере ;) , а точнее на FlatAssembler (FASM) т.к. он очень быстрый, компактный, постоянно развивается и не отягощен всякими бредовыми конструкциями (типа assume/offset) других ассемблеров (типа TASM ;)... а, ну да, самое главное, для читателей откуда-нибудь из Изгггаеля, его еще и на шару (конечно же под небо-о-ольшой процЭнт ;) можно скачать на www.flatassembler.net .Берем оттуда версию "for Win32 GUI" которая еще и, до кучи, оснащена простеньким IDE ! Далее... Распаковываем добро из скаченного архива "fasmw.zip" в какую-нибудь папку, и "усе готово шэф" !

Глава 4: Первое приложение (Simple)

Я предполагаю что вы уже писали под Win32 хотя бы и "Hello world !" (если нет то почитайте какой-нибудь tutorial по этому поводу, благо, их в сети туева хуча расплодилась). Окинув взглядом прилагаемый "simple.asm" в папке "simple" вы заметили что в нем нету даже оконной процедуры... Ой фу-у-у-у ! Но не спешите закрывать fasmw.exe, это всего лишь следствие моих небольших "оптимизаций" (а иначе накой хрен нам писать на ассемблере, как не из извращенческих соображений оптимизации ?) Но обо всем по порядку... Надеюсь следующий код понятен всем ? (fasminc можно установить например так (в файле autoexec.nt для WinXP) "set fasminc=путь_к_папке_fasm")
;формат exe файла
format PE GUI 4.0
;точка входа программы
entry start
;включаем файлы API процедур
;(должна быть установлена переменная окружения "fasminc")
include '%fasminc%\win32a.inc'
;констант OpenGL
include '..\include\opengl_const.inc'
;и макросов
include '..\include\opengl_macros.inc'
;начало программы
start:
Для начала спрячем курсор, дабы некоторые "продвинутые" юзвери не начали тыкать им куда не попадя, пораниться ведь можно (Извращения начинаются, см. "push ebx" )
;обнулим ebx. Т.к. он не изменяется API процедурами
;то будем использовать push ebx вместо push 0, для оптимизации
xor ebx,ebx
;спрячим курсор
invoke ShowCursor,ebx
Для начала "урока рисования" нам нужно создать окно, а точнее его класс (и тут не без извращений ;) ) размером с экран. Можно, конечно, для начала изменить разрешение, а потом уже уверенно создавать окно с известным размером, но это будет слишком просто для нас (и долго) ;) Поэтому мы получим текущее разрешение экрана по X и Y (по Z пока не будем ;) ) и запихнем их в стек (перед этим забьем его нулями (ebx) это у нас будут другие параметры процедуры CreateWindowEx. Вообще многие функции подставляют вместо "0" (ebx) параметры "по умолчанию", этим, как говорит Microsoft, "интеллектуальным свойством" мы будем активно пользоваться во всех частях tutorial'а) Помним при этом что функции возвращают результат в "eax". Вообще, следите за стеком ! Это хоть и геморойно, но зато позволяет не сохранять кучу ненужных "промежуточных" результатов функций (вроде GetSystemMetrics).
;поместим в стек 4-е "0" для процедуры "CreateWindowEx"
push ebx
push ebx
push ebx
push ebx
;получим текущее разрешение по вертикали
invoke GetSystemMetrics,SM_CYSCREEN
;поместим его в стек для процедуры "CreateWindowEx"
push eax
;и по горизонтали
invoke GetSystemMetrics,ebx
;и его в стек
push eax
Тут мы используем полученное разрешение экрана (его берем уже из стека... а накой нам лишние переменные ?) чтобы найти его (экрана) AspectRatio (разрешающую способность, т.е. "max_x/max_y") Потом она нам еще пригодится
;вычислим соотношение разрешений экрана по горизонтали и вертикали
fild dword [esp]
fidiv dword [esp+4]
;и сохраним его в ratio
fstp [ratio]
По идее, для создания класса окна нам его нужно было бы с начала описать, зарегистрировать... В общем, скука ужасная (и еще байтов жрет кучу !) Вместо этого мы просто создадим окно с предопределенным классом, таких в винде множество, но мы выберем ну хотя бы класс "edit" (о чем красноречиво сообщает строка szClass) Помним о том что некоторые "недостающие" в invokе'е параметры мы уже положили в стек
;создадим окно размером с экран с предопределенным классом "edit"
;(т.к. его регистрировать не надо,
;то это позволяет избавиться от не нужного кода)
invoke CreateWindowEx,WS_EX_TOPMOST,\
		szClass,szTitle,WS_VISIBLE+WS_POPUP,ebx,ebx
И так, окно мы создали. Если вы уже рисовали что-нибудь под Win32, то вы знаете что для этого нужно получить его "контекст", это делается довольно просто (так как контекст окна нам еще пригодится, мы сохраним его в одном из "неиспользуемых" (в функциях Win32 API) регистров (ebx,ebp,esi,edi) )
;получим контекст окна
invoke GetDC,eax
;сохраним его в ebp
xchg ebp,eax
Теперь нужно задать режим работы OpenGl'а, число бит на пиксель, режимы отображение и прочую белиберду (половину из которой мы оставляем нулевой (т.е. по умолчанию)
;инициализируем дескриптор формата 
;пикселей OpenGL (поддержку OpenGL и двойной буферизации)
mov [pfd.dwFlags],PFD_DRAW_TO_WINDOW+\
	PFD_SUPPORT_OPENGL+PFD_DOUBLEBUFFER
;тип пикселей RedGreenBlueAlpha
mov [pfd.iPixelType],PFD_TYPE_RGBA
;глубину цвета
mov [pfd.cColorBits],32
;плоскость отображения
mov [pfd.dwLayerMask],PFD_MAIN_PLANE
;выберем его
invoke ChoosePixelFormat,ebp,pfd
;и установим его
invoke SetPixelFormat,ebp,eax,pfd
У нас есть контекст окна, у нас есть пиксельный формат ! "Мы сделаем из тебя парень новую звезду"... Аннннн нет, для начала нам еще осталось преобразовать контекст окна в "нечто" понятное OpenGL'у (т.к OpenGL не должен знать что такое Windows, для этого используется специальная "windows-compliant" функция API wgl***) и еще сделать его "текущим" (т.е. рисовать будем в него)
;преобразуем контекст окна в контекст OpenGL
invoke wglCreateContext,ebp
;и сделаем его текущим
invoke wglMakeCurrent,ebp,eax
Фууу-х-х-х-х ! Самое сложное позади ;) Винды остались не у дел, теперь "познакомимся", собственно, с простым и удобным OpenGL'ем ;) Говоря голосом ведущего "в мире животных", эта забавная зверушка тоже требует некоторой настройки параметров (ОПЯТЬ ?!) Дело в том что нам нужно включить "перспективные преобразования", проще говоря перспективу, она зависит от текущей разрешающей способности экрана (т.е. max_x/max_y) вот для этого то мы ее и вычисляли. Так как некоторые извращенческие функции OpenGL воспринимают только параметры типа Double (8 байт) то придется пихать их в стек макросом glCall (который использует макрос glPush) А шо делать ? У нас же Intel Все-таки :( Число 90.0 это требуемый "угол обзора" можно поставить его 45.0 или 60.0 как кому нравится. Числа 0.1 и 100.0 это координаты "отсекающих плоскостей", все, что находится между ними, будет отображено на экране. Остальное - обрезано. Надо заметить что многие функции API OpenGL (glRotate, glTransform и пр.) работают с "текущей матрицей", коих 3 (GL_MODELVIEW, GL_PROJECTION и GL_TEXTURE) поэтому чтобы преобразовать матрицу перспективы мы выбираем ее функцией glMatrixMode.
;выберем режим вычисления перспективных преобразований (наилучший)
invoke glHint,GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST
;выберем для преобразований матрицу перспективной проекции
invoke glMatrixMode,GL_PROJECTION
;умножим ее на матрицу перспективы,
;т.е. попросту включим ее (используем макрос glcall т.к.
;параметры передаются в виде 8 байтов)
glcall gluPerspective,90.0,ratio,0.1,100.0
;выберем для преобразований матрицу изображения
invoke glMatrixMode,GL_MODELVIEW
Ура, а вот и он, "основной цикл". В нем вы "пока" ;) не увидите навороченной демы от haujobb, просто очистка экрана и выход по Esc. Т.к. мы работаем в режиме "двойной буферизации" то рендеринг идет в дополнительный буфер, для отображения его на экране нужно вызвать функцию GDI32 "SwapBuffers". Дабы впоследствии (когда мы добавим сюда что-нибудь) все не вертелось с бешеной скоростью, добавим сюда еще и синхронизацию с таймером
;основной цикл
.draw:
;получаем текущее значение
;счетчика начала работы Windows (для синхронизации)
invoke GetTickCount
;сравним его с сохраненным значением
cmp eax,[msec]
;если оно не изменилось то ждем
jz .draw
;если значение поменялось сохраним его
mov [msec],eax
;очистим буфер экрана
invoke glClear,GL_COLOR_BUFFER_BIT
;отобразим буфер на экран
invoke SwapBuffers,ebp
;проверим на нажатие клавиши ESC
invoke GetAsyncKeyState,VK_ESCAPE
;если она не нажата
test eax,eax
;то продолжим цикл
jz .draw
Не забудем выйти и описать переменные ;) Обратите внимание как (как просто ! ;) ) в Fasm'е описывается импорт и ресурсы.
;выход из программы
invoke ExitProcess,ebx
;заголовок окна
szTitle db 'OpenGL tutorial by Tyler Durden - Simple',0
;имя предопределенного класса окна
szClass db 'edit',0
;включим файл с описанием импорта
data import
include '..\include\imports.inc'
end data
;описание ресурсов
data resource
directory RT_ICON,icons,RT_GROUP_ICON,group_icons
resource icons,1,LANG_NEUTRAL,icon_data
resource group_icons,1,LANG_NEUTRAL,icon
icon icon,icon_data,'..\resources\icons\simple.ico'
end data
;счетчик тиков таймера
msec dd ?
;соотношение разрешений экрана по горизонтали и вертикали
ratio dq ?
;дескриптор пиксельного формата
pfd PIXELFORMATDESCRIPTOR

Ну, вот в принципе и все, в первом OpenGL приложении. Это, типа, был "template", дальше я буду добавлять только измененные части.

Глава 4: Первый цветной треугольник (и тут негры ;) )

Ну, покончив с пережитками Win32 программирования, откинувшись (и оттопырившись) на спинку стула, можно наконец приступить к OpenGL'у вплотную... Оказывается нарисовать на нем треугольник не просто, а, как говорится, очень просто ! Все что надо это задать его координаты. Для начала сообщим что мы хотим, собственно, рисовать треугольники (GL_TRIANGLES) т.е. каждые три точки определяют один треугольник (следующие три - уже другой)

;начало рисования треугольника
invoke glBegin,GL_TRIANGLES
Теперь задаем координаты его вершин и их цвет. Цвет представляет собой 3 float'а (красную, зеленую и синюю (чуть было не написал голубую ;) ) составляющие) о чем говорит постфикс функции "3f". Этими префиксами/постфиксами отличаются все функции OpenGL API (т.е. если надо задать еще и alpha-состовляющую - используем функцию glColor4f, если нужно задавать параметры в Double то 3d/4d или 3i/4i для Integer) То же самое относится и к функции glVertex3f (соответственно первый параметр - координата X, второй - Y, третий - Z)
;цвет 1-й вершины
invoke glColor3f,1.0,0.0,0.0
;ее координаты
invoke glVertex3f,-1.0,-1.0,1.0
;цвет 2-й вершины
invoke glColor3f,0.0,1.0,0.0
;ее координаты
invoke glVertex3f,1.0,-1.0,1.0
;цвет 1-й вершины
invoke glColor3f,0.0,0.0,1.0
;ее координаты
invoke glVertex3f,1.0,1.0,1.0
Все, нарисовались !
;конец рисования треугольника
invoke glEnd
Если все так и оставить, то мы ничего не увидим, т.к. треугольник будет лежать прямо в точке наблюдения, надо бы его отодвинуть. Но, так как мы будем делать это каждую итерацию цикла то наш треугольник будет все дальше и дальше отодвигаться от нас (проверьте это сами, удалив вызов glLoadIdentity) поэтому вначале мы будем "обнулять" матрицу изображения.
;обнулим текущую матрицу (матрицу изображения)
invoke glLoadIdentity
;отодвинем объекты в глубь экрана (z=-3.5)
invoke glTranslatef,ebx,ebx,-3.5
Просто нарисованный треугольник ничем не говорит нам о своей "трехмерности", надо его для этого повращать. Делается это функцией glRotate которая вращает (умножает текущую матрицу на матрицу поворота) на угол [theta] вокруг векторов с координатами (0,0,0) и (второй параметр, третий параметр, четвертый параметр) соответственно. Угол мы будем увеличивать каждую итерацию цикла (т.к. матрица изображения "обнуляется")
;умножим матрицу изображения на матрицу поворота
;(повернем все объекты сцены
;на угол theta относительно
;вектора из (0.0,0.0,0.0) в (0.0,0.0,1.0))
invoke glRotatef,[theta],ebx,ebx,1.0
. . .
;загрузим значение угла theta
fld [theta]
;увеличим его на значение delta
fadd [delta]
;и запишем обратно
fstp [theta]

Вот и все ! И оч-ч-чень просто, по-моему...

Глава 5: Да будет свет !

Свет в OpenGL включается очень просто (как и многое другое) выключателем ;) т.е. функцией glEnable с параметром GL_LIGHTING (отключается соответственно функцией glDisable). Нужно еще включить еще и определенный источник GL_LIGHT* (номер источника). Мы не будем пока парить себе мозг разнообразными видами источников и их параметрами, включим для начала источник GL_LIGHT0 (для него параметры настроены по умолчанию)

;включим источник света GL_LIGHT0 (используя значения по умолчанию)
invoke glEnable,GL_LIGHT0
;включим освещение
invoke glEnable,GL_LIGHTING
Так как в этой главе мы будем рисовать объемную фигуру (кубик) то нужно еще включить режим отсечения не лицевых граней (а то все грани кубика будут рисоваться и перекрывать друг друга) т.е. z-буфер. Делается это опять же функцией glEnable. Цвет получившегося (освещенного) полигона зависит от свойств "материала" и только :( Чтобы правильно отображать его цвет нужно еще включить режим его отслеживания (GL_COLOR_MATERIAL).
;включим режим отсечения не лицевых граней (z-буфер)
invoke glEnable,GL_DEPTH_TEST
;включим изменение свойств материала в зависимости от его цвета
invoke glEnable,GL_COLOR_MATERIAL
Для того чтобы использовать освещение, для каждой грани нужно определить нормаль. Делается это функцией glNormal3f, координата X, координата Y, координата Z
;начало рисования куба из 6 четырехугольников GL_QUADS
;(каждые 4 точки glVertex описывают один четырехугольник)
invoke glBegin,GL_QUADS
;нормаль 1-го четырехугольника
invoke glNormal3f,0.0,0.0,1.0
;цвет 1-й вершины
invoke glColor3f,1.0,0.0,0.0
;ее координаты
invoke glVertex3f,-1.0,-1.0,1.0
;цвет 2-й вершины
invoke glColor3f,0.0,1.0,0.0
;ее координаты
invoke glVertex3f,1.0,-1.0,1.0
;и т.д.
invoke glColor3f,0.0,0.0,1.0
invoke glVertex3f,1.0,1.0,1.0
invoke glColor3f,1.0,1.0,0.0
invoke glVertex3f,-1.0,1.0,1.0
;нормаль 2-го четырехугольника
invoke glNormal3f,0.0,0.0,-1.0
invoke glColor3f,0.0,0.0,1.0
invoke glVertex3f,-1.0,-1.0,-1.0
invoke glColor3f,0.0,1.0,0.0
invoke glVertex3f,-1.0,1.0,-1.0
invoke glColor3f,1.0,0.0,0.0
invoke glVertex3f,1.0,1.0,-1.0
invoke glColor3f,1.0,1.0,0.0
invoke glVertex3f,1.0,-1.0,-1.0
;нормаль 3-го четырехугольника
invoke glNormal3f,0.0,1.0,0.0
invoke glColor3f,0.0,1.0,0.0
invoke glVertex3f,-1.0,1.0,-1.0
invoke glColor3f,1.0,1.0,0.0
invoke glVertex3f,-1.0,1.0,1.0
invoke glColor3f,0.0,0.0,1.0
invoke glVertex3f,1.0,1.0,1.0
invoke glColor3f,1.0,0.0,0.0
invoke glVertex3f,1.0,1.0,-1.0
;нормаль 4-го четырехугольника
invoke glNormal3f,0.0,-1.0,0.0
invoke glColor3f,0.0,0.0,1.0
invoke glVertex3f,-1.0,-1.0,-1.0
invoke glColor3f,1.0,1.0,0.0
invoke glVertex3f,1.0,-1.0,-1.0
invoke glColor3f,0.0,1.0,0.0
invoke glVertex3f,1.0,-1.0,1.0
invoke glColor3f,1.0,0.0,0.0
invoke glVertex3f,-1.0,-1.0,1.0
;нормаль 5-го четырехугольника
invoke glNormal3f,1.0,0.0,0.0
invoke glColor3f,1.0,1.0,0.0
invoke glVertex3f,1.0,-1.0,-1.0
invoke glColor3f,1.0,0.0,0.0
invoke glVertex3f,1.0,1.0,-1.0
invoke glColor3f,0.0,0.0,1.0
invoke glVertex3f,1.0,1.0,1.0
invoke glColor3f,0.0,1.0,0.0
invoke glVertex3f,1.0,-1.0,1.0
;нормаль 6-го четырехугольника
invoke glNormal3f,-1.0,0.0,0.0
invoke glColor3f,0.0,0.0,1.0
invoke glVertex3f,-1.0,-1.0,-1.0
invoke glColor3f,1.0,0.0,0.0
invoke glVertex3f,-1.0,-1.0,1.0
invoke glColor3f,1.0,1.0,0.0
invoke glVertex3f,-1.0,1.0,1.0
invoke glColor3f,0.0,1.0,0.0
invoke glVertex3f,-1.0,1.0,-1.0
;конец рисования куба
invoke glEnd

Заметьте что мы вызываем glRotatef 3 раза, для придания траектории вращения большей random'ности

Глава 5: Текстурирование

Для пущего реализЬма напялим на наш кубик текстуру. Делается это тоже проще пареной репы. Для начала нужно ее (текстуру) нарисовать (можно выдрать откуда-то, главное помните, что ее размер должен быть степенью двойки (256*256*24bpp в примере) ;), и сохранить в формате "raw" (например Photoshop'ом или IrfanView) конечно размер полученного файла будет немаленьким, но проще потом exe-шник сжать UPX'ом чем париться с JPEG/TGA форматами... Затем подключим полученный файл в виде ресурса в resourse.inc

fileres texture,'textures.raw'
Далее ее нужно перевести в понятный OpenGL'у формат функцией glTexImage2D. GL_TEXTURE_2D говорит что текстура у нас 2-х мерная, следующий ноль (ebx) определяет mip-map уровень для нее (для ускорения текстурирования из текстуры можно предварительно сгенерировать несколько уменьшенных копий, но у нас пока только одна), 3-ка - это число компонент в цвете (у нас RGB, а может быть и1-а (R), 2-е (RA), 4-е (RGBA)), затем размерность текстуры по x и y соответственно (256*256), "0" (ebx) это толщина "рамки" вокруг текстуры, далее формат текстуры (RGB, RGB, RGB, ...), тип каждой компоненты (у нас байт) и, собственно, смещение в файле (texture+16).
;создадим 256x256x24bpp текстуру
;из ресурса texture (raw файл)
invoke glTexImage2D,GL_TEXTURE_2D,ebx,\
		3,256,256,ebx,GL_RGB,\
		GL_UNSIGNED_BYTE,texture+16
Теперь осталось определить еще некоторые параметры текстуры, такие как фильтры уменьшения/увеличения (GL_TEXTURE_MIN_FILTER/GL_TEXTURE_MAG_FILTER) (т.е. которые применяются когда текстура, соответственно, меньше/больше требуемой для текстурирования в данный момент времени) функцией glTexParameteri. Установим их в GL_LINEAR (лучшее качество, можно еще GL_NEAREST побыстрее, но похуже)
;установим фильтр текстуры при уменьшении (linear)
invoke glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
;установим фильтр текстуры при увеличении (linear)
invoke glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
Вообще говоря, нужно было предварительно выбрать ID текстуры (1 для первой текстуры, 2 для второй и т. д.) функцией glBindTexture, но т.к. у нас только одна, и вся работа идет с 1-ой текстурой (по умолчанию), то нам ничего не мешает этого не делать.
;выберем текстуру номер 1
invoke glBindTexture,GL_TEXTURE_2D,1
Осталось только включить текстурирование функцией glEnable
;включим текстурирование
invoke glEnable,GL_TEXTURE_2D
Усе, однако OpenGL не знает как привязать координаты вершин объектов к текстурным координатам (за исключением автоматической генерации текстурных координат, но об этом позже) так что надо ей об этом сообщить функцией glTexCoord2f с двумя параметрами (для 2-х мерной текстуры) x и y координатами текстуры (они изменяются также как и все остальное в OpenGL - в относительных координатах от 0.0 до 1.0)
;нормаль 1-го четырехугольника
invoke glNormal3f,0.0,0.0,1.0
;координата текстуры для 1-ой вершины
invoke glTexCoord2f,0.0,1.0
;1-я вершина
invoke glVertex3f,-1.0,-1.0,1.0
;координата текстуры для 2-ой вершины
invoke glTexCoord2f,1.0,1.0
;2-я вершина
invoke glVertex3f,1.0,-1.0,1.0
;и т.д.

В случае с несколькими текстурами, их выбор осуществляется, как я уже говорил, функцией glBindTexture.

Глава 6: Alpha смешивание (blending) или шара продолжается

Alpha смешивание позволяет нам использовать эффекты прозрачности. Для этого нужно опять таки его просто включить функцией glEnable (вам даже не надо знать как оно работает ! Вот шара :smile3: помучившись со своим дос-софтверным 3D движком можно вздохнуть спокойно ;) ), настроить его тип можно функцией glBlendFunc, параметры источника (что смешивается), параметры результата (с чем смешивается). Параметры зависят от требуемого результата.

;выберем режим работы альфа смешивания
invoke glBlendFunc,GL_SRC_ALPHA,GL_ONE
;и включим его
invoke glEnable,GL_BLEND
Чтобы было с чем смешивать, нужно отключить режим отсечения не лицевых граней (попросту z-буфер) функцией glDisable.
;выключим режим отсечения не лицевых граней (z-буфер)
invoke glDisable,GL_DEPTH_TEST

либо вообще его не включать (как у нас в примере).

Глава 7: Туман (без лошади)

А теперь "офигенно" сложный этап tutorial'а ;) Туман ! Делается даже сложней чем alpha смешивание ;) а именно:

;выберем режим работы тумана (экспоненциальный)
invoke glFogi,GL_FOG_MODE,GL_EXP2
;установим его цвет (fogColor)
invoke glFogfv,GL_FOG_COLOR,fogColor
;его интенсивность
invoke glFogf,GL_FOG_DENSITY,0.55
;начало
invoke glFogf,GL_FOG_START,1.0
;и конец
invoke glFogf,GL_FOG_END,3.0
;установим режим вычисления тумана (наилучший)
invoke glHint,GL_FOG_HINT,GL_NICEST
;установим цвет очистки экрана равный цвету тумана
invoke glClearColor,0.5,0.5,0.5,1.0
;включим туман
invoke glEnable,GL_FOG
. . .
;цвет тумана
fogColor dd 0.5,0.5,0.5,1.0

И все ! Параметры я думаю говорят сами за себя... Так как "туманятся" только объекты, то для пущей выразительности окрашиваем остатки экрана под цвет тумана (непонятно почему разработчики OpenGL не предусмотрели для glClearColor вариант с постфиксом fv (передается указатель на массив из Float'ов). Режим "работы" тумана можно выбрать и другой (ну, например, с 9 до 6 вечера, без выходных ;) ) GL_EXP или GL_LINEAR, поэкспериментируйте...

Глава 8: Outline шрифты

Заценим теперь, какую "титаническую" работу проделала Microsoft (ну вот, а говорил, что не будет про них) в своем scream-saver'е "3D Text". Для начала привычные для шрифтов вызовы Win32 API: создаем объект типа (ти-и-ипа) "шрифт" (все значения оставляем на совести Windows), и даем установку (а-ля Кашпировский) для "рисования".

;создадим шрифт 
;(все значения по умолчанию "0", шрифт семейства "Comic Sans MS")
invoke CreateFont,ebx,ebx,ebx,ebx,ebx,ebx,\
	ebx,ebx,ebx,ebx,ebx,ebx,ebx,fontName
;выберем полученый объект (шрифт) для текущего контекста окна (ebp)
invoke SelectObject,ebp,eax
Дальше вступает в дело OpenGL: преобразовывает векторный (именно векторный ! Шрифт "System" преобразовать не удастся, а жаль) в так называемый список (с... что ?!), т.е. последовательность OpenGL команд описания графических примитивов (те что обычно задаются между glBegin, glEnd). Для этого используется функция wglUseFontOutlinesA (или ее unicode версия wglUseFontOutlinesW для символов с кодами больше 255) Получаем "outline font". Мы будем преобразовывать символы начиная с нулевого (ebx) по 255 (всего, значит, 256 символов), единица означает что используются полигоны, а не линии ("0"), "0.2" это толщина нашего шрифта, в ebp, если помните, сохранен контекст OpenGL. Для операций с несколькими (у нас - 256) списками нужно выбрать первый список (вообще-то его их нужно было сгенерировать функцией glGenLists, но за нас это делает OpenGL). Для этого используется функция glListBase.
;преобразуем символы от 0-го до 256-го
;в список 3-х мерных моделей (outline font)
;толщиной 0.2. 
;"1" означает использование полигонов вместо линий ("0")
invoke wglUseFontOutlinesA,ebp,\
	ebx,256,listBase,ebx,0.2,1,ebx
;выберем первый элемент в списке
invoke glListBase,listBase
Посмотрим на предыдущие примеры tutorial'а, и офигеем ;) Зачем было столько носиться с оптимизацией, если размер какой-то завшивленной 256*256*24bpp текстуры занимает целых 200 кило ? Это не есть хорошо, будем теперь генерить ее вручную ! Это не tutorial про генерацию текстур (читайте в "Hugi" про это), поэтому выберем простую "шумовую" текстуру, для этого неплохо подходят код нашей проги. Заюзаем его в качестве текстуры (хе-хе, вот это я понимаю, извращение). OpenGL, конечно, ничего не знает про координаты текстуры для "outline" шрифта, да и вклиниться в работу функции wglUseFontOutlines и "наследить" там вызовами glTexCoord2f будет сложно (для этого нужно юзать feedback буфер), так что просто предоставим заниматься этим самой OpenGL, включив автоматическую генерацию текстурных координат (да, да, вот где еще одна шара !). Причем выберем режим текстуры GL_SPHERE_MAP, который обычно используется в создании всяческих отражений (enironment mapping).
;создадим 16x16x32bpp текстуру используя
;в качестве значений цветов пикселей опкоды нашей программы
;(начиная с метки start)
invoke glTexImage2D,GL_TEXTURE_2D,ebx,4,16,\
		16,ebx,GL_RGBA,GL_UNSIGNED_BYTE,start
;установим тип генерируемой текстуры
;в направлении "S" (GL_SPHERE_MAP)
invoke glTexGeni,GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
;в направлении "T" (GL_SPHERE_MAP)
invoke glTexGeni,GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
;и включим автоматическую генерацию
;текстурных координат в направлении "S"
invoke glEnable,GL_TEXTURE_GEN_S
;и "T"
invoke glEnable,GL_TEXTURE_GEN_T
Ндааа... Опять глава кончилась не успев начаться ;) Ах да, нужно еще написать какое-нибудь слово (желательно не из трех букв, хотя в OpenGL слава Богу еще не встроили Parental Lock ;) все-таки это не DirectX), для этого вызовем списки (хмм, с этим словом главное не описАться... ой) с номерами равными кодам желаемых букв (т.к. мы генерили символы от "0" до "255"). Делаем это функцией glCallLists.
;вызавем txtIntro_length элементов списка (шрифта)
;с номерами указанными в строке байт (GL_UNSIGNED_BYTE) txtIntro
invoke glCallLists,txtIntro_length,GL_UNSIGNED_BYTE,txtIntro
Ура ! Получилось довольно красиво (и, главное, всего на три кило). Можно даже забацать из этого какое-нибудь "Newschool scrolltro". Дерзайте.

Глава 9: Motion blur и рендеринг в текстуру

И так, мы уже вплотную подобрались к алгоритмам используемым в демках. Т.к. OpenGL не позволяет (нормальным образом) получить доступ к видеопамяти, поэтому и алгоритмы эффектов здесь соответствующие - извращенческие. Так что обычным образом motion blur нам сделать не удастся, будем юзать "встроенную" возможность OpenGL blur'ить текстуры при их растягивании (ну да, нам даже не надо знать как работают motion blur фильтры !) Вкратце план довольно прост:
  1. Отрендерив сцену в окно размером blurSize*blurSize (512*512 в примере), скопируем полученное изображение из экранной памяти в текстуру.
  2. Идем дальше ("Строго на север, в порядке 50 метров"), нарисуем четырехугольник с полученной текстурой, размером с экран, поверх изображения.
  3. Затем ("Проходя мимо пЫхты"), отрисуем сцену еще раз.
Все, т.к. альфа смешивание включено, то каждый раз смешивая текстуру с предыдущими сценами (с альфа компонентой меньше "1.0") и черный экран (с альфа компонентой "0.0"), она будет постепенно "исчезать". Так как мы будем рендерить все в пустую текстуру, то с начала нужно ее создать, делается это как и обычно:
;выберем текстуру номер 1
invoke glBindTexture,GL_TEXTURE_2D,1
;создадим blurSizexblurSizex24bpp
;текстуру из буфера TextureBlank (пустого)
invoke glTexImage2D,GL_TEXTURE_2D,ebx,3,blurSize,\
	blurSize,ebx,GL_RGB,GL_UNSIGNED_BYTE,textureBlank 
Заметьте, что кубик мы предварительно "компилируем" в список.
;создадим новый список cubeList
invoke glNewList,cubeList,GL_COMPILE
Blur'ить мы будем не каждый раз, а только через 4 фрейма (иначе "хвост" будет слишком маленьким, да и тормознуто это).
;увеличим счетчик кадров blur'а
inc [blurCounter]
;он стал равен 4-м ?
cmp [blurCounter],4
;нет не в этот раз ;)
jnz .notBlur
С начала нужно установить окно размером blurSize*blurSize, и отрендерить туда нашу "сцену".
;установим окно вывода с 
;координатами 0,0 размером blurSize*blurSize
invoke glViewport,ebx,ebx,blurSize,blurSize
;отрендерим blur сцену
call renderMotionBlur
Скопируем то что получилось в текстуру, функцией glCopyTexImage2D. GL_TEXTURE_2D показывает что текстура двухмерная, следующий ноль (ebx) - это mip-map уровень, затем координаты текстуры (0,0), ее размер (blurSize*blurSize), и размер границы ("0"). Очистим экран, и вернем OpenGL'ое окно обратно.
;выберем текстуру номер 1
invoke glBindTexture,GL_TEXTURE_2D,1
;скопируем в нее отрендеринную только что
;сцену размером blurSize*blurSize
invoke glCopyTexImage2D,\
	GL_TEXTURE_2D,ebx,GL_RGB,ebx,ebx,blurSize,blurSize,ebx
;очистим буфер экрана и z-буфер
invoke glClear,GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT
;установим окно вывода с координатами 0,0
;размером screenWidth*screenHeight
invoke glViewport,ebx,ebx,[screenWidth],[screenHeight]
Теперь как нам нарисовать четырехугольник точно размером с экран ? Ведь OpenGL'у плевать на пиксели, он оперирует какими-то "условными единицами" (зелеными, наверное ;) ). Для этого нам надо установить так называемую "Ортогональную (прямоугольную) проекцию" делается это не сложнее установки перспективной проекции:
;и умножим на матрицу ортогональной проекции
glcall glOrtho,0.0,screenWidthDouble,screenHeightDouble,0.0,-1.0,1.0
Усе, рисуем нашу сцену четырехугольником (заметьте, что перспективную проекцию мы устанавливаем не функцией, а, просто, сохранив проекционную матрицу в стеке функцией glPushMatrix, и восстанавливаем ее функцией glPopMatrix)
;выберем текстуру номер 1
invoke glBindTexture,GL_TEXTURE_2D,1
;уменьшим альфа компоненту объектов (0.9)
invoke glColor4f,1.0,1.0,1.0,0.9
;выберем для преобразований матрицу перспективной проекции
invoke glMatrixMode,GL_PROJECTION
;сохранить текущую матрицу (матрицу перспективы)
invoke glPushMatrix
;обнулим ее
invoke glLoadIdentity
;и умножим на матрицу ортогональной проекции
glcall glOrtho,0.0,screenWidthDouble,screenHeightDouble,0.0,-1.0,1.0
;выберем для преобразований матрицу изображения
invoke glMatrixMode,GL_MODELVIEW
;обнулим текущую матрицу (матрицу изображения)
invoke glLoadIdentity
;нарисуем четырехугольник размером в экран
;(отрендеренная до этого сцена используется в качестве тексуры)
invoke glBegin,GL_QUADS
invoke glTexCoord2f,ebx,1.0
invoke glVertex2i,ebx,ebx
invoke glTexCoord2f,ebx,ebx
invoke glVertex2i,ebx,[screenHeight]
invoke glTexCoord2f,1.0,ebx
invoke glVertex2i,[screenWidth],[screenHeight]
invoke glTexCoord2f,1.0,1.0
invoke glVertex2i,[screenWidth],ebx
invoke glEnd
;выберем для преобразований матрицу перспективной проекции
invoke glMatrixMode,GL_PROJECTION
;восстановим ее из стека
invoke glPopMatrix
В принципе ничего сложного тут нет, все абсолютно "прозрачно", смотрите код и все поймете.


Глава 10: Zoom blur

Ур-ра ! Последняя часть tutorial'а (пока, гы гы). Надоели вы мне, хех, как сказал один доцент (тоже ученый, у него 3 класса образования): "пришить бы вас, да возиться неохота" ;). Но пока, так... Копаем... Копать будем "zoom blur", или как его еще иногда (и неправильно) называют "radial blur", или даже "volumetric lights"... Но не в названии дело, а в его простоте. Собственно, после предыидущей главы, делать мне тут нечего, разве что так, "на шухере постою". Алгоритм довольно тормознутый (для моей отстойной видюхи GeForce4 MX), в 1024*768 лучше не пускать, поэтому установим-ка, для начала, что нибудь вроде 640*480:
;подготовим структуру описывающую видеорежим, ее размер
mov [dmScreenSettings.dmSize],dmScreenSettings_size
;разрешение по горизонтали
mov [dmScreenSettings.dmPelsWidth],screenWidth
;вертикали
mov [dmScreenSettings.dmPelsHeight],screenHeight
;изменяемые поля
mov [dmScreenSettings.dmFields],DM_PELSWIDTH+DM_PELSHEIGHT
;сменим видеорежим
invoke ChangeDisplaySettings,dmScreenSettings,ebx
Обратите внимание на, уже набивший всем оскомину, "эффект" широкоформатной пленки (типа, фи-и-и-ильму, фи-и-и-ильму !), который кое-кто уже умудрился впарить даже в 256b intro. Т.к. я поубивал много лишних вызовов процедур (к примеру, glViewport нужно было бы вставить после инициализации), то прога потеряла некоторую долю логичности (а заодно и пару "килограмм"), но к 10 главе мы уже все стали "хавцами" в OpenGl'е, неправда ли ?
;установим окно вывода с координатами 0,77
;размером screenWidth*screenHeight
invoke glViewport,ebx,77,screenWidth,screenHeight
Сам алгоритм, который очень похож на motion blur, состоит в следующем (неизмененные места оставлены специально):
  1. Отрендерив сцену в окно размером blurSize*blurSize (256*256 в примере), скопируем полученное изображение из экранной памяти в текстуру.
  2. Нарисуем четырехугольники с полученной текстурой поверх изображения несколько (blurCount) раз, каждый раз увеличивая их размер, от blurSize до полноэкранного размера (640), и уменьшая значение альфа компоненты.
  3. Рулим ;) (можно отрендерить сцену еще раз, поверх этого, но мы не будем).
Смотрим код:
;инициализируем максимальный радиус радиального blur'а
mov ecx,blurCount
;и начальные значения переменных
mov dword [blurInc],ebx
mov dword [blurDec],1.0
mov dword [blurAlpha],0.35
;цикл радиального размытия
.renderMotionBlur:
;сохраним счетчик
push ecx
;увеличим радиус "справа"/уменьшим "слева"
;и уменьшим альфа компоненту радиального blur'а
fld dword [blurDelta]
fld st0
fadd dword [blurInc]
fstp dword [blurInc]
fsubr dword [blurDec]
fstp dword [blurDec]
fld dword [blurAlpha]
fsub dword [alphaDelta]
fstp dword [blurAlpha]
;выберем текущее значение альфа компоненты
invoke glColor4f,[esp],[esp],1.0,[blurAlpha]
;нарисуем четырехугольник размером в экран
;(отрендеренная до этого сцена используется в качестве тексуры)
invoke glBegin,GL_QUADS
invoke glTexCoord2f,[blurInc],[blurDec]
invoke glVertex2i,ebx,ebx
invoke glTexCoord2f,[esp],[blurInc]
invoke glVertex2i,ebx,screenHeight
invoke glTexCoord2f,[blurDec],[blurInc]
invoke glVertex2i,screenWidth,screenHeight
invoke glTexCoord2f,[esp],[blurDec]
invoke glVertex2i,screenWidth,ebx
invoke glEnd
;восстановим счетчик
pop ecx
;уменьшим его
dec ecx
;и продолжим цикл
jnz .renderMotionBlur
Незабудем вернуть видеорежим обратно, для этого просто вызовем функцию "ChangeDisplaySettings(null,0)"
;вернем видеорежим обратно
invoke ChangeDisplaySettings,ebx,ebx

Вот и все ! Уже украли/обрили... Просто, но, черт возьми, эффЭктно (и впереди я, на белом верблюде). Так о чем это я ? Ага, пришло, наконец, время долгожданного прощания. Просьба не устраивать сцен и истерик ;) обнимемся как братья... Э-э-э-э, что там еще говорят ? Желаю счастья в личной жизни... Один маленький, но очень гордый птичка... Нет, не то... Типа, надобранич дитлохи, до зустричи (тут я прям прослезился, как все Хрюши, Степаши и тети Тани/Амаяки Акопяны, канала О.Р.Т., вместе взятые)...

Приложение 1: Vertex arrays

Не успев обрадоваться от вышеописанного расставания (сцена с рыдающими детьми: "Папа ! Папа ! На кого ты нас покинул ?!"). Люба, я вернулся (wmplayer.exe c:\windows\media\tada.wav). Ну "да-ра-гггие мАи" ну не мог же я выпустить вас "в люди" голыми, так сказать ! Так бы сейчас и писали glBegin'ами. Шо вы ? Это уже совсем не модно ! Щас "в Европе, а также в лучших домах Филадельфии" все поголовно юзают массивы вершин. Вообще то, массив вершин (vertex array) - это почти тоже самое, что и команды между glBegin/glEnd, только их выполняет сама карта, подставляя значения из нашего массива. Как нам уже известно, описывать приходится не только, собственно, вершины, но и нормали, текстурные координаты, цвет и пр., поэтому и массивы бывают разные. Однако, мы не будем мучить себя подключением всех массивов, скинем просто все в кучу функцией "glInterleavedArrays" (т.е. данные в массиве идут последовательно, сначала нормаль одной вершины, потом ее текстурные координаты, цвет и пр. затем сами координаты вершины. Затем тоже самое для следующей вершины и т.д.). Она принимает параметрами тип "сваленных в кучу" массивов, тут это GL_N3F_V3F, т.е. N3F - массив нормалей, по 3 float'а на нормаль и V3F - массив вершин, тоже из 3-х float'ов на вершину, так называемый "stride" - "расстояние" между данными для соседней вершины (у нас будет "0" (ebx) т.к. данные в массиве идут подряд), и ,наконец, ссылку на массив с данными (mdlSheep). Но glInterleavedArrays только подключает массивы на отображение, их еще нужно, собственно, отобразить. Делается это функциейй "glDrawArrays". Первый параметр - это тип используемых примитивов (GL_TRIANGLES), второй - это первый элемент массива (ebx=0, т.е. рисуем все элементы) и третий параметр - это общее число элементов...

;включим отображение массивов нормалей
;(по 3 float'а на нормаль) и вершин (3 float'а на нормаль)
invoke glInterleavedArrays,GL_N3F_V3F,ebx,mdlShip
;отрисуем вершинный массив из 8686 треугольников,
;начиная с треугольника номер "0" (ebx)
invoke glDrawArrays,GL_TRIANGLES,ebx,8686*3-1
Дело за малым - осталось найти де взять этот массив ;) Можно, конечно, и ручками (как в следующем приложении), но я (для демонстрации "крутизны" массивов вершин) отконвертил модельку X-Wing'а (предварительно сохранив ее 3DStudio в формате "ASC"). Можно было вставить ее просто как переменную (mdlShip dd ...) но это б заняло полтора мегабайта ;) Так что пришлось вынести ее в отдельный файл, и скомпилить FASM'ом, в результате чего получился 600 Kb бинарник, который я засунул в ресурсы директивой "file".
mdlShip file '..\resources\models\ship.bin'
Обратите внимание как я "генерил" текстуру (так вот откуда Cпилберг спер терминатора T1000 ;)) взяв за основу только красные компоненты "пикселей" (а на деле опкоды... красные уже и сюда добрались...) "GL_LUMINANCE_ALPHA". Для придания текстуре яркости, я использовал специальный режим обработки bitmap'ов (о них в следующем приложении) т.е. здесь - текстуры: "glPixelTransferf" с параметром "GL_RED_SCALE" (множителем красной (нет, ну хоть в кавычках это слово пиши) компоненты) и значением "10.0".
invoke glPixelTransferf,GL_RED_SCALE,10.0

Ну, теперь все быстренько на борьбу с кубиками и торами ! Рисуйте свои модельки и вперед. Помните только (неоднократно натыкался на такое "недопонимание") что массив индексов, это на самом деле массив индексов цветов в режимах с маленькой глубиной цвета (256 цветов и т.д.) а не массив индексов вершин (как в 3D моделях).

Приложение 2: Битовые карты (ну там, тройка, семерка, туз...)

Ну почему обязательно карты ? Есть много интересных и полезных спортивных игр ! Например литрбо... оуэ-э-э-э битмап шрифты ! Еще бы, как же иначе мы напишем в нашу демку кучу псевдо-философского бреда, и причем непременно (непреме-е-енно !) "разъежающейся-блендещайся" Tahoma'ой... После главы про outlined шрифты, здесь уже все прозаично. Как обычно "создадим" шрифт:
;создадим шрифт (размером "9", остальные значения по умолчанию "0"
;, шрифт семейства "Lucida Console")
invoke CreateFont,-9,ebx,ebx,ebx,ebx,\
;ebx,ebx,ebx,ebx,ebx,ebx,ebx,ebx,fontName
;выберем полученый объект
;(шрифт) для текущего контекста окна (ebp)
invoke SelectObject,ebp,eax
Теперь, по анологии с outlined шрифтами, преобразуем все это в список команд рисования битмап'ов функцией "wglUseFontBitmaps(контекст OpenGL (ebp),первый символ (ebx),число символов (256),список (берем 0-й))" :
;преобразуем символы от 0-го до 256-го в список битмап'ов
invoke wglUseFontBitmapsA,ebp,ebx,256,ebx
Установим ортогональную проекцию (для битмап'ов работать в ней удобнее). Вот тут начинается самое интересное... Будем делать "Матрицу" ! Ну, ту самую, что "has Neo...". Надеюсь ему понравилось, но мы за него ОТОМСТИМ ! Вощ-щ-щем расклад такой:
  1. Заведем массив на каждую "строку" "матрицы" с координатами X (dword), Y (dword) и ее начальным символом
  2. Генерим каждой все вышеописанное
  3. В основном цикле распихиваем "строки" по сгенереным координатам, каждый раз начиная с начального символа "строки" и "начального цвета" (черного), увеличивая их для каждой следующей буквы
  4. Начальный символ увеличивается для каждой "charChangeFrequency" "строки"
  5. Каждый последний символ "строки" выделяем голубым цветом (мстим за Нео)
  6. Так как координата "Y" увеличивается на размер "строки" по вертикали, то вернем ее на место, пришьем, попутно, + 3 за побег (+ 5 за детсад), т.к. "строки" должны "бежать" вниз
  7. Если какая-нибудь "строка" сЦучилась (читай, вылезла за края экрана) - обнулим ей "Y" (шоб неповадно было)
  8. Просто матрица это ж страшный "порожняк", поэтому напялим эту в качестве текстуры, для начала, хотяб на кубик (месть продолжается). Делается это как обычно, копированием экрана в текстуру функцией "glCopyTexImage2D"
Надеюсь вы что-нибудь поняли... Если нет, то смотрите код, там понятней. Вообще-то это была задумка написать "город" а-ля в "Матрице", отсюда и кубик, но меня потом в конец обломало :( Пишите сами, в общем. На том и сказке/тьюториалу конец (делу венец и т.п.) а кто слушал #$%&@# !!! © Tyler Durden

1 5.704
archive

archive
New Member

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