将GLFW窗口嵌入Win32 SDK窗口及其多线程渲染方法

这篇文章(MFC单文档视图中嵌入GLFW窗口)提到了glfw嵌入mfc的办法,采用的查找进程PID再嵌入的方法,进程间通信采用UDP,略微繁琐。

其实不必如此麻烦,SetParent直接就可以办到。

先上最终效果,其中的三角形是实时旋转的:
在这里插入图片描述

第1步 创建标准Win32 SDK窗口

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT ("HelloWin") ;
	HWND   hwnd ;
	MSG    msg ;
	WNDCLASS wndclass ;//WNDCLASSEX比WNDCLASS多两个结构成员--cbSize(指定WNDCLASSEX结构的大小--字节)  --hIconSm(标识类的小图标)
	wndclass.style        = CS_HREDRAW | CS_VREDRAW ;
	wndclass.lpfnWndProc  = WndProc ;
	wndclass.cbClsExtra   = 0 ;
	wndclass.cbWndExtra   = 0 ;
	wndclass.hInstance    = hInstance ;
	wndclass.hIcon        = LoadIcon (NULL, IDI_APPLICATION) ;
	wndclass.hCursor      = LoadCursor (NULL, IDC_ARROW) ;
	wndclass.hbrBackground= (HBRUSH) (COLOR_WINDOW+1) ;//白色//(HBRUSH)(COLOR_MENU  +1)界面灰
	wndclass.lpszMenuName  = NULL ;
	wndclass.lpszClassName= szAppName ;

	if (!RegisterClass (&wndclass))
	{
		MessageBox (  NULL, TEXT ("This program requires Windows NT!"),szAppName, MB_ICONERROR) ;
		return 0 ;
	}
	hwnd = CreateWindow( szAppName,      // window class name
		TEXT ("The Hello Program"),   // window caption
		WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU,
		CW_USEDEFAULT,// initial x position
		CW_USEDEFAULT,// initial y position
		CW_USEDEFAULT,// initial x size
		CW_USEDEFAULT,// initial y size
		NULL,                 // parent window handle
		NULL,            // window menu handle
		hInstance,   // program instance handle
		NULL) ;      // creation parameters

	ShowWindow (hwnd, iCmdShow) ;
	UpdateWindow (hwnd) ;

	while (GetMessage (&msg, NULL, 0, 0))
	{
		TranslateMessage (&msg) ;
		DispatchMessage (&msg) ;
	}
	return msg.wParam ;
}


LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
		return 0;
	case WM_PAINT:
		HDC hdc;
		PAINTSTRUCT ps ;
		hdc = BeginPaint(hwnd, &ps);
		//DrawText (hdc, s, -1, &rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
		EndPaint(hwnd, &ps);
		return 0 ;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0 ;
	case WM_QUIT:
		return 0;
	}

	return DefWindowProc (hwnd, message, wParam, lParam);
}

这一长段麻烦又难记,我每次都是复制粘贴了再改。要么就是直接用封装好的窗口类(涉及静态函数做消息转发,有人看再写吧,再说这部分内容网上也多)。

第2步 在WinMain中创建glfw窗口

在CreateWindow后加入glfw的窗口创建过程:

	//初始化glfw
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//创建glfw窗口
	window = glfwCreateWindow(400, 400, "openGL", NULL, NULL);	
	if (window == NULL)
	{
		OutputDebugString("Failed to create GLFW window");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	//注册glad函数地址
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		OutputDebugString("Failed to initialize GLAD");
		return -1;
	}
	glViewport(0, 0, 400, 400);

	//背景颜色
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);

添加include,链接好lib,然后上面这段直接插入到主消息循环之前(就是WinMain里那个while的前面)。
注意:有人说glfw的init只能做一次,做多次会发生其他窗口不渲染的bug,我自己没有试过。

此时会打开2个窗口,并且glClearColor设置的颜色并没有生效,单独关闭opengl窗口也没反应。第1个问题是因为opengl的渲染循环没有建立;第2个问题是因为GLFW截获了WM_CLOSE消息的响应,要设置glfwSetWindowShouldClose才能让他捕获关闭事件。
在这里插入图片描述

第3步 使用SetParent将GLFW窗口嵌入主窗口

