尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DirectX游戏截屏原理与Visual C++实现方案

DirectX游戏截屏原理与Visual C++实现方案 简介本资源是一份面向C游戏开发初学者与DirectX图形编程实践者的实用技术方案聚焦解决DirectX硬件加速游戏无法使用系统截屏键如PrtScn截图的核心痛点。资源提供完整的Visual C工程实现包含Direct3D 11环境下从交换链捕获帧缓冲、转换为BMP格式并保存的完整代码逻辑适用于游戏调试、画面分享及教学演示等场景。压缩包共12个文件含4个头文件.h定义接口与结构体、2个源文件.cpp实现核心截屏逻辑、1个资源脚本.rc及解决方案配置.sln/.vcproj另有图标、预编译头和说明文档总大小仅22KB轻量易读。已有558人学习下载读者可直接编译运行参考工程深入理解Map/Unmap内存映射、CopyResource资源拷贝等关键API调用时机与注意事项并结合附带的‘hardware overlay抓不了屏’说明文本掌握绕过硬件覆盖层的技术原理与实践路径。1. DirectX 游戏截屏失效不是 bug是显卡驱动绕过了 GDI——Visual C 必须用 DXGI 或 D3D9/11 API 主动抓帧你按下 PrintScreen 键桌面截图正常但《绝地求生》《CS2》《原神》《崩坏星穹铁道》等主流 DirectX 游戏却一片黑屏或返回纯色背景——这不是系统故障也不是显卡坏了。根本原因是这些游戏启用硬件加速后渲染管线完全脱离 Windows GDI/GDI 图形子系统GPU 直接将帧写入显存专用缓冲区如 back buffer、swap chain不经过桌面窗口管理器DWM合成。Windows 截图 API如BitBlt、PrintWindow、Graphics.CopyFromScreen只能捕获 DWM 合成后的最终画面而 DirectX 游戏在独占全屏或启用DXGI_SWAP_EFFECT_DISCARD模式时其帧数据根本不进入 DWM 流水线。Visual C 开发者若想可靠截屏必须绕过 GDI用 DirectX 自身的接口DXGI、ID3D11DeviceContext::CopyResource、ID3D9Surface::GetDC从 GPU 显存中直接读取原始像素。本方案不依赖第三方工具如 OBS 的 hook 模块、不修改游戏进程内存、不触发反作弊检测仅通过合法 DirectX 设备枚举与资源拷贝实现适用于 DirectX 9/10/11/12 兼容环境且对 Visual C 2015–2022 工具链完全兼容。2. 为什么 GDI 截图在 DirectX 游戏中失效从 DXGI Swap Chain 到显存映射的底层机制2.1 DirectX 渲染路径彻底绕过 DWMSwap Chain 的三种模式决定截屏可行性Windows 桌面窗口管理器DWM仅能访问被标记为“可合成”的表面composable surface。DirectX 应用通过IDXGISwapChain::Present()提交帧时其行为取决于创建 Swap Chain 时指定的DXGI_SWAP_EFFECTSwap Effect是否进入 DWM 合成GDI 截图是否有效常见场景DXGI_SWAP_EFFECT_DISCARD❌ 否显存直写❌ 黑屏/纯色全屏独占、高性能游戏默认DXGI_SWAP_EFFECT_SEQUENTIAL✅ 是经 DWM✅ 可用BitBlt窗口化 DirectX 应用部分旧游戏DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL✅ 是DWM 优化路径✅ 可用DesktopDuplicationWindows 10 窗口化 DX11/12 游戏提示绝大多数现代 DirectX 游戏尤其全屏模式使用DISCARD模式此时GetDC()获取的 HWND DC 不包含真实渲染内容BitBlt拷贝的是未更新的前台缓冲区残留或系统默认背景色。2.2 DXGI Desktop Duplication API唯一官方支持的“无侵入”截屏方案微软自 Windows 8 起提供IDXGIOutputDuplication接口允许应用以只读方式镜像整个显示器输出流。它不挂钩游戏进程不注入 DLL不修改显存权限而是由 DXGI 驱动层在帧提交后、DWM 合成前将当前帧副本交付给调用方。该方案需满足Windows 8.1 或更高版本显卡驱动支持 WDDM 1.2当前用户拥有桌面会话权限不能在服务会话中运行以下代码在 Visual C 中初始化 Desktop Duplication 并获取首帧// 初始化 DXGI Factory 和 Output ComPtrIDXGIFactory1 factory; CreateDXGIFactory1(__uuidof(IDXGIFactory1), factory); ComPtrIDXGIAdapter1 adapter; factory-EnumAdapters1(0, adapter); ComPtrIDXGIOutput output; adapter-EnumOutputs(0, output); ComPtrIDXGIOutput1 output1; output.As(output1); // 创建 Desktop Duplication 接口 ComPtrIDXGIOutputDuplication duplication; output1-DuplicateOutput(nullptr, duplication); // nullptr 表示当前桌面会话 // 获取帧数据 DXGI_OUTDUPL_FRAME_INFO frameInfo {}; ComPtrIDXGIResource desktopResource; HRESULT hr duplication-AcquireNextFrame(1000, frameInfo, desktopResource); if (SUCCEEDED(hr) desktopResource) { ComPtrID3D11Texture2D texture; desktopResource.As(texture); // 此 texture 即为当前桌面完整帧含 DirectX 游戏画面 }参数说明AcquireNextFrame(1000, ...)中1000是超时毫秒数避免无限阻塞frameInfo.LastPresentTime可用于计算帧率desktopResource是只读纹理不可写入确保游戏渲染不受干扰。2.3 为何不推荐 Hook D3D9/11 设备反作弊与稳定性风险部分开发者尝试通过Detour或IAT Hook拦截游戏进程中的IDirect3DDevice9::Present或ID3D11DeviceContext::Present在 Present 前调用GetRenderTargetData或CopyResource抓帧。此法虽技术可行但存在三重硬伤反作弊拦截Easy Anti-Cheat、BattlEye、Riot Vanguard 等引擎级反作弊系统主动扫描Detour内存特征、IAT 修改痕迹、非白名单 DLL 注入Hook 行为极易触发封禁线程安全缺陷Present 调用常在渲染线程非 UI 线程执行Hook 后同步拷贝显存易引发 GPU 资源竞争导致游戏卡顿或崩溃API 兼容断裂DirectX 12 使用显式资源屏障ID3D12CommandList::ResourceBarrierHook 成本指数级上升且CopyTextureRegion需手动管理D3D12_RESOURCE_STATES错误状态切换即致设备丢失。注意标题中“必须用 DX 写个代码在游戏中截屏”实为误导——正确做法是独立进程通过 DXGI 枚举目标游戏窗口的输出设备而非注入游戏进程。本方案全程运行于外部 Visual C 控制台程序与游戏进程隔离。3. Visual C 实现 DirectX 游戏截屏从项目配置到 DXGI 帧捕获完整流程3.1 Visual Studio 项目配置启用 Windows SDK 10 与 DirectX SDK 兼容头Visual C 2017 及以后版本不再自带 DirectX SDK需改用 Windows SDK 内置头文件。关键配置步骤平台工具集项目属性 → 通用属性 → 平台工具集 → 选择v143VS2022或v142VS2019Windows SDK 版本项目属性 → 通用属性 → Windows SDK 版本 →10.0.22621.0Win11 22H2或10.0.19041.0Win10 2004附加包含目录项目属性 → 配置属性 → C/C → 常规 → 附加包含目录 → 添加$(WindowsSdkDir)Include\um;$(WindowsSdkDir)Include\shared附加库目录项目属性 → 配置属性 → 链接器 → 常规 → 附加库目录 → 添加$(WindowsSdkDir)Lib\um\x64x64或$(WindowsSdkDir)Lib\um\x86x86链接库项目属性 → 配置属性 → 链接器 → 输入 → 附加依赖项 →dxgi.lib;d3d11.lib;user32.lib;gdi32.lib。提示若编译报错error C2065: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL : undeclared identifier说明 Windows SDK 版本过低需 ≥10.0.17134.0请升级 SDK 或手动定义宏#define DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 3。3.2 核心截屏类DXScreenshot封装 DXGI 输出枚举与帧拷贝逻辑以下为可直接编译的 Visual C 类定义DXScreenshot.h支持自动定位目标游戏窗口并捕获其所在显示器帧#pragma once #include dxgi.h #include d3d11.h #include vector #include string class DXScreenshot { private: ComPtrIDXGIFactory1 m_factory; ComPtrIDXGIOutput1 m_output; ComPtrIDXGIOutputDuplication m_duplication; ComPtrID3D11Device m_device; ComPtrID3D11DeviceContext m_context; ComPtrID3D11Texture2D m_stagingTexture; public: bool Initialize(HWND targetHwnd); bool CaptureFrame(std::vectorBYTE outPixels, UINT width, UINT height); void Cleanup(); private: bool FindOutputForWindow(HWND hwnd); bool CreateStagingTexture(const DXGI_OUTPUT_DESC desc); };3.2.1Initialize(HWND targetHwnd)定位游戏窗口所属显示器输出bool DXScreenshot::Initialize(HWND targetHwnd) { // 1. 创建 DXGI Factory if (FAILED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), m_factory))) { return false; } // 2. 根据窗口句柄获取其所在显示器 HMONITOR hMonitor MonitorFromWindow(targetHwnd, MONITOR_DEFAULTTONEAREST); if (!hMonitor) return false; MONITORINFOEX monitorInfo {}; monitorInfo.cbSize sizeof(monitorInfo); if (!GetMonitorInfo(hMonitor, monitorInfo)) return false; // 3. 枚举适配器查找匹配的输出 ComPtrIDXGIAdapter1 adapter; for (UINT i 0; m_factory-EnumAdapters1(i, adapter) ! DXGI_ERROR_NOT_FOUND; i) { ComPtrIDXGIOutput output; for (UINT j 0; adapter-EnumOutputs(j, output) ! DXGI_ERROR_NOT_FOUND; j) { DXGI_OUTPUT_DESC desc {}; output-GetDesc(desc); if (wcscmp(desc.DeviceName, monitorInfo.szDevice) 0) { output.As(m_output); break; } } if (m_output) break; } if (!m_output) return false; // 4. 创建 Desktop Duplication 接口 if (FAILED(m_output.As(m_output)-DuplicateOutput(nullptr, m_duplication))) { return false; } // 5. 创建 D3D11 设备用于纹理操作 D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL_11_0 }; if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, m_device, nullptr, m_context))) { return false; } // 6. 获取输出描述并创建暂存纹理 DXGI_OUTPUT_DESC desc {}; m_output-GetDesc(desc); return CreateStagingTexture(desc); }逻辑说明MonitorFromWindow确保获取游戏窗口物理显示器EnumOutputs匹配szDevice字符串如\\.\DISPLAY1精准定位对应IDXGIOutput避免多显示器环境下抓错屏幕。D3D11CreateDevice创建硬件设备BGRA_SUPPORT标志启用DXGI_FORMAT_B8G8R8A8_UNORM格式兼容性。3.2.2CaptureFrame(...)执行帧捕获并导出为 RGB24 像素数组bool DXScreenshot::CaptureFrame(std::vectorBYTE outPixels, UINT width, UINT height) { DXGI_OUTDUPL_FRAME_INFO frameInfo {}; ComPtrIDXGIResource desktopResource; HRESULT hr m_duplication-AcquireNextFrame(1000, frameInfo, desktopResource); if (FAILED(hr)) { if (hr DXGI_ERROR_WAIT_TIMEOUT) return false; // 超时无新帧 return false; } // 将 DXGI 资源转换为 D3D11 纹理 ComPtrID3D11Texture2D texture; desktopResource.As(texture); // 拷贝到 CPU 可读的暂存纹理 m_context-CopyResource(m_stagingTexture.Get(), texture.Get()); // 映射暂存纹理获取像素数据 D3D11_MAPPED_SUBRESOURCE map; if (FAILED(m_context-Map(m_stagingTexture.Get(), 0, D3D11_MAP_READ, 0, map))) { m_duplication-ReleaseFrame(); return false; } // 计算尺寸Desktop Duplication 返回整个显示器分辨率 width static_castUINT(map.Width); height static_castUINT(map.DepthOrArraySize); size_t pitch map.RowPitch; size_t dataSize pitch * height; outPixels.resize(dataSize); BYTE* dst outPixels.data(); BYTE* src static_castBYTE*(map.pData); // BGRA → RGB 转换Windows 默认为 BGRA for (UINT y 0; y height; y) { for (UINT x 0; x width; x) { size_t srcOffset y * pitch x * 4; size_t dstOffset y * width * 3 x * 3; dst[dstOffset 0] src[srcOffset 2]; // R dst[dstOffset 1] src[srcOffset 1]; // G dst[dstOffset 2] src[srcOffset 0]; // B } } m_context-Unmap(m_stagingTexture.Get(), 0); m_duplication-ReleaseFrame(); // 必须调用否则 AcquireNextFrame 阻塞 return true; }参数说明map.RowPitch是每行字节数含内存对齐填充不可直接用width * 4ReleaseFrame()是强制释放帧锁遗漏将导致后续AcquireNextFrame永久阻塞RGB 转换因 Windows DXGI 默认格式为DXGI_FORMAT_B8G8R8A8_UNORM需交换 B/R 通道。4. 截屏结果保存与性能调优BMP 文件生成、帧率控制与多显示器处理4.1 保存为标准 BMP 文件无需 GDI纯二进制构造文件头DirectX 截屏得到的是原始 RGB24 像素数据可直接写入 BMP 文件避免Gdiplus::Bitmap::Save依赖 GDI 初始化易在无桌面会话环境失败。BMP 文件头结构如下#pragma pack(push, 1) struct BMPFILEHEADER { WORD bfType; // BM 0x4D42 DWORD bfSize; // 文件总大小 WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; // 像素数据起始偏移54 字节头 0 字节调色板 }; struct BMPINFOHEADER { DWORD biSize; // INFOHEADER 大小 40 LONG biWidth; // 图像宽度像素 LONG biHeight; // 图像高度正数表示自下而上存储 WORD biPlanes; // 必为 1 WORD biBitCount; // 24 位真彩色 DWORD biCompression; // BI_RGB 0 DWORD biSizeImage; // 像素数据大小0 表示自动计算 LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; // 0 表示使用全部颜色 DWORD biClrImportant; // 0 表示所有颜色重要 }; #pragma pack(pop) void SaveToBMP(const std::vectorBYTE pixels, UINT width, UINT height, const char* filename) { DWORD pixelDataSize width * height * 3; DWORD fileSize 54 pixelDataSize; // 14 40 54 字节头 std::ofstream file(filename, std::ios::binary); if (!file.is_open()) return; BMPFILEHEADER fh {0x4D42, fileSize, 0, 0, 54}; BMPINFOHEADER ih {40, (LONG)width, (LONG)height, 1, 24, 0, 0, 0, 0, 0, 0}; file.write(reinterpret_castconst char*(fh), sizeof(fh)); file.write(reinterpret_castconst char*(ih), sizeof(ih)); // BMP 行需 4 字节对齐每行末尾补 0 DWORD rowSize (width * 3 3) ~3; // 向上取整到 4 的倍数 DWORD padding rowSize - width * 3; for (int y height - 1; y 0; --y) { // BMP 自下而上存储 file.write(reinterpret_castconst char*(pixels[y * width * 3]), width * 3); if (padding 0) file.write(\0\0\0, padding); } }注意BMP 高度为正数时数据按自下而上顺序存储故循环y从height-1递减至0rowSize计算确保每行字节数为 4 的倍数否则图像错位。4.2 帧率控制与资源复用避免AcquireNextFrame频繁超时AcquireNextFrame在无新帧时返回DXGI_ERROR_WAIT_TIMEOUT频繁轮询消耗 CPU。优化策略动态超时调整初始设1000ms若连续 3 次超时降为100ms若连续 5 次成功升至2000ms帧时间戳过滤检查frameInfo.LastPresentTime.QuadPart若与上次时间差 16ms60fps跳过本次处理纹理复用m_stagingTexture在Initialize()中创建一次CaptureFrame()中重复CopyResource和Map/Unmap避免反复new/delete。// 在 CaptureFrame 中添加帧时间过滤 static LONGLONG lastPresentTime 0; if (frameInfo.LastPresentTime.QuadPart lastPresentTime) { m_duplication-ReleaseFrame(); return false; // 重复帧丢弃 } lastPresentTime frameInfo.LastPresentTime.QuadPart;4.3 多显示器场景按 HWND 精确绑定输出避免跨屏误捕当用户双屏运行游戏如主屏桌面 副屏全屏游戏MonitorFromWindow返回副屏句柄EnumOutputs匹配副屏szDevice确保DuplicateOutput仅捕获副屏内容。若需捕获游戏窗口客户区非整个显示器需额外计算窗口在显示器坐标系中的位置// 在 FindOutputForWindow 中追加 RECT windowRect; GetWindowRect(targetHwnd, windowRect); POINT topLeft { windowRect.left, windowRect.top }; ScreenToClient(GetDesktopWindow(), topLeft); // 转换为客户区相对坐标 // 后续 CopyResource 时用 ID3D11DeviceContext::CopySubresourceRegion 拷贝子区域提示CopySubresourceRegion需先创建与目标区域等大的D3D11_TEXTURE2D_DESC再CopySubresourceRegion拷贝比全屏捕获内存开销更低适合仅需游戏窗口画面的场景。5. 常见错误排查与 DirectX 运行库依赖验证从DXGI_ERROR_UNSUPPORTED到visual c redistributable安装检查5.1 典型错误码解析与修复路径错误码含义修复方法DXGI_ERROR_UNSUPPORTED显卡驱动不支持 Desktop DuplicationWDDM 1.2更新显卡驱动至最新版或降级使用D3D9 GetFrontBufferData仅限窗口化DXGI_ERROR_ACCESS_DENIED当前进程无桌面会话权限如服务进程确保程序以交互式用户身份运行非 SYSTEM 或 LocalServiceE_NOINTERFACEIDXGIOutput1查询失败Windows SDK 版本过低升级至 ≥10.0.17134.0或改用IDXGIOutputGetDisplaySurfaceDataDX9 仅限DXGI_ERROR_INVALID_CALLDuplicateOutput在窗口化 DirectX 12 应用上失败改用IDXGISwapChain3::GetFrameStatisticsCopyDescriptorsSimpleDX12 特有5.2 验证visual c redistributable与directx repair工具的适用边界标题中提及的microsoft visual c redistributable是运行时依赖不影响 DXGI 截屏功能本身但缺失会导致CreateDXGIFactory1等函数调用失败LNK2019链接错误或LoadLibrary失败。验证方法# 检查已安装的 Visual C Redistributable wmic product where name like Microsoft Visual C%Redistributable% get name,version # 输出示例Microsoft Visual C 2015-2022 Redistributable (x64) 14.34.33810.0注意directx repair工具如 DirectX Repair v4.3仅修复d3dcompiler_47.dll、d3dx9_43.dll等旧版 D3DX 库对 DXGI API 无影响。现代 DirectX 应用DX11/12不依赖 D3DXdxgi.dll由系统内置修复工具无法更新。若遇DXGI_ERROR_NOT_FOUND应检查 Windows Update 是否安装最新累积更新KB500XXXX而非运行directx repair。5.3 最小化依赖部署单文件发布与运行时检查脚本为免去用户手动安装visual c redistributable可在 Visual Studio 项目中设置项目属性 → 配置属性 → 常规 → 使用 C 运行时 →/MT静态链接 CRT项目属性 → 配置属性 → 常规 → Windows SDK 版本 →10.0.22621.0Win11 SDK向下兼容 Win10。最终生成的.exe文件仅依赖系统dxgi.dllWindows 7 SP1 自带无需额外 redistributable。发布时附带批处理检查脚本echo off ver | findstr 10\.0\. nul if %errorlevel% neq 0 ( echo 错误本程序需 Windows 10 或更高版本。 pause exit /b 1 ) dxdiag /t dxcheck.txt nul 21 findstr DirectX Version dxcheck.txt | findstr 12 nul if %errorlevel% equ 0 ( echo DirectX 12 已就绪。 ) else ( echo DirectX 11 已就绪兼容。 ) del dxcheck.txt逻辑说明ver命令确认 Windows 版本dxdiag /t生成诊断文件findstr提取 DirectX 版本确保最低DirectX 11支持Desktop Duplication 要求DirectX 12非必需但推荐。本文还有配套的精品资源点击获取
返回列表