В предыдущих шагах мы познакомились с основными понятиями OpenGL и библиотекой GLU.
Возможности этих библтотек велики, но всегда хочется больше. В этом шаге мы сделаем полноэкранное windows приложение (те, кто играл в Q3 Arena видели full screen OpenGL в действии).
Постепенно, за несколько шагов, мы пройдем путь от рисования плоских примитивов до создания простенькой 3-х мерной "ходилки".
В этом шаге мы создадим скелет приложения, который мы будем использовать во всех дальнейших проектах. Я, например, сделал проект skeleton и спрятал в надежное место ;)
Прежде всего создайте пустой проект Win32 Application. Назовем его skeleton. Добавьте в проект пустой файл skeleton.cpp ( или .c, поскольку мы пока не будем использовать с++)
Теперь настроим проект: Меню Project->Settings, заладка Link и в editbox "Object/Library Modules" добавим библиотеки: opengl32.lib, glu32.lib ( в принципе, можно обойтись и только opengl32.lib, но glu32.lib имеет несколько удобных функций для настроек ViewPort-а)
Нажимаем "Ok" и, как пишут в американских книжках, мы готовы пить кофе ;) Ну ладно, шутки в сторону.
Пишем заголовочные файлы:
#include <windows.h> // это, сами понимаете для того что б win заработало #include <gl/gl.h> // OpenGL #include <gl/glu.h> // GLU
Теперь объявим несколько глобальных переменных:
static HGLRC hRC; // Это контекст рендеринга static HDC hDC; // А это стандартный контекст устройства windows BOOL keys[256]; // это массивчик, который нам пригодится // для обработки нажатия клавиш
Теперь объявляем функции:
GLvoid Initial(GLsizei Width, GLsizei Height); // по названию ясно, это инициализация всей страны (т.е. OpenGL) GLvoid Resize(GLsizei Width, GLsizei Height); // эта маленькая функция будет вызываться при получении окном сообщения WM_SIZE GLvoid Draw(GLvoid); // это центр вселенной для OpenGL LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Ну это все и так знают. Функция окна в Win32
Переходим к реализации функций:
GLvoid Initial(GLsizei Width, GLsizei Height) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// устанавливаем цвет для очистки буфера цвета glClearDepth(1.0); // устенавливаем параметр для очистки буфера глубины glDepthFunc(GL_LESS); // настройка Z буфера glEnable(GL_DEPTH_TEST); // и, наконец, включение glShadeModel(GL_SMOOTH); // выбираете режим затенения ( flat или smooth ) glMatrixMode(GL_PROJECTION);// устанавливаем текушей матрицей матрицу проекта glLoadIdentity(); // обнуляем эту самую матрицу // настраиваем перспективу ( вот она, функция из glu32 ) gluPerspective(45.0f, (GLfloat)Width / (GLfloat)Height, 0.1f, 100.0f); glMatrixMode(GL_MODELVIEW); // и переключаемся в модельную матрицу }
Следующую функцию я комментировать не буду, мне кажется и так все понятно.
GLvoid Resize(GLsizei Width, GLsizei Height) { if(Height==0) Height = 1; glViewport(0, 0, Width, Height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f, (GLfloat)Width / (GLfloat)Height, 0.1f, 100.0f); glMatrixMode(GL_MODELVIEW); }
Функция Draw пока очень маленькая, но она будет расти, и будет это делать очень быстро:
GLvoid Draw(GLvoid) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // очищаем буферы glLoadIdentity(); // обнуляем модельную матрицу }
А сейчас самое интересное. Функция WndProc. В ней мы должны установить формат пикселей и будем обрабатывать сообщения:
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { RECT Screen; // Это структурка в которую мы поместим размеры области экрана GLuint PixelFormat; // формат пикселя static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR),// размер структуры 1, // версия ? PFD_DRAW_TO_WINDOW| // format must support Window PFD_SUPPORT_OPENGL| // format must support OpenGL PFD_DOUBLEBUFFER, // must support double buffer PFD_TYPE_RGBA, // требуется RGBA формат 16, // 16Bit color depth 0, 0, 0, 0, 0, 0, // Color bits ignored ? 0, // No Alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accumulation buffer bits ignored 16, // 16bit z-buffer (depth buffer) 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main drawing layer 0, // reserved 0, 0, 0 // layer mask ignored }; // Начинаем обрабатывать сообщения switch(msg) { case WM_CREATE: // на этапе создания приложения мы должны настроить // формат пикселей и инициализироваит библиотеку hDC = GetDC(hWnd); // получаем контекст windows // Следующие несколько строк настраивают формат пикселей PixelFormat = ChoosePixelFormat(hDC, &pfd); if(!PixelFormat){ MessageBox(0, "Can't find suitable PixelFormat", "Error", MB_OK|MB_ICONERROR); PostQuitMessage(0); break; } if(!SetPixelFormat(hDC, PixelFormat, &pfd)) { MessageBox(0, "Can't set The PixelFormat", "Error", MB_OK|MB_ICONERROR); PostQuitMessage(0); break; } hRC = wglCreateContext(hDC); // создаем контекст рендеринга if(!hRC) { MessageBox(0, "Can't Create Render Device Context", "Error",MB_OK|MB_ICONERROR); PostQuitMessage(0); break; } if(!wglMakeCurrent(hDC, hRC)) { // устанавливаем его текущим MessageBox(0, "Can't set current Render Device Context", "Error",MB_OK|MB_ICONERROR); PostQuitMessage(0); break; } GetClientRect(hWnd, &Screen); // получаем клиентскую область Initial(Screen.right, Screen.bottom);// инициализация OpenGL break; case WM_CLOSE: case WM_DESTROY: // по CLOSE и/или DESTROY ChangeDisplaySettings(NULL, 0); // восстанавливаем установки wglMakeCurrent(hDC, NULL); // устанавливаем обычный device context wglDeleteContext(hRC); // удаляем контекст рендеринга ReleaseDC(hWnd, hDC); // освобождаем device context PostQuitMessage(0); break; case WM_SIZE: // ну это для resize ( хотя как сделать resize // для fuulscreen application я не знаю ;) ) Resize(LOWORD(lParam), HIWORD(lParam)); break; default: return( DefWindowProc(hWnd, msg, wParam, lParam)); } return(0); }
Дальше все стандартно за исключением того, что заполняется структурка DEVMODE и устанавливаются DisplaySettings:
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShowCmd) { MSG msg; WNDCLASS wc; HWND hWnd; wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "Lirik"; if(!RegisterClass(&wc)) { MessageBox(0,"Error Create window class","Error", MB_OK|MB_ICONERROR); return FALSE; } hWnd = CreateWindow("Skeleton", "Skeleton", WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 800, 600, NULL, NULL, hInst, NULL); if(!hWnd){ MessageBox(0,"Error Create window","Error",MB_OK|MB_ICONERROR); return FALSE; } DEVMODE dmScreenSettings; memset(&dmScreenSettings, 0, sizeof(DEVMODE)); dmScreenSettings.dmSize = sizeof(DEVMODE); dmScreenSettings.dmPelsWidth = 800; dmScreenSettings.dmPelsHeight = 600; dmScreenSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN); ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); SetFocus(hWnd); while(1) { while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { return TRUE; } } Draw(); SwapBuffers(hDC); if(keys[VK_ESCAPE]) SendMessage(hWnd, WM_CLOSE, 0, 0); } }
Теперь, если мы все правильно сделали, вы увидите просто черный экранчик и мышиный курсорчик.
Ну вы не расстраивайтесь.
В следующем шаге мы на этом черненьком экранчике сделаем беленькие фигурки, а потом цветные, а потом 3-х мерные, а потом : стоп, не все сразу.