OpenGL - Създаване на прозорец с GLUT

В този материал ще ви покажа как да включите библиотеката OpenGL и как да изградите средата, комуникираща с операционната система. Както може би вече знаете, с OpenGL не можете да построявате прозорец, в който да се визуализира графиката, както и не можете да приемате събития от мишката и клавиатурата. За тази цел ще използваме библиотеката GLUT, чиято последна версия може да намерите на официалния сайт на OpenGL - www.opengl.org
Заедно с повечето компилатори се предлага библиотеката GLAUX и макар че тя е близка на GLUT, втората притежава значително по-голяма функционалност. След като свалите GLUT ще трябва да я инсталирате към системата. Библиотеката се състои от три файла - glut32.dll, glut32.lib и glut.h, като всеки от тях трябва да бъде разположен на правилното място:

glut32.dll - поставете го в папката Windows

glut32.lib - съответно при останалите *.lib файлове в папката LIB на вашия компилатор, а най - вероятно това е Microsoft Visual C++. Ето директорията: C:/ Microsoft Visual Studio / VC98 / LIB , където С:/ е името на съответното дисковото устройство.

glut32.h - съответно при останалите хедърните файлове в папката Include, но най-добре ще е да го сложите в подпапката GL. Директория:C:/ Microsoft Visual Studio / VC98 / Include / GL

Много е важно при построяването на вашия проект, да изберете Win32 Console Application, а не Win32 Application. След като сте готови с проекта трябва да свържете библиотеките : Project / Settings / Link / във реда Оbject/Library Modules пред kernel32.lib трябва да добавите файловете opengl32.lib и glut32.lib

Това е. Следва един код, който ще бъде скелета на вашите OpenGL програми.

Първо трябва да включим хедърните файкове. Въпреки че използваме GLUT за създаването на прозореца, той взаимодейства с Windows, така че трябва да включим и хедърния файл windows.h

#include < windows.h > // Хедърният файл за Windows
#include < gl/glut.h > // Хедърният файл за GLUT
#include < gl/gl.h > // Хедърният файл за OpenGL

void Render( ); // Прототипът на рендериращата ни функция

Сега идва ред на функцията main( ), която приема целочисления аргумент argc и масива от низови указатели *argv[ ]

int main( int argc, char *argv[ ] )
{


Най-напред трябва да инициализираме библиотеката GLUT с функцията: glutInit( int *argc, char *argv[ ] )

glutInit( &argc, argv );

След това трябва да определим режима на рендериране с функцията glutInitDisplayMode, която приема няколко аргумента разделени със символа " | ". Това са :

GLUT_SINGLE или GLUT_DOUBLE за единичен или двоен буфер. Винаги използвайте двоен буфер, освен ако няма да рендерирате някоя 2D фигура. За триизмерна графика е задължително използването на двоен буфер, който е и много по-бърз.При GLUT_DOUBLE само единия буфер е видим, а в другия се рендерира графиката и след това двата буфера се разменят.
GLUT_DEPTH, GLUT_STENCIL и/или GLUT_ACCUM. Първата константа определя наличието на z-буфер. Ако правите триизмерна графика неговото включване е задължително. За останалите две константи ще научите в другa тема от сайта.

GLUT_RGBA или GLUT_INDEX oпределя начина за изграждане на цветовете във вашата програма. При GLUT_INDEX цвета се определя от т.н. цветова палитра, която може да съдържа 16 или 256 цвята и на всяко число от 0 до 15 или от 0 до 255 съответства точно определен цвят - безспорно остарял стандарт, който дори възпрепятства използването на други ефекти като например - динамична светлина. Използвайте винаги GLUT_RGBA при който цвета се сформира от количеството на RED, GREEN, BLUE цветовете и ALPHA канала ( полупрозрачност ).

glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);

Сега трябва да определим големината на прозореца с функцията: glutInitWindowSize( int width, int height )

glutInitWindowSize( 400 , 400 ); // Определяме по 400 пиксела височина и широчина за прозореца

След като сме определили характеристиките, създаваме прозореца с функцията: glutCreateWindow(char *string), която приема избраното от нас име за прозореца.

glutCreateWindow( "Created with GLUT (OpenGL Utility Toolkit)" );

След като сме създали прозореца, извикваме функцията glutDisplayFunc(void ( *func )( void )), която извиква на свой ред нашата рендерираща всеки път когато прозореца трябва да бъде прерисуван. Това може да се случи когато прозореца се показва за първи път, когато е повреден или ако се извика функцията glutPostRedisplay( ).

glutDisplayFunc( Render );

Накрая извикаме функцията glutMainLoop( ), която стартира цикъла на съобщенията.

glutMainLoop( );

return 0;
}


Нашата main функция е вече готова, но тя все още не е съвсем пълна. След малко ще ви представя най-полезните функции от GLUT, които следят за съобщения от мишката и клавиатурата, оразмеряват правилно прозореца при минимизиране и максимизиране и много други.

Следва нашата рендерираща функция:

void Render() {

Първо трябва да изчистим буфера от предишното изображение, понеже искаме кадрите да се сменят, а не да се изрисуват един върху друг във вид на мацаница :) Това става с функцията glClear( ), която вече е част от OpenGL. Tя може да приема няколко от следните аргументи разделени със знака " | " :

GL_COLOR_BUFFER_BIT - изчиства цветовия буфер
GL_DEPTH_BUFFER_BIT - изчиства z - буфера
GL_ACCUM_BUFFER_BIT - изчиства Accumulation буфера
GL_STENCIL_BUFFER_BIT - изчиства Stencil буфера

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

Друга изключително полезна функция е glClearColor( ), с която можете да запълните цветовия буфер с определен цвят или по-просто казано - да определите цвета на фона. Това е нейния прототип :

void glClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ) - тя приема RGBA цвета на фона. Засега тази функция няма да ни трябва много, но я запомнете добре, понеже без нея няма да може да създадете реалистична мъгла, за която ще стане въпрос след няколко теми...

Тук би трябвало да се помества нашия OpenGL код, но за момента няма да има такъв.

Ако ползвате двоен буфер, задължително трябва да извикате функцията glutSwapBuffers( ), която разменя двата буфера т.е невидимия буфер в който изображенията са вече рендерирани се показва, а буфера който до момента е бил видим става невидим, изчиства се и в него се рендерира следващия кадър, който трябва да бъде показан. В случай че предпочетете единичен буфер, трябва да извикате функцията glFlush( ), вместо glutSwapBuffers( ).

glutSwapBuffers( );

Функцията glutPostRedisplay( ) съобщава на GLUT, че прозореца трябва да бъде прерисуван.Това води до извикване на glutDisplayFunc( ), която пък извиква рендериращата ни функция. Така се стига до един безкраен цикъл, който обаче е задължителен ако не искате програмата ви да показва само първия кадър от 3D графиката и да спира.

glutPostRedisplay( );
}


Ако след компилиране на този сорс, стартирате програмата и се покаже вашия прозорец ( ще го познаете по името ) показващ само черен фон, заедно с още един DOS прозорец, значи всичко е наред. Ако сте нетърпеливи да научите още сега колкото се може повече за OpenGL може да пропуснете останалата част от темата за GLUT, и да се върнете към нея по-късно, защото вече имате основата да започнете истинската работа с OpenGL. А и останалата част може да ви се стори скучна, понеже предствлява нещо като справочник на функции. Ето от тук може да видите един код за създаване на прозорец с GLUT, в който съм използвал някои от по-долу обяснените функции.


Сега ще ви представя някои други полезни функции от библиотеката GLUT. Това са :

int glutCreateWindow( char *name ) - създава прозорец ( както вече знаете ), но и връща неговия индентификационен номер. Например: win = glutCreateWindow( "Window" ) , където win е променлива от тип int.

glutDestroyWindow( int win ) - унищожава прозорец, определен от идентификационния номер win.

int glutGetWindow( void ) - връща идентификационния номер на текущия прозорец.

glutInitWindowPosition( int x, int y ) - определя началното местоположение на прозореца като приема два аргумена( x и y координата ), които се отнасят за горния ляв ъгъл на прозореца. Имайте в предвид че при операционна система Windows най-горната лява точка от екрана на монитора има координати ( 0 , 0 ).Специално функцията glutInitWindowPosition( ) трябва да бъде извикана преди функцията glutCreateWindow( ), която изгражда прозореца.

glutPositionWindow( int x, int y ) - променя позицията на създадения вече прозорец в зависимост от подадените от нас аргументи.

glutReshapeWindow( int width, int height ) - променя размерите на създадения прозорец в зависимост от подадените от нас аргументи.

glutSetWindowTitle( char *name ) - определя/променя заглавието на прозореца в зависимост от подадения аргумент.

Следващите функции трябва да бъдат извикани задължително след създаването на прозореца с glutCreateWindow и преди започване на цикъла на съобщенията т.е. преди функцията glutMainLoop().

glutFullScreen( void ) - оразмерява прозореца на цял екран. Изключително полезна финкция.

glutReshapeFunc(void ( *func )( int width, int height )) - определя функцията, която се извиква когато прозореца промени своя размер. На нашата функция се подават новите размери на прозореца. Например : glutReshapeFunc( OurFunction ), където нашата функция трябва да е от вида void OurFunction(int w, int h)

glutSetCursor( int cursor ) - променя курсора на мишката в зависимост от подадения аргумент, който може да бъде една от константите :
GLUT_CURSOR_RIGHT_ARROW - наклонена надясно стрелка
GLUT_CURSOR_LEFT_ARROW - наклонена наляво стрелка
GLUT_CURSOR_INFO - сочеща ръка
GLUT_CURSOR_DESTROY - череп с кръстосани кости
GLUT_CURSOR_HELP - стрелка с въпросителен знак
GLUT_CURSOR_CYCLE - кръг с пресечена черта
GLUT_CURSOR_SPRAY - спрей
GLUT_CURSOR_WAIT - пясъчен часовник
GLUT_CURSOR_TEXT - знака " I ", когато добавяте текст
GLUT_CURSOR_CROSSHAIR - кръст
GLUT_CURSOR_UP_DOWN - две противоположно сочещи вертикални стрелки, свързани в една цяла - появява се когато искате да промените размера на даден прозорец
GLUT_CURSOR_LEFT_RIGHT - две противоположно сочещи хоризонтални стрелки, свързани в една цяла
GLUT_CURSOR_TOP_SIDE - стрелка сочеща нагоре
GLUT_CURSOR_BOTTOM_SIDE - стрелка сочеща надолу
GLUT_CURSOR_LEFT_SIDE - стрелка сочеща наляво
GLUT_CURSOR_RIGHT_SIDE - стрелка сочеща надясно
GLUT_CURSOR_TOP_LEFT_CORNER - стрелка сочеща към горния ляв ъгъл
GLUT_CURSOR_TOP_RIGHT_CORNER - стрелка сочеща към горния десен ъгъл
GLUT_CURSOR_BOTTOM_RIGHT_CORNER - стрелка сочеща към долния десен ъгъл
GLUT_CURSOR_BOTTOM_LEFT_CORNER - стрелка сочеща към долния ляв ъгъл
GLUT_CURSOR_NONE - невидим курсор


Обработка на събития :

glutKeyboardFunc(void ( *func )( unsigned char key,int x, int y )) - определя коя функция да бъде извикана при натискане на клавиш от клавиатурата.Тя трябва да е от вида void OurFunction(unsigned char key, int x, int y). На нашата функция се прадава ASCII кода на натиснатия клавиш и координатите на мишката.

glutSpecialFunc(void ( *func )( int key, int x, int y )) - oпределя коя функция да бъде извикана ако буде натиснат някой от специалните клавиши на клавиатурата.Тя трябва да е от вида void OurFunction(int key, int x, int y). Първия аргумент може да бъде една от константите : GLUT_KEY_F1, ..., GLUT_KEY_F12, GLUT_KEY_LEFT, GLUT_KEY_RIGHT, GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_PAGE_UP, GLUT_KEY_PAGE_DOWN, GLUT_KEY_INSERT, GLUT_KEY_END. Другите два аргумента определят местоположението на курсора на мишката.

glutMouseFunc(void ( *func )( int button, int state, int x, int y )) - определя функция която се извиква при натискане/отпускане на бутон на мишката. Тя трябва да е от вида void OurFunction( int button, int state, int x, int y ). Първият от подадените аргументи може да е равен на една от константите : GLUT_LEFT_BUTTON, GLUT_RIGHT_BUTTON и GLUT_MIDDLE_BUTTON. Втория аргумент ( state ) може да е равен на GLUT_DOWN ако бутона е натиснат или на GLUT_UP ако бутона е отпуснат. Последните два аргумента определят местоположението на курсора на мишката.

glutMotionFunc(void ( *func )( int x, int y )) - oпределя функция, която се извиква ако мишката се премести докато някой от бутините й е натиснат. Двата аргумента определят местоположението на мишката.

glutPassiveMotionFunc(void ( *func )( int x, int y )) - определя функция, която се извиква, ако мишката се премести докато не е натиснат никой от бутоните й.

glutEntryFunc(void ( *func )( int state )) - определя функция, която се извиква когато курсора на мишката напусне прозореца или влезе отново в него. Подадения аргумент може да бъде една от двете константи GLUT_LEFT(ако курсора излиза от прозореца) или GLUT_ENTERED (ако курсора влиза в прозореца).

glutIdleFunc(void ( *func )( void )) - определя функция, която се извиква ако няма възникнали събития т.е ако никоя от по-рано изброените функции не бъде извикана.

glutTimerFunc( unsigned int msecs, void ( *func )( int value ), int value ) - определя функция, която се извиква след определено време. Първия аргумент ( msecs ) определя интервала от време в милисекунди, а последния аргумент ( value ) се подава на нашата функция, която се извиква от glutTimerFunc( ). Тя трябва да бъде от вида void OurFunction( int value ).


Създаване на менюта :

Библиотеката GLUT предлага няколко функции с които можете бързо и лесно да създадете различни контекстни менюта. Функциите с които се създава меню са :

int glutCreateMenu(void ( *func )( int value )) - тя връща индентификационния номер на създаденото меню, който трябва да е от тип int и определя функция която трябва да бъде извикана при избиране на елемент от менюто. Функцията трябва да е от вида void OurFunction( int value ). Подадения й аргумент е индентификационния номер на избрания елемент от менюто.

void glutSetMenu( int menu ) - определя текущо контекстно меню, в зависимост от подадения индентификационен номер.

int glutGetMenu( void ) - връща индентификационния номер на текущото контекстно меню.

void glutDestroyMenu( int menu ) - унищожава контекстното меню, чиито индентификационен номер сме подали като аргумент на функцията.

void glutAddMenuEntry( char *name, int value ) - добавя елемент към текущото контекстно меню. Първия аргумент определя името на елемента, което ще видим в менюто, а втория индентификационния номер на елемента. Обикновено първия елемент има инд. номер 1, втория - 2 и т.н.

void glutAddSubMenu( char *name, int menu ) - добавя подменю към текущото контекстно меню. Първия аргумент определя текста, който ще се покаже в текущото меню, от който ще излиза подменюто при задържане на курсора на мишката върху него. Втория аргумент приема индентификационния номер на вече създадено меню, което искаме да превърнем в подменю на текущото меню.

void glutRemoveMenuItem( int entry ) - премахва елемент от менюто с пореден номер entry. Първия елемент има пореден номер 1. Подменютата са също елемени на главното меню, така че и те имат поредни номера и могат да бъдат премахвани.

void glutAttachMenu( int button ) - определя с кой бутон на мишката да се пуска определено контекстно меню. Аргумента button може да бъде една от константите GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON или GLUT_RIGHT_BUTTON.

void glutDetachMenu( int button ) - премахва възможноста на дадено меню да бъде пуснато със съответния бутон, определен от аргумента button.

Преди да създадете меню, трябва да дефинирате променлива от тип int, която ще се използва за съхранение на индентификационния номер на менюто. След това създаваме менюто : ID = glutCreateMenu( OurFunction ), където ID е нашата променлива. След това трябва да определим това меню като текущо и извикваме glutSetMenu( ID ). Вече можем да добавяме елементи с glutAddMenuEntry( ) или подменюта с glutAddSubMenu( ). Накрая извикваме функцията glutAttachMenu( int button ) за да определим с кой бутон на мишката да се пуска менюто. Ако искаме можем да имаме три действащи менюта, които да се пускат с трите бутона на мишката. Просто извършваме гореописаните стъпки за всяко меню поотделно.

Не съм разгледал абсолютно всичко, но смятам че и това е повече от достатъчно. Ако нещо не ви е ясно не се затормозявайте с него. Вижте другите сорс-кодове в сайта и може да намерите отговор на въпроса си. А и в крайна сметка целта не е да научите перфектно как се прави прозорец, а да усвоите 3D графичното програмиране с OpenGL.

Автор: Иван Георгиев Иванов