Skip to content

Instantly share code, notes, and snippets.

@lxfly2000
Last active April 10, 2025 15:28
Show Gist options
  • Save lxfly2000/f31182868679ff6cf84ec505970bc1e4 to your computer and use it in GitHub Desktop.
Save lxfly2000/f31182868679ff6cf84ec505970bc1e4 to your computer and use it in GitHub Desktop.

使用以下几种方法,程序既可获得GPU加速,又能实现带有Alpha值(不会出现锯齿、毛刺等现象)的透明效果。

使用DirectComposition API

然后将Direct3D的ClearRenderTargetView颜色设为透明色就可以了。

优点

Win8才引入的API,没有窗口后表面的中间过程,或许效率是最好的?

缺点

  1. 代码改动较大,操作繁琐;
  2. 貌似对窗口尺寸的感知不太好;
  3. 只能DirectX用;
  4. DComp API只能在Win8及以后的系统中使用;
  5. 微软文档中提到不再推荐使用这个API,而是应用Win10UWP的Windows.UI.Composition,应该只是DComp API的UWP包装吧,但是这样应用范围不就更窄了么?会不会只能在Win10/11上用了呢?(Win32应用调用UWP的API后还能在Win7上运行吗?)

使用DWM API(强烈推荐使用该方法)

参考前两个链接可知,有两个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);

同样需要将清空颜色设为透明色。

优点

  1. 代码简单;
  2. 无论是DirectX、OpenGL还是Vulkan,只要是支持带有透明度格式的API均可用,而不是原作者说的只有OpenGL才能用;
  3. DWM API在Vista中就已经引入了,适用系统范围广。

缺点

根据微软的文档,这个API在Vista和Win7上其实是让Aero效果应用至整个窗口,而非Win10上的单纯就是透明而已,或许需要采用下面的方法把窗口边框隐藏掉才是正确的效果。

MARGINS margins={-1};
DwmExtendFrameIntoClientArea(hWnd,&margins);

同样需要将清空颜色设为透明色。

优点

同上。

缺点

这跟第一个API的区别是只有将边框隐藏掉(下面的方法)才有透明效果,否则它只会让窗口边缘上只有1像素宽的黑框消失(在Win10上是这样的),而且背景是窗体的颜色(一般是纯白色),不会有透明效果。

使用分层窗口

优点

  • 支持Alpha为0处的点击穿透,一般用来实现异形窗口。

缺点

  1. 涉及HDC操作,这个操作只能在CPU上进行,无法利用GPU加速功能,效率低下;
  2. 操作繁琐,一般使用GDI+绘制界面(GDI默认不支持透明度),Direct2D/3D/GL等支持透明度的API也可以;
  3. 不支持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);
}

除非你的程序界面形状非常离谱,不得不详细处理,否则我不建议这样做,因为要处理鼠标穿透只有两种方法:

  1. 使用SetLayeredWindowAttributes的色键,这会导致Alpha像素丢失,窗口出现锯齿边;
  2. 使用UpdateLayeredWindow,涉及HDC的操作,这会导致程序性能低下,且需要注意这个函数会改变窗口位置和大小,不能做缩放操作。

因此若要支持点击穿透应当仔细考虑取舍。

建议使用DxLib

若嫌上述几种方法太麻烦,或者一定要实现点击穿透,则可使用DxLib库,该库含有几乎所有的2D/3D功能,且包括桌宠类程序可能用到的MMD和Live2D功能,只需几行代码就能完成配置。

//……其他初始化
SetWindowStyleMode(2);
SetUseBackBufferTransColorFlag(TRUE);
DxLib_Init();

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment