在学习熟悉 CubismSDK 的时候,曾给轴伊Joi制作过一个简单的 Live2D 桌面宠物;由于是在官方样例的基础上进行的修改,因此程序主题通过 glew + glfw 来进行实现。由于桌面宠物的特殊性(需要尽可能减少对桌面操作的影响),可以说是必须实现异形窗口。这个异形窗口与一般的需求还不太一样:通常异形窗口是静态的,仅以一张图片作为底图,有很多种方法可以实现,其中一种便是用蒙版(Mask)来实现,但这种方式在桌面宠物这种场景下显得有点尴尬。
对于用 OpenGL 动态渲染的桌面宠物来说,读取当前 Buffer 生成 Mask 是效率极低的。好在 Windows 下提供了 SetLayeredWindowAttributes(hwnd,RGB(0, 0, 0), 255, LWA_COLORKEY)
这一方法,直接进行键值抠图即可。但问题是其精度极低,渲染出的模型边缘会出现很明显的底色锯齿边缘。
在 glfw 中,通过 glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE)
可实现窗口 Buffer 新增 Alpha 通道;配合 LWA_COLORKEY
即可实现无锯齿边缘的 OpenGL 渲染的即时异形窗口。
然而最近在使用 Qt 对其重构的过程中,又遇到了异形窗口的这一问题。Qt 提供了两种使用 OpenGL 的方式,QOpenGLWindow 与 QOpenGLWidget;这两种方式在使用上几乎没有差异,可以很方便的互相转换。但在实现异形窗口的过程中遇到了问题。
通过实践发现,QOpenGLWidget 通过设置 Qt::WA_TranslucentBackground
可以很简单的实现透明背景;但 LWA_COLORKEY
只对 WS_EX_LAYERED
样式的窗口生效;经过测试,QOpenGLWidget 单独作为窗口时,无法设置 WS_EX_LAYERED
样式,因此无法实现异形窗口。
QOpenGLWindow 可以直接设置 WS_EX_LAYERED
样式,但无法设置透明背景(可使用 setFormat
添加 alpha 通道属性,但配合 LWA_COLORKEY
会显示异常);因此需要自行实现 GLFW_TRANSPARENT_FRAMEBUFFER
的功能。
通过阅读 glfw 源码,该设置相关的代码如下所示:
1#define DWM_BB_ENABLE 0x00000001
2#define DWM_BB_BLURREGION 0x00000002
3typedef struct
4{
5 DWORD dwFlags;
6 BOOL fEnable;
7 HRGN hRgnBlur;
8 BOOL fTransitionOnMaximized;
9} DWM_BLURBEHIND;
10
11typedef HRESULT(WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*);
12
13void setTransparentBuffer(HWND hwnd)
14{
15 auto dll = LoadLibraryA("dwmapi.dll");
16 auto DwmEnableBlurBehindWindow = (PFN_DwmEnableBlurBehindWindow)GetProcAddress((HMODULE) dll, "DwmEnableBlurBehindWindow");
17 HRGN region = CreateRectRgn(0, 0, -1, -1);
18 DWM_BLURBEHIND bb = {0};
19 bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
20 bb.hRgnBlur = region;
21 bb.fEnable = TRUE;
22
23 DwmEnableBlurBehindWindow(hwnd, &bb);
24 DeleteObject(region);
25}
最终使用 QOpenGLWindow,手动设置透明 FrameBuffer,然后设置 LWA_COLORKEY
,实现了完美的 OpenGL 内容的异形窗口。