继续加入代码:

	//取得glfw窗口句柄并将其嵌入父窗口
	HWND hwndGLFW = glfwGetWin32Window(window);
	SetWindowLong(hwndGLFW, GWL_STYLE, WS_VISIBLE);
	MoveWindow(hwndGLFW, 0, 0, 400, 400, TRUE);
	SetParent(hwndGLFW, hwnd);

注意glfwGetWin32Window函数是不在glfw3.h里的,要在include处加入以下代码才能使用:

#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>

SetWindowLong这句是重设窗口外观,不加的话GLFW的窗口会嵌入主窗口,但是标题栏什么的一应俱全,只是不能拖出主窗口外而已。不加的话效果就像这样:
在这里插入图片描述
MoveWindow这句如果不加的话,因为GLFW窗口弹出的位置不固定,所有你会发现每次打开主程序时GLFW窗口都在随机的位置。

第4步 在主消息循环中加入opengl渲染

在主消息循环中加入:

	if (!glfwWindowShouldClose(window))
	{

		//刷新颜色缓冲和深度
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

现在的主消息循环长这样:

	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!glfwWindowShouldClose(window))
		{
			//刷新颜色缓冲和深度
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			glfwSwapBuffers(window);
			glfwPollEvents();
		}

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

此时opengl已经开始渲染了,可以看到底色了。
在这里插入图片描述
然后可以画个三角形,再让它随时间旋转。如果这样做了,你就会发现,只有鼠标在窗口上不停移动,三角形才会转,一停下就不转了。这是因为主消息循环只有在接收到消息时才刷新,只有你不停地造,动鼠标啊,按键盘啊,拖滚轮什么的它才更新。

这显然不符合要求。所以我们还需要开一个新线程。

第5步 使用多线程为GLFW窗口进行渲染

加入一个函数:

void RenderProc()
{
	glfwMakeContextCurrent(window);
	while (!glfwWindowShouldClose(window))
	{
		float time = glfwGetTime();

		//刷新颜色缓冲和深度
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		shader->UseProgram();
		shader->Uniform("angle", time);

		triangle->Bind();
		triangle->DrawTriangles();

		//
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	delete shader;
	delete triangle;
	glfwTerminate();
}

其中的shader和triangle分别是对着色器和VAO的封装。其初始化函数为:

void Init()
{
	float triangle_vertex[] =
	{
		0.0f,0.5f,0.0f,1.0f,0.0f,0.0f,
		-0.5f,-0.5f,0.0f,0.0f,1.0f,0.0f,
		0.5f,-0.5f,0.0f,0.0f,0.0f,1.0f
	};
	triangle=new TVertexArray(sizeof(triangle_vertex), triangle_vertex, { 3,3 });

	shader=new TShader("tri_vertex.glsl", "tri_fragment.glsl");
}

在glClearColor后加入:

	Init();

	//将渲染移交线程前需要将当前上下文设为null
	glfwMakeContextCurrent(NULL);

	std::thread RenderThread(RenderProc);

这里首先初始化了shader和triangle指针,然后将opengl的context设为Null,这是因为glfwMakeContextCurrent的说明里说了,将渲染函数移交到新线程的时候,要先在旧线程里把上下文设为空,再在新的线程里设置上下文。否则的话,在渲染中GetLocation和VAO的绑定操作等都会出错。

因为渲染已经移交新线程,主消息循环可以删掉和glfw, opengl相关的内容了。
然后消息循环后需要把thread阻塞一下,确认关闭:

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	RenderThread.join();

最后在WndProc中WM_DESTROY消息处加入glfwSetWindowShouldClose,让主窗口带着glfw窗口关闭:

	case WM_DESTROY:
		glfwSetWindowShouldClose(window, true);

		PostQuitMessage(0);
		return 0;

现在的关闭流程是这样:
主窗口点击关闭->WM_DESTROY消息发出->glfwSetWindowShouldClose函数设置glfw窗口可以关闭->PostQuitMessage函数调用->主消息循环退出->RenderThread线程阻塞,等待glfw窗口、渲染循环以及RenderThread线程退出(glfw窗口可能在主消息循环结束前就已经退出,此处阻塞主要起检查并等待的作用)->主程序返回

现在流程就很完善了。写个旋转三角形,三角形可以不停旋转,主窗口也可以正常响应。

最终效果:
在这里插入图片描述
最后是整个main.cpp文件:

#include <windows.h>

#include <thread>
#include <memory>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>

#include "TShader.h"
#include "TVertexArray.h"

GLFWwindow* window;
TVertexArray *triangle;
TShader *shader;
void Init();
void RenderProc();


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT("HelloWin");
	HWND   hwnd;
	MSG    msg;
	WNDCLASS wndclass;//WNDCLASSEX比WNDCLASS多两个结构成员--cbSize(指定WNDCLASSEX结构的大小--字节)  --hIconSm(标识类的小图标)
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);//白色//(HBRUSH)(COLOR_MENU  +1)界面灰
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAppName;

	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
		return 0;
	}
	hwnd = CreateWindow(szAppName,      // window class name
		TEXT("The Hello Program"),   // window caption
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_SIZEBOX,
		CW_USEDEFAULT,// initial x position
		CW_USEDEFAULT,// initial y position
		CW_USEDEFAULT,// initial x size
		CW_USEDEFAULT,// initial y size
		NULL,                 // parent window handle
		NULL,            // window menu handle
		hInstance,   // program instance handle
		NULL);      // creation parameters

	//初始化glfw
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//创建glfw窗口
	window = glfwCreateWindow(400, 400, "openGL", NULL, NULL);	
	if (window == NULL)
	{
		OutputDebugString("Failed to create GLFW window");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	//注册glad函数地址
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		OutputDebugString("Failed to initialize GLAD");
		return -1;
	}
	glViewport(0, 0, 400, 400);

	//取得glfw窗口句柄并将其嵌入父窗口
	HWND hwndGLFW = glfwGetWin32Window(window);
	SetWindowLong(hwndGLFW, GWL_STYLE, WS_VISIBLE);
	MoveWindow(hwndGLFW, 0, 0, 400, 400, TRUE);
	SetParent(hwndGLFW, hwnd);

	//背景颜色
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);

	Init();

	//将渲染移交线程前需要将当前上下文设为null
	glfwMakeContextCurrent(NULL);

	std::thread RenderThread(RenderProc);

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	RenderThread.join();

	return msg.wParam;
}

void Init()
{
	float triangle_vertex[] =
	{
		0.0f,0.5f,0.0f,1.0f,0.0f,0.0f,
		-0.5f,-0.5f,0.0f,0.0f,1.0f,0.0f,
		0.5f,-0.5f,0.0f,0.0f,0.0f,1.0f
	};
	triangle=new TVertexArray(sizeof(triangle_vertex), triangle_vertex, { 3,3 });

	shader=new TShader("tri_vertex.glsl", "tri_fragment.glsl");
}

void RenderProc()
{
	glfwMakeContextCurrent(window);
	while (!glfwWindowShouldClose(window))
	{
		float time = glfwGetTime();

		//刷新颜色缓冲和深度
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		shader->UseProgram();
		shader->Uniform("angle", time);

		triangle->Bind();
		triangle->DrawTriangles();

		//
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	delete shader;
	delete triangle;
	glfwTerminate();
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
		return 0;
	case WM_PAINT:
		HDC hdc;
		PAINTSTRUCT ps;
		RECT rect;
		GetClientRect(hwnd, &rect);
		hdc = BeginPaint(hwnd, &ps);
		DrawText (hdc, "This is text", -1, &rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		glfwSetWindowShouldClose(window, true);

		PostQuitMessage(0);
		return 0;
	case WM_QUIT:
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

至于opengl窗口和主窗口的通信,就按多线程的通信方式来就行,最简单就直接用全局变量,其他地方写,RenderProc里读,就可以修改渲染内容了。或者用mutex啊condition_variable这些设施进行双向通信都行。

限于篇幅TShader类和TVertexArray类就不粘贴了,你看过LearnOpenGL网站的话相信能写出来,或者替换成你自己的渲染过程也行。

参考文献
[1] MFC单文档视图中嵌入GLFW窗口
[2] cv::namedWindow, GLFWwindow以及其他程序嵌入到MFC中的教程

  • 8
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 在使用GLFW创建窗口后,可以通过OpenGL来绘制背景和其他内容。 为了给GLFW窗口绘制背景,我们首先需要在窗口的绘制循环中清除当前的帧缓冲区。可以使用glClearColor函数来设置清除颜色,并使用glClear函数将帧缓冲区中的颜色缓冲区清除为指定的颜色。 ``` // 设置清除颜色为白色 glClearColor(1.0f, 1.0f, 1.0f, 1.0f); while (!glfwWindowShouldClose(window)) { // 清除颜色缓冲区 glClear(GL_COLOR_BUFFER_BIT); // 绘制其他内容... glfwSwapBuffers(window); glfwPollEvents(); } ``` 之后,在绘制循环中可以通过OpenGL的绘制函数来绘制其他内容,例如绘制点、线段、面或者贴图等等。可以使用glBegin和glEnd配合不同的绘制模式(例如GL_POINTS、GL_LINES、GL_TRIANGLES等)以及各种绘制函数(例如glVertex2f、glVertex3f、glColor3f等)来绘制不同的图形。 ``` while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); // 绘制背景 // ... // 绘制其他内容 glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(-0.5f, -0.5f); glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(0.5f, -0.5f); glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(0.0f, 0.5f); glEnd(); glfwSwapBuffers(window); glfwPollEvents(); } ``` 通过以上的步骤,就可以在GLFW窗口中先绘制背景,然后在上面绘制其他内容。 ### 回答2: glfw是一个用于创建窗口和处理用户输入的库。要在glfw窗口上绘制背景并在其上方绘制其他内容,可以按照以下步骤进行操作: 首先,需要创建一个glfw窗口。这可以通过使用glfwInit()初始化glfw库,然后通过glfwCreateWindow()函数创建一个窗口来完成。确保为窗口设置适当的屏幕宽度和高度。 接下来,您可以使用OpenGL或其他图形库来在glfw窗口中绘制背景。可以使用glClearColor()函数设置要用于清除窗口的背景颜色,然后使用glClear()函数清除窗口,并填充所选背景颜色。例如,可以将背景颜色设置为红色,然后用红色填充整个窗口。 一旦完成绘制背景,您就可以在窗口上绘制其他内容。这可以通过编写绘制函数来实现。绘制函数可以使用OpenGL或其他图形库中的图形绘制函数来绘制所需的形状、图形或其他内容。 最后,在glfw窗口的循环中,您可以将刚才编写的绘制函数调用添加到绘制循环的合适位置。这样,在每次循环迭代中,窗口都会被清除并显示出背景,然后在其上方重新绘制内容。根据需要,可以多次调用绘制函数以在窗口上叠加显示多个内容。 总之,您可以通过使用glfw库来创建窗口和处理用户输入,并使用OpenGL或其他图形库来在窗口中绘制背景和其他内容。确保在绘制循环中适时调用绘制函数,以保证在背景的基础上叠加绘制其他内容。 ### 回答3: 在使用GLFW创建窗口并绘制背景后,在上面绘制其他内容,你可以按照以下步骤进行: 1. 使用GLFW创建窗口,并设置窗口的属性,如窗口大小、标题等。 2. 在GLFW主循环之前,准备好需要绘制的背景和其他内容的数据。 - 首先,使用OpenGL函数清除颜色缓冲区,将背景设置为所需颜色或材质。 - 然后,准备其他你想要绘制的物体的顶点或片段着色器数据。 3. 在主循环中,反复执行以下步骤: - 使用glClear函数清除颜色缓冲区,以便在新的帧中绘制内容。 - 使用glDrawArrays或glDrawElements等OpenGL函数,绘制需要的背景对象。这可以是一个简单的全屏矩形,使用指定的背景材质或颜色。 - 使用glDrawArrays或glDrawElements绘制其他内容。这取决于你希望如何绘制这些对象,可以是2D或3D的。 - 使用glfwSwapBuffers函数,将绘制的结果显示在窗口上。 - 处理窗口事件,例如按键、鼠标移动等。 以上是基本的实现步骤。当然,具体的代码实现将依赖于你使用的编程语言和图形库。你可以使用OpenGL和GLFW的任何版本,根据你的需求进行修改和调整。 希望以上回答对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值