使用以下几种方法,程序既可获得GPU加速,又能实现带有Alpha值(不会出现锯齿、毛刺等现象)的透明效果。
然后将Direct3D的ClearRenderTargetView颜色设为透明色就可以了。
Win8才引入的API,没有窗口后表面的中间过程,或许效率是最好的?
- 代码改动较大,操作繁琐;
- 貌似对窗口尺寸的感知不太好;
- 只能DirectX用;
- DComp API只能在Win8及以后的系统中使用;
- 微软文档中提到不再推荐使用这个API,而是应用Win10UWP的Windows.UI.Composition,应该只是DComp API的UWP包装吧,但是这样应用范围不就更窄了么?会不会只能在Win10/11上用了呢?(Win32应用调用UWP的API后还能在Win7上运行吗?)
参考前两个链接可知,有两个API可用:
#include <dwmapi.h>
#pragma comment (lib, "dwmapi.lib")
DWM_BLURBEHIND bb = { 0 };
HRGN hRgn = CreateRectRgn(0, 0, -1, -1); //应用毛玻璃的矩形范围,
//参数(0,0,-1,-1)可以让整个窗口客户区变成透明的,而鼠标是可以捕获到透明的区域
bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
bb.hRgnBlur = hRgn;
bb.fEnable = TRUE;
DwmEnableBlurBehindWindow(hWnd, &bb);
同样需要将清空颜色设为透明色。
- 代码简单;
- 无论是DirectX、OpenGL还是Vulkan,只要是支持带有透明度格式的API均可用,而不是原作者说的只有OpenGL才能用;
- DWM API在Vista中就已经引入了,适用系统范围广。
根据微软的文档,这个API在Vista和Win7上其实是让Aero效果应用至整个窗口,而非Win10上的单纯就是透明而已,或许需要采用下面的方法把窗口边框隐藏掉才是正确的效果。
MARGINS margins={-1};
DwmExtendFrameIntoClientArea(hWnd,&margins);
同样需要将清空颜色设为透明色。
同上。
这跟第一个API的区别是只有将边框隐藏掉(下面的方法)才有透明效果,否则它只会让窗口边缘上只有1像素宽的黑框消失(在Win10上是这样的),而且背景是窗体的颜色(一般是纯白色),不会有透明效果。
- WS_EX_LAYERED
- UpdateLayeredWindow(使用ULW_ALPHA参数)
- 涉及HDC操作,这个操作只能在CPU上进行,无法利用GPU加速功能,效率低下;
- 操作繁琐,一般使用GDI+绘制界面(GDI默认不支持透明度),Direct2D/3D/GL等支持透明度的API也可以;
- 不支持Direct3D9,不支持XP及以前的系统。
除非要用到点击穿透,否则尽量不要用这种方法。
利用上述几种方法,再将窗口设置成无边框,则可实现无边框且带有透明度的窗口效果,与屏幕高度融合,适合制作桌面挂件、浮窗、桌宠类应用程序。
SetWindowLong(hWnd,GWL_STYLE,WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_SYSMENU);
SetWindowPos(hWnd,NULL,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);
void ProcessMouseDrag(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static POINT posMouseClick;
switch (message)
{
case WM_LBUTTONDOWN:
SetCapture(hWnd);
posMouseClick.x = LOWORD(lParam);
posMouseClick.y = HIWORD(lParam);
break;
case WM_LBUTTONUP:ReleaseCapture(); break;
case WM_MOUSEMOVE:
if (GetCapture() == hWnd)
{
RECT rWindow;
GetWindowRect(hWnd, &rWindow);
SetWindowPos(hWnd, NULL, rWindow.left + (short)LOWORD(lParam) - posMouseClick.x,
rWindow.top + (short)HIWORD(lParam) - posMouseClick.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
break;
}
}
在窗口过程回调函数的开始处调用上面的函数。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
ProcessMouseDrag(hWnd, message, wParam, lParam);
//……其他处理
return 0;
}
需要将Direct3D等图形API与UpdateLayeredWindow结合使用。
以Direct3D11为例,创建分层窗口:
WNDCLASSEX wcex;
//……其他初始化
m_window = CreateWindowEx(WS_EX_LAYERED, wcex.lpszClassName, L"Direct3D Window", WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr, nullptr, hInstance, nullptr);
创建DXGI交换链时指定GDI兼容:
DXGI_SWAP_CHAIN_DESC swapChainDesc = {/*初始化数据*/};
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;//不能使用带有FLIP的参数,会导致透明度丢失
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE;
dxgiFactory->CreateSwapChain(m_d3dDevice,&swapChainDesc,&m_pSwapChain);
使用UpdateLayeredWindow:(注意绘制时将清空颜色设为透明色)
void Game::Present()
{
ComPtr<IDXGISurface1> surface;
m_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&surface));
HDC hdcSurface, hdcWindow = GetDC(m_window);
surface->GetDC(FALSE, &hdcSurface);
POINT ptOrigin{};
DXGI_SURFACE_DESC desc;
surface->GetDesc(&desc);
SIZE szOrigin{ desc.Width,desc.Height };
BLENDFUNCTION blend{AC_SRC_OVER,0,255,AC_SRC_ALPHA};
//szOrigin参数不能超出surface的尺寸,因为该函数不做缩放操作,而是访问内存,超出就越界了
UpdateLayeredWindow(m_window, hdcWindow, NULL, &szOrigin, hdcSurface, &ptOrigin, 0, &blend, ULW_ALPHA);
surface->ReleaseDC(NULL);
ReleaseDC(m_window, hdcWindow);
m_pSwapChain->Present(1, 0);
}
除非你的程序界面形状非常离谱,不得不详细处理,否则我不建议这样做,因为要处理鼠标穿透只有两种方法:
- 使用SetLayeredWindowAttributes的色键,这会导致Alpha像素丢失,窗口出现锯齿边;
- 使用UpdateLayeredWindow,涉及HDC的操作,这会导致程序性能低下,且需要注意这个函数会改变窗口位置和大小,不能做缩放操作。
因此若要支持点击穿透应当仔细考虑取舍。
建议使用DxLib库
若嫌上述几种方法太麻烦,或者一定要实现点击穿透,则可使用DxLib库,该库含有几乎所有的2D/3D功能,且包括桌宠类程序可能用到的MMD和Live2D功能,只需几行代码就能完成配置。
//……其他初始化
SetWindowStyleMode(2);
SetUseBackBufferTransColorFlag(TRUE);
DxLib_Init();