Шаг 8 - Готовим каркас

В предыдущих шагах мы познакомились с основными понятиями 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-х мерные, а потом : стоп, не все сразу.


Загрузить проект | Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Kirill V. Ratkin - 14.01.2000