将 Direct3D11 在 GPU 中的纹理(Texture2D)导出到内存(Map)或导出成图片文件

news/2025/3/15 10:18:17/

Direct3D11 的使用通常不是应用程序唯一的部分,于是使用 Direct3D11 的代码如何与其他模块正确地组合在一起就是一个需要解决的问题。

本文介绍将 Direct3D11 在 GPU 中绘制的纹理映射到内存中,这样我们可以直接观察到此纹理是否是正确的,而不用担心是否有其他模块影响了最终的渲染过程。


本文内容

    • SharpDX
    • 来自于 Direct3D11 的渲染纹理
    • 关键代码(SharpDX.DXGI.Surface.Map)
    • 你可能需要拷贝资源
    • 导出成图片文件

SharpDX

本文的代码会使用到 SharpDX 库,因此,你需要在你的项目当中安装这些 NuGet 包:

<!-- 基础,必装 -->
<PackageReference Include="SharpDX" Version="4.2.0" />
<PackageReference Include="SharpDX.D3DCompiler" Version="4.2.0" />
<PackageReference Include="SharpDX.DXGI" Version="4.2.0" />
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" /><!-- 其他,可选 -->
<PackageReference Include="SharpDX.Direct2D1" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D9" Version="4.2.0" />

来自于 Direct3D11 的渲染纹理

本文不会说如何创建或者获取来自 Direct3D11 的渲染纹理,不过如果你希望了解,可以:

  • 自己创建:WPF 使用封装的 SharpDx 控件
  • 或者从其他进程/模块获取:使用 Direct3D11 的 OpenSharedResource 方法渲染来自其他进程/设备的共享资源(SharedHandle)

本文接下来的内容,是在你已经获得了 SharpDX.Direct3D11.Resource 的引用,或者 SharpDX.Direct3D11.Texture2D 的前提之下。当然,如果你获得了其中任何一个实例,可以通过 COM 组件的 QueryInterface 方法获得其他实例。

var texture = resource.QueryInterface<SharpDX.Direct3D11.Texture2D>();
var resource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();

关键代码(SharpDX.DXGI.Surface.Map)

要获得 GPU 中渲染的图片,我们必须要将其映射到内存中才行。而映射到内存中的核心代码是 SharpDX.DXGI.Surface 对象的 Map 方法。

using (var surface = texture2D.QueryInterface<SharpDX.DXGI.Surface>())
{var map = surface.Map(SharpDX.DXGI.MapFlags.Read, out DataStream dataStream);for (var y = 0; y < surface.Description.Height; y++){for (var x = 0; x < surface.Description.Width; x++){// 在这里使用位图的像素数据,坐标为 (x, y)。// 得到此坐标下的像素指针://     var ptr = ((byte*)map.DataPointer) + y * map.Pitch;// 得到此像素的颜色值://     var b = *(ptr + 4 * x);//     var g = *(ptr + 4 * x + 1);//     var r = *(ptr + 4 * x + 2);//     var a = *(ptr + 4 * x + 3);}}dataStream.Dispose();surface.Unmap();
}

注意以上代码使用了不安全代码(指针),你需要为你的项目开启不安全代码开关,详见:

  • 如何在 .NET 项目中开启不安全代码(以便启用 unsafe fixed 等关键字)

你可能需要拷贝资源

实际上,在使用上面的代码时,你可能会遇到错误,错误出现在 Map 方法的调用上,描述为“参数错误”。实际上真正检查这里的两个参数时并不能发现究竟是哪个参数出了问题。

实际上出问题的参数是 surface 的实例。

一段 GPU 中的纹理要能够被映射到内存,必须要具有 CPU 的访问权。而是否具有 CPU 访问权在创建纹理的时候就已经确定下来了。

如果前面你得到的纹理是自己创建的,那么恭喜你,你只需要改一下创建纹理的参数就好了。给 Texture2DDescriptionCpuAccessFlags 属性加上 CpuAccessFlags.Read 标识。

desc.CpuAccessFlags = CpuAccessFlags.Read;

但是,如果此纹理不是由你自己创建的,那么就需要拷贝一份新的纹理了。当然,拷贝过程发生在 GPU 中,占用的也是 GPU 专用内存(即显存,如果有的话)。

拷贝需要做到两点:

  1. 创建一个新的 Texture2DDescription(一定要是新的实例,你不能影响原来的实例),然后修改其 CPU 访问权限为 Read
  2. 使用 ImmediateContext 实例的 CopyResource 方法来拷贝资源(此实例可以通过 SharpDX.Direct3D11.Device 来找到)。
var originalDesc = originalTexture.Description;
var desc = new Texture2DDescription
{CpuAccessFlags = CpuAccessFlags.Read,BindFlags = BindFlags.None,Usage = ResourceUsage.Staging,Width = originalDesc.Width,Height = originalDesc.Height,Format = originalDesc.Format,MipLevels = 1,ArraySize = 1,SampleDescription ={Count = 1,Quality = 0},
};var texture2D = new Texture2D(device, desc);
device.ImmediateContext.CopyResource(originalTexture, texture2D);

需要注意,拷贝纹理会额外占用显存,一般不建议这么做,除非你真的有需求一定要 CPU 能够访问到这段纹理。

导出成图片文件

实际上,当你组合起来以上以上方法,你应该能够将纹理导出成图片了。

不过,为了理解更方便一些,我还是将导出成图片的全部代码贴出来:

public static unsafe void MapTexture2DToFile(SharpDX.Direct3D11.Texture2D texture, string fileName)
{// 获取 Texture2D 的相关实例。var device = texture.Device;var originDesc = texture.Description;// 创建新的 Texture2D 对象。var desc = new Texture2DDescription{CpuAccessFlags = CpuAccessFlags.Read,BindFlags = BindFlags.None,Usage = ResourceUsage.Staging,Width = originDesc.Width,Height = originDesc.Height,Format = originDesc.Format,MipLevels = 1,ArraySize = 1,SampleDescription ={Count = 1,Quality = 0},OptionFlags = ResourceOptionFlags.Shared};var texture2D = new Texture2D(device, desc);// 拷贝资源。device.ImmediateContext.CopyResource(texture, texture2D);var bitmap = new System.Drawing.Bitmap(desc.Width, desc.Height);using (var surface = texture2D.QueryInterface<SharpDX.DXGI.Surface>()){var map = surface.Map(SharpDX.DXGI.MapFlags.Read, out DataStream dataStream);var lines = (int)(dataStream.Length / map.Pitch);var actualWidth = surface.Description.Width * 4;for (var y = 0; y < desc.Height; y++){var h = desc.Height - y;var ptr = ((byte*)map.DataPointer) + y * map.Pitch;for (var x = 0; x < desc.Width; x++){var b = *(ptr + 4 * x);var g = *(ptr + 4 * x + 1);var r = *(ptr + 4 * x + 2);var a = *(ptr + 4 * x + 3);bitmap.SetPixel(x, y, System.Drawing.Color.FromArgb(a, r, g, b));}}dataStream.Dispose();surface.Unmap();bitmap.Save(fileName);}
}

如果你是希望以纯软件的方式渲染到 WPF 中(WriteableBitmap),可以参考:

  • WPF 高性能位图渲染 WriteableBitmap 及其高性能用法示例

记得打开不安全代码开关哦!详见:

  • 如何在 .NET 项目中开启不安全代码(以便启用 unsafe fixed 等关键字)

参考资料

  • c++ - How to access pixels data from ID3D11Texture2D? - Stack Overflow
  • SharpDX Directx11 How to add normal mapping ? - Graphics and GPU Programming - GameDev.net
  • directx 11 - How to create bitmap from Surface (SharpDX) - Stack Overflow
  • Desktop Duplication API - Windows applications - Microsoft Docs
  • c# - Reading Datastream sharpDX Error all values are 0 - Stack Overflow
  • SharpDX-Samples/Program.cs at master · sharpdx/SharpDX-Samples

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。


http://www.ppmy.cn/news/918146.html

相关文章

使用Direct3D渲染2D图素

> > 使用Direct3D渲染2D图素 2001-09-22 中国游戏开发者.CN 图片及源代码请登陆下面网站&#xff1a; 合作翻译&#xff1a; 中国游戏开发者.CN – mays http://mays.6to23.com 游戏制作天地 – wonyee&#xff08;rocks_lee&#xff09; http://wonyee…

Direct2D 介绍

最近做了一个项目&#xff0c;需要使用Windows的2D渲染技术&#xff0c;对GDI、Direct2D、Direct3D、opengl等图像渲染库做了一个比较&#xff0c;发现使用Direct2D是最优的。因为它既没有Direct3D和Opengl使用的复杂度&#xff0c;又具有硬件加速功能(GDI没有硬件加速功能)。所…

禁用/开启 Windows系统3D加速

前言 有的老游戏(主要是2D游戏)在新系统中运行&#xff0c;可能出现许多问题&#xff0c;如黑屏、报错、闪退、速度异常等&#xff01; 这时候只需要禁用3D加速&#xff0c;大多即可正常运行。 当然&#xff0c;也可以通过这个方法&#xff0c;来限制别人玩3D游戏~ XP及以前…

Direct3D 12 尝鲜(二): Fence

(转载请注明出处) 正如上节末尾所说, 出现了运行时错误: D3D12 ERROR: ID3D12CommandAllocator::Reset: A command allocator is being reset before previous executions associated with the allocator have completed. [ EXECUTION ERROR #548: COMMAND_ALLOCATOR_SYNC] …

OpenGL Direct3D(DirectX)区别

1. OpenGl仅仅是个图形图像接口&#xff0c;基本不包括其它多媒体功能, 它的优势是平台无关性。DirectX则是基于Windows的&#xff0c;不能在MAC 和 Linux Solars上使用 。 2. OpenGL多用于专业高端绘图领域&#xff0c;D3D用于游戏较多&#xff0c;因为支持各种多媒体功能。

Direct3D 11在windows7上提示创建d3d设备失败

1.在windows7上编译程序&#xff0c;设置的是Debug版本&#xff0c;结果在执行第一步就失败了&#xff0c;无法创建设备。 2.跟踪到执行点&#xff0c;发现是Debug版本&#xff0c;显卡驱动程序不支持&#xff0c;无法创建支持调试的Direct3D设备创建。

Direct3D基础概念和模型整理

参考整理自文章&#xff1a; http://zh.wikipedia.org/zh/Direct3D http://blog.csdn.net/weili_2007/article/details/1907066 http://msdn.microsoft.com/en-us/library/windows/desktop/bb219679(vvs.85).aspx#Direct3D_System_Integration 概念主要包括IDirect3D, Adapt…