15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


MCF и OpenGL.

"Подать сюда MFС!!! - кричил он, топая всеми 4-мя лапами."

Сижу тут как-то, программку сочиняю, тут смотрю, царь зверей пожаловал. Вопрос задать пришел. Спрашивает как же OpenGL в MFC то вставить? Сначала думал отмажусь, потом смотрю, настойчивый такой царь попался. Письма шлет, желает знать как же все-таки её туда вставить-то. Вот и решил я примерчик на MFC состроить дабы цари меньше утруждали себя, а больше на солнышке бы нежились, чтоб у царей спокойно и хорошо все было, тогда и нам, простым зверушкам жить хорошо будет. И так поехали.

Для начала сделаем приложение MFC как диалог. Я назвал его BitScroll. Как это делать? Смотрите шаги по MFC.

Теперь, при помощи визарда добавим функцию

BOOL CBitScrollDlg::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
	return CDialog::PreCreateWindow(cs);
}

Помните, мы устанавливали слиль окна в функции CreateWindow ? Так вот это действие по смыслу тоже самое. Напомню как это выглядело в Win32API:

hWnd = CreateWindow("Skeleton", "Skeleton", WS_OVERLAPPEDWINDOW |
	 WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 50, 50,700, 400, NULL,
	 NULL, hInst ,NULL);

Теперь обратимся к функции OnInitDialog(). В ней сначала вызывается функция базового класса, т.е. CDialog, а потом устанавливаются иконки для диалога. Давайте вставим наш код между иконками и CDialog::OnInitDialog().

SetWindowPos(&wndTop, 0, 0, WIDTH, HEIGHT, SWP_NOMOVE);
pDC = GetDC();
CenterWindow();
Init();
SetTimer(1,SPEED, NULL);

Теперь посмотрим, что мы сделали. Сначала сделаем окно нужного нам размера (макросы WIDTH и HEIGHT объявлены так #define WIDTH 640 и #define HEIGHT 480 в заголовочном файле).

Затем получим контекст для рисования. Установимся в центр вселеной и... вот, тут самое интересное, тут мы вызываем нашу собственную функцию, которая будет инициализировать OpenGL (напоминаю, что тоже самое делала ф-ция Initial в программе на Win32API).

А потом включаем таймер, чтобы обеспечить анимацию. Обратимся теперь к Init(). Выглядит она итак:

void CBitScrollDlg::Init()
{
	CRect rect;
	HGLRC hrc;
	if (!bSetupPixelFormat())
		return;
	hrc = wglCreateContext(pDC->GetSafeHdc());
	ASSERT(hrc != NULL);
	wglMakeCurrent(pDC->GetSafeHdc(), hrc);

	GetClientRect(&rect);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClearDepth(1.0);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f, (GLfloat)rect.right / 
		(GLfloat)rect.bottom, 0.1f, 100.0f);
	glMatrixMode(GL_MODELVIEW);

}

Что мы тут делаем? Прежде всего вызываем ф-ию SetupPixelFormat(), это опять наша функция и мы посмотрим ее чуть позже.

Далее, как и раньше, получаем контекст рендеринга (маленькая деталь, ранее pDC(а точнее эта переменная называлась hDC) была объявлена как static HDC hDC, сейчас контекст рисования является пременной типа CDC, а ф-ция wglCreateContext и другие ф-ции OpenGL требуют в качестве аргумента переменную типа HDC. Поэтому мы получаем этот hardware context с помощью pDC->GetSafeHdc()).

Затем делаем этот контекст текущим и настраиваем область отображения, так как это делалось в Initial() (Win32API)

Функция bSetupPixelFormat() содержит следующее:

BOOL CBitScrollDlg::bSetupPixelFormat()
{
	static PIXELFORMATDESCRIPTOR pfd =
	{
		sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
		1, // version number
		PFD_DRAW_TO_WINDOW | // support window
		PFD_SUPPORT_OPENGL | // support OpenGL
		PFD_DOUBLEBUFFER, // double buffered
		PFD_TYPE_RGBA, // RGBA type
		24, // 24-bit 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, // accum bits ignored
		32, // 32-bit z-buffer
		0, // no stencil buffer
		0, // no auxiliary buffer
		PFD_MAIN_PLANE, // main layer
		0, // reserved
		0, 0, 0 // layer masks ignored
	};

	int pixelformat;

	if ((pixelformat = ChoosePixelFormat(pDC->GetSafeHdc(), &pfd)) == 0)
	{
		MessageBox("ChoosePixelFormat failed");
		return FALSE;
	}

	if (SetPixelFormat(pDC->GetSafeHdc(), pixelformat, &pfd) == FALSE)
	{
		MessageBox("SetPixelFormat failed");
		return FALSE;
	}
	return TRUE;
}

Как не трудно заметить, она почти полностью взята из Win32API приложения за исключением того, что hDC заменена на pDC->GetSafeHdc()

Теперь добавим в нашу программку обработчик от таймера (мы его недавно сделали в ф-ции Init())

void CBitScrollDlg::OnTimer(UINT nIDEvent)
{
	DrawScene();
	CDialog::OnTimer(nIDEvent);
}

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

void CBitScrollDlg::DrawScene()
{
	static GLfloat angle = 0;
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glTranslatef(0.0f, 0.0f, -2.0f);
	glRotatef(angle, 1.0f, 0.5f, 0.3f);
	glBegin(GL_QUADS);
	glColor3f(1.0f, 0.0f, 1.0f);
	glVertex3f(-0.5f, 0.5f, 0.0f);
	glColor3f(1.0f, 1.0f, 1.0f);
	glVertex3f(0.5f, 0.5f, 0.0f);
	glColor3f(0.0f, 1.0f, 1.0f);
	glVertex3f(0.5f, -0.5f, 0.0f);
	glColor3f(0.0f, 0.0f, 1.0f);
	glVertex3f(-0.5f, -0.5f, 0.0f);
	glEnd();
	glFinish();
	SwapBuffers(wglGetCurrentDC());
	angle += 0.5f;
}

По моему проще уже некуда. Я надеюсь, что все ясно.

Ну вот казалось бы и все. Единственное, что остается сделать - убраться за собой. Т.е. надо при выходе удалить контекст рендеринга и убить таймер.

Эти вещи надо сделать в 2-х обработчиках OnClose и OnDestroy. Посмотрим на них:

void CBitScrollDlg::OnClose()
{
	// TODO: Add your message handler code here and/or call default
	HGLRC hrc;
	KillTimer(1);
	hrc = ::wglGetCurrentContext();
	::wglMakeCurrent(NULL, NULL);
	if (hrc)
		::wglDeleteContext(hrc);
	CDialog::OnClose();
}

и

void CBitScrollDlg::OnDestroy()
{
	CDialog::OnDestroy();
	// TODO: Add your message handler code here
	HGLRC hrc;
	KillTimer(1);
	hrc = ::wglGetCurrentContext();
	::wglMakeCurrent(NULL, NULL);
	if (hrc)
		::wglDeleteContext(hrc);
}

Она приактически одинаковые. Работают так же как и case WM_CLOSE в Win32API. Т.е. убиваем таймер, получаем контекст рендеринга, если он есть - удаляем его.

Ну вот и все. Еще добавлю, что в проекте есть ф-ция OnSize для обработки изменений размеров окна. В данном случае она не нужна, но если вставлять OpenGL в SDI или MDI, то она вам понадобится.