资讯动态

uWindowCapture插件:Unity桌面捕获的工业级方案

发布时间:2026/9/15 16:09:56 来源:尧图企业网站定制
简介面向需要在 Unity 中实时抓取并展示 Windows 桌面的开发者一份可直接集成的插件资源可解决桌面内容嵌入游戏窗口、远程协作投屏等需求覆盖桌面实时预览、屏幕采集与同步投射等常见用途。压缩包共 141 个文件包括 26 个 C# 脚本、13 个 Unity 场景、材质与预制体以及 DLL、Shader、编辑器配置文件等核心模块涵盖桌面窗口管理、纹理同步、窗口布局与编辑器扩展整体仅 600KB功能集中且结构清晰。已有 565 人学习下载说明该插件在 Unity 桌面接入场景中有较高的实用价值与学习参考意义尤其适合桌面投射类项目。借助其中已有的窗口捕获、纹理更新与布局工具能显著减少从底层抓屏到纹理映射的重复开发也适合希望研究 Windows 桌面采集原理、加深对 Unity 渲染管线理解的中高级开发者参考学习。1. 为什么Unity里取桌面画面不能靠截屏循环做数字孪生大屏、远程协助工具或者录屏类应用时常遇到一个需求把Windows桌面实时搬进Unity场景作为UI背景、三维物体的贴图或者画中画窗口。很多人的第一反应是调用System.Drawing的CopyFromScreen然后转Texture2D。这个方案在1080p下勉强能动但只要分辨率上到2K或者帧率要求超过30FPSCPU占用就会飙到无法接受而且鼠标轨迹还会出现明显的撕裂。uWindowCapture这个Unity插件走的是另一条路通过Windows Graphics Capture API直接抓取桌面窗口句柄把D3D表面的帧拷贝到Unity的Texture上。整个过程不走GDI也不经过CPU回读性能开销比截屏低一个数量级这才是它适合工业级项目的原因。这篇文章会把插件的文件结构、采集流程、参数配置和踩坑点完整拆一遍确保你拿到源码包之后能直接落地。2. 核心架构剖析从窗口句柄到Unity纹理的完整链路2.1 文件清单与职责划分解压资源包后Assets目录下会多出一组文件每个文件的用途需要理清楚文件名类型职责UwcManager.cs管理器初始化和释放Windows Graphics Capture持有采集线程UwcWindow.cs数据模型抽象桌面窗口记录窗口类名、进程ID、共享资源指针UwcWindowTexture.cs核心组件挂载到Unity物体上把采集到的纹理帧更新到材质UwcWindowTextureChildrenManager.cs子物体管理为多个子窗口同步创建纹理组件UwcDesktopLayouter.cs自动化布局按网格或条幅方式排列多个窗口纹理的Transform位置UwcWindowTextureEditor.cs编辑器扩展Inspector面板的定制显示和调试按钮UwcLib.csP/Invoke封装把C接口封成C#可调用的DllImportUwcCommon.cgincShader片段内置着色器中对纹理采样和色彩空间处理的公共宏关键点在于UwcManager是单例入口它只负责建立会话不直接产生Texture。UwcWindow才是真正代表一个可采集目标UwcWindowTexture在Start时根据UwcWindow的共享资源句柄创建Unity Texture2D并每帧调用Update从共享纹理读数据。2.2 管理器的启动流程与生命周期控制打开UwcManager.cs核心方法集中在Start和OnApplicationQuit伪代码如下void Start() { if (!isSupportedPlatform()) return; _direct3D11Device GetUnityD3D11Device(); _desktopDuplication new DesktopDuplicationManager(); _desktopDuplication.Initialize(_direct3D11Device); string[] args System.Environment.GetCommandLineArgs(); foreach (string arg in args) { if (arg -uwcDebug) _isDebugMode true; } Application.targetFrameRate 60; } void Update() { if (_desktopDuplication ! null) _desktopDuplication.Update(); } void OnApplicationQuit() { if (_desktopDuplication ! null) { _desktopDuplication.Release(); _desktopDuplication null; } }GetUnityD3D11Device会从Unity底层拿到当前图形设备的ID3D11Device这是整个采集链路的前提——Windows Graphics Capture要求采集器和目标纹理共享同一个D3D设备。Application.targetFrameRate 60这一步容易被忽略如果不锁帧Update里的纹理更新次数会跟渲染帧率解耦产生跳帧感。关于Debug模式插件支持在启动参数里加-uwcDebug开启后会把每次采集的帧耗时、纹理拷贝大小输出到Console排查性能问题时非常实用。注意这个模式只对使用Unity的Build方法构建出的exe有效Editor里直接跑需要手动修改PlayerSettings的Scripting Define Symbols。2.3 UwcWindowTexture的帧同步机制UwcWindowTexture.cs的Update方法决定了画面更新到的时机代码逻辑如下void Update() { if (_window null) return; if (_isPresent) return; // 更新纹理内容 _updateTimer Time.deltaTime; if (_updateTimer 0.1f) { _updateTimer 0f; if (m_updateMode UpdateMode.Auto) UpdateTexture(); } } void UpdateTexture() { // 从共享资源获取最新帧 _window.GetSharedSurfaceData(); // 处理纹理格式转换 if (_window.textureFormat TextureFormat.BGRA32) { _texture new Texture2D(_window.width, _window.height, TextureFormat.BGRA32, false); _texture.SetPixels32(_window.textureColors); _texture.Apply(); } }UpdateMode.Auto是默认值插件内部用计时器控制更新频率默认0.1秒也就是10FPS左右。如果项目需要录屏级的流畅度需要把m_UpdateMode设为Manual然后自己调用UpdateTexture或者修改_updateTimer 0.016f让它跑满60FPS。这段代码里还有一个细节if (_isPresent) return;。该字段用于判断Texture是否已经被副本占用如果上一帧拷贝还没完成当前帧会直接跳过避免产生资源竞争。在画面变化频繁的场景比如视频播放器窗口有可能出现跳帧但只要拷贝开销小于间隔_isPresent绝大多数情况下都是false。3. 接入Unity项目的完整步骤与参数调优3.1 基础接入从解压到显示桌面直接拖进Assets目录后打开UwcWindowTexture.cs对应的Prefab或空物体在Inspector面板上能看到相关配置项Window Title窗口标题的关键字插件会按标题模糊匹配Process Name进程名过滤填chrome则只找Chrome窗口Capture Mode可选Screen或WindowUpdate ModeAuto或Manual建议Manual配合脚本自己控制节拍在场景里创建一个RawImage用于承载纹理写一个初始化脚本using UnityEngine; using UnityEngine.UI; using uWindowCapture; public class DesktopUser : MonoBehaviour { public UwcManager manager; UwcWindowTexture windowTexture; void Start() { // 按窗口标题获取桌面窗口 UwcWindow window manager.FindWindowByTitle(Unity); if (window null) { Debug.LogError(未找到窗口); return; } // 将窗口纹理绑定到RawImage windowTexture gameObject.AddComponentUwcWindowTexture(); windowTexture.targetWindow window; windowTexture.StartCapture(); RawImage image GetComponentRawImage(); image.texture windowTexture.texture; } }FindWindowByTitle用的是子串匹配传入Unity可以同时匹配到“Unity 2021.3.20f1c1”和“Unity Hub”如果你的窗口标题过长用部分关键字反而更灵活。绑定纹理的时机必须在StartCapture之后否则拿到的texture为null。如果脚本要兼容Editor和Build两种环境预览窗口最好固定显示主监视器避免在多显示器场景下选择出错。3.2 多窗口同步显示的布局控制UwcDesktopLayouter.cs解决的问题是当你有多个UwcWindowTexture需要同时显示时不想手动拖位置用代码自动排布子物体。使用方法如下// 创建父物体承载多个窗口 GameObject root new GameObject(DesktopGrid); var layouter root.AddComponentUwcDesktopLayouter(); // 添加两个窗口采集 var tex1 CreateWindowTexture(Chrome); tex1.transform.SetParent(root.transform); var tex2 CreateWindowTexture(Outlook); tex2.transform.SetParent(root.transform); // 调用自动布局 layouter.Layout(2, 1, 10f, 10f);Layout方法接收行列数、间隔和边距值内部按从左到右、从上到下的顺序把每个子物体的RectTransform铺满父区域。默认还提供了一个Arrange方法可以配合ScrollRect实现一页展示多个窗口翻页时自动调整缩放比例。另外在UwcWindowTextureChildrenManager中你可以动态监控子窗口的变化。例如进程内新弹出一个对话框它会自动创建一个新的UwcWindowTexture显示在界面上。这个类适用于需要在主窗口内嵌若干子元素的场景比如游戏内嵌浏览器、IDE中的调试面板。3.3 与PLC通信等第三方系统的联动策略这个插件本身不做数据交互但它可以作为可视化层接收外部事件。常见的做法是写一个事件转发脚本监听UwcWindowTexture的OnWindowClosed和OnWindowSizeChanged事件再通过Unity的SendMessage或者其他事件系统通知业务层。比如做产线数字孪生系统经常需要把组态软件的画面集成进来用西门子PLC通信指令控制设备同时把PLC面板的桌面画面作为背景叠加在虚拟机模型上。典型做法先用UwcWindowTexture把WinCC画面加载到场景中的大屏模型面片再用异步Socket接收PLC的交互指令当PLC值变化时修改窗口的显隐和位置。这个插件的职责边界就是提供画面层具体业务逻辑完全在Unity侧自己拼装。3.4 参数表格不同场景推荐配置场景Capture ModeUpdate Mode更新间隔备注数字孪生大屏ScreenManual0.05s保证20FPS以上不要跑满60录屏/直播WindowManual0.016s需要60FPS时开启垂直同步低配工控机ScreenAuto0.1s默认值就够远程桌面画中画WindowManual0.1s建议同时开启硬件缩放这里强调一个容易踩的坑Capture Mode选Screen会把整个桌面变成一个巨大的纹理在4K分辨率下支持不了。建议优先用Window模式找主窗口如果必须要整个桌面可以先用Unity的Screen.SetResolution降分辨率再启动采集。4. 性能优化、光标渲染与多显示器避坑4.1 帧率控制在20~30FPS的工程取舍做导览类应用时桌面画面不需要非常流畅20~30FPS是感官和性能的最佳平衡点。把UpdateTexture改成这样就能稳定输出定义变量int targetFps 25;在Update中判断累计时间超过1f / targetFps时才刷新。从显卡层面来看即使你的采集纹理只更新25FPS显卡渲染开销仍然存在。因此要达到省电节能的目的需要把Application.targetFrameRate同样调到25让Unity渲染本身也降帧。这个值不是越高越好因为Windows的桌面合成器DWM刷新率通常锁定在显示器级别抓桌面帧的间隔过短会产生额外的等待空转。4.2 光标分量缺失的处理方式Windows Graphics Capture默认采集的是整个桌面画面但不会给光标单独建立一层纹理因此插件的纹理里看不到鼠标指针除非桌面窗口处于非活动状态时Windows自动隐藏光标。UwcWindowTextureEditor.cs里有个DrawCursor选项开启后会在Texture上叠加一个由IngameCursorManager生成的CursorMesh但不支持在Game视图下同步光标因为它走的是鼠标事件监听而不是桌面COM接口。如果需要与渲染内容完全一致的光标画面建议改用Windows API的GetCursorPos加上DrawIcon绘制到单独层再混入这是比较常见的配合手段。4.3 多显示器场景下的窗口识别策略插件的窗口查找逻辑使用枚举所有Desktop Window并将句柄、Width、Height写入UwcWindow对象。在多显示器场景下某个窗口的Width会包含出它的整个尺寸但你无法直接知道这个窗口在哪个显示器上。解决办法是读取Screen.currentResolution对比窗口坐标是否在该分辨率范围内或者用UwcWindow暴露出的MonitorIndex属性。此外多显示器下抓整个桌面时Texture的宽高是所有显示器拼接的总和用UpdateTexture之前要先判断宽高是否超出SystemInfo.maxTextureSize否则Unity会报错并停止更新严重时直接白屏。经验阈值是单块4K屏拼接双屏时必须手动切分采集区域分成两个Window分别处理。4.4 加入内存保护与异常恢复长时间运行的场景比如展厅播控系统窗口最小化或者切换桌面会造成采集会话错误。UwcManager中内置了异常重连机制当Update方法捕获到SharpDXException时会触发OnErrorOccurred事件并尝试再次初始化会话。我建议在业务侧注册这个事件因为默认的重连逻辑只恢复管理器不会恢复你界面上已有的UwcWindowTexture对象。以下是一个稳定的恢复策略示例void OnEnable() { manager.OnErrorOccurred OnCaptureError; } void OnCaptureError(object sender, Exception e) { // 销毁所有客户端纹理对象 foreach (var tex in allTextures) { Destroy(tex.texture); tex.StopCapture(); } // 延迟重建 StartCoroutine(RestartCapture()); } IEnumerator RestartCapture() { yield return new WaitForSeconds(1f); foreach (var config in savedWindows) { CreateWindowTexture(config); } }这里AllTextures需要你自己维护一个列表UwcManager不做这个持久化。注意StopCapture后必须将UwcWindow的状态重置否则重连时会因为旧句柄无效而报ArgumentException。5. 排错锦囊版本兼容性、Shader定制与常用测试命令5.1 Unity版本与系统环境不匹配时的症状插件的README里明确写了一句“如果存在问题请更换Unity版本”实际情况是它底层调用了较新的Windows SDK接口Windows 10 2004及以上提供的CreateDirect3D11DeviceFromDXGIDevice因此如果你的系统是Windows 7或者未更新的Windows 10 1903初始化时会在UwcWindowTexture.Start直接抛NullReferenceException这类问题换Unity版本没有意义建议先升级系统补丁在Unity 2020.3和2021.3下测试稳定Unity 2022.2及之后版本需要把API Compatibility Level设置为.NET Standard 2.1否则P/Invoke的DllImport会加载失败如果项目使用URP或HDRP渲染管线默认的UwcCommon.cginc里的采样方式会失效需要在Shader中手动定义UNITY_UI_ALPHACLIP和UNITY_UI_CLIP_RECT等宏5.2 Shader层纹理采样异常的处理UwcCommon.cginc内部包含两个宏一个是转换线性空间到Gamma空间另一个是用于在URP下兼容旧版SampleTexture2D。如果你的画面看起来发灰或者发暗多半是色彩空间问题// 在自定义Shader中强制开启Gamma修正 #define UWC_FORCE_GAMMA 1 #include Assets/uWindowCapture/Shaders/UwcCommon.cginc fixed4 frag(v2f i) : SV_Target { fixed4 color tex2D(_MainTex, i.uv); #ifdef UWC_FORCE_GAMMA color.rgb LinearToGammaSpace(color.rgb); #endif return color; }LinearToGammaSpace是Unity的内置函数在Built-in管线可用。Custom SRP下需要自己实现映射公式或者直接用pow(color.rgb, 1.0 / 2.2)近似处理。5.3 Editor中的调试命令Inspector面板最底部的Debug区域提供以下三个按钮Reload重新初始化会话适合窗口重启后重新绑定Capture Now强制立即抓一帧不等待Update计时器Dump Info把当前窗口的句柄、进程名、实际尺寸、DPI缩放值全部打印出来在自动化测试脚本中也可以调用# 配合Unity命令行运行 Unity.exe -batchmode -projectPath . -executeMethod UwcTest.RunCapture -logFile capture_test.log然后在编辑器脚本里写public static void RunCapture() { var manager GameObject.FindObjectOfTypeUwcManager(); var result manager.CaptureOnce(); Debug.Log($Capture result: {result}); }这是验证插件是否正常工作的最快路径不需要打开Play模式直接在EditMode就能看采集会话是否建立成功。5.4 常见的错误信息对照表错误提示原因对策0x80070005 Access is denied窗口属于更高权限的进程如管理员命令行以管理员身份运行Unity0x80070057 E_INVALIDARG窗口最小化或不存在先检查IsWindowVisible0x8889000BGPU资源丢失长时间锁屏或驱动重置监听OnErrorOccurred并自动恢复The request is not supported系统版本过旧不支持DXGI 1.5升级到Windows 10 21H2及以上注意第2条很频繁——当你用WindowTitle关键字匹配时窗口的最小化状态会导致宽度为0Texture创建立即失败。固定流程应该是先调用window.IsIconic判断如果是则强制恢复窗口或用ShowWindow重新显示再继续采集流程。5.5 同步周期对功耗的影响如果把Capture模式设置为Manual后暂停纹理更新插件不会自动停止D3D资源占用的GPU拷贝操作。检测这个问题的方法打开Windows自带的任务管理器查看Desktop Window Manager进程的GPU占用率如果持续高于20%说明即使不更新纹理后台采集线程仍在运行。解决方法是在窗口失焦时主动调用windowTexture.StopCapture()聚焦后再恢复这种方式在自动导览项目中能明显降低裸功耗和风扇噪声。最后在Shader中使用UNITY_SAMPLE_TEX2D_SAMPLER替代tex2D能规避URP下因为平台差异带来的采样结果偏移这也是把这个插件从Built-in管线迁移到URP时必须做的改动。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价