RenderTargetBitmap和Viewport3D – 质量问题

我想将一个3D场景从Viewport3D导出到位图。

显而易见的方法是使用RenderTargetBitmap – 但是当我这样做时,导出的位图的质量明显低于屏幕上的图像。 环顾四周,似乎RenderTargetBitmap没有利用硬件渲染。 这意味着渲染在第0层完成。 这意味着没有mip-mapping等,因此降低了导出图像的质量。

有谁知道如何以屏幕质量导出Viewport3D的位图?

澄清

虽然下面给出的示例没有显示这一点,但我最终需要将Viewport3D的位图导出到文件中。 据我所知,唯一的方法是将图像转换为从BitmapSource派生的东西。 下面的Cplotts显示使用RenderTargetBitmap提高导出质量可以改善图像,但由于渲染仍然在软件中完成,因此速度极慢。

有没有办法使用硬件渲染将渲染的3D场景导出到文件? 当然应该可以吗?

你可以看到这个xaml的问题:

                                           

这段代码:

  private void Button_Click(object sender, RoutedEventArgs e) { RenderTargetBitmap bmp = new RenderTargetBitmap((int)viewport3D.ActualWidth, (int)viewport3D.ActualHeight, 96, 96, PixelFormats.Default); bmp.Render(viewport3D); rtbImage.Source = bmp; viewport3D.Visibility = Visibility.Collapsed; rtbImage.Visibility = Visibility.Visible; } 

RenderTargetBitmap上没有设置告诉它使用硬件进行渲染,因此您必须回退到使用Win32或DirectX。 我建议使用本文中给出的DirectX技术。 本文中的以下代码显示了它是如何完成的(这是C ++代码):

 extern IDirect3DDevice9* g_pd3dDevice; Void CaptureScreen() { IDirect3DSurface9* pSurface; g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL); g_pd3dDevice->GetFrontBufferData(0, pSurface); D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL); pSurface->Release(); } 

您可以创建与呈现WPF内容的位置对应的Direct3D设备,如下所示:

  1. 在屏幕图像中的某个点上调用Visual.PointToScreen
  2. User32.dll调用MonitorFromPoint以获取hMonitor
  3. 在d3d9.dll中调用Direct3DCreate9以获取pD3D
  4. 调用pD3D->GetAdapterCount()来计算适配器
  5. 迭代从0到count-1并调用pD3D->GetAdapterMonitor()并与之前检索的hMonitor进行比较以确定适配器索引
  6. 调用pD3D->CreateDevice()来创建设备本身

我可能会在使用C ++ / CLR编写的单独库中完成大部分工作,因为我很熟悉这种方法,但您可能会发现使用SlimDX将其转换为纯C#和托管代码很容易。 我还没有尝试过。

我不知道mip-mapping是什么(或软件渲染器是否进行了这种操作和/或多级抗锯齿),但我记得前一段时间Charles Petzold的一篇文章是关于打印高分辨率WPF的一篇文章 3D视觉效果。

我用你的示例代码尝试了它,看起来效果很好。 所以,我认为你只需要稍微扩展一下。

您需要在rtbImage上将Stretch设置为None并修改Click事件处理程序,如下所示:

 private void Button_Click(object sender, RoutedEventArgs e) { // Scale dimensions from 96 dpi to 600 dpi. double scale = 600 / 96; RenderTargetBitmap bmp = new RenderTargetBitmap ( (int)(scale * (viewport3D.ActualWidth + 1)), (int)(scale * (viewport3D.ActualHeight + 1)), scale * 96, scale * 96, PixelFormats.Default ); bmp.Render(viewport3D); rtbImage.Source = bmp; viewport3D.Visibility = Visibility.Collapsed; rtbImage.Visibility = Visibility.Visible; } 

希望能解决你的问题!

我认为你有一个空白屏幕有几个原因。 首先,VisualBrush需要指向可见的Visual。 第二,也许你忘记了RectangleGeometry需要有尺寸(我知道我最初做过)。

我确实看到了一些我不太了解的奇怪事情。 也就是说,我不明白为什么我必须在VisualBrush上将AlignmentY设置为Bottom。

除此之外,我认为它的工作原理……我认为您应该能够轻松地根据您的实际情况修改代码。

这是按钮单击事件处理程序:

 private void Button_Click(object sender, RoutedEventArgs e) { GeometryDrawing geometryDrawing = new GeometryDrawing(); geometryDrawing.Geometry = new RectangleGeometry ( new Rect(0, 0, viewport3D.ActualWidth, viewport3D.ActualHeight) ); geometryDrawing.Brush = new VisualBrush(viewport3D) { Stretch = Stretch.None, AlignmentY = AlignmentY.Bottom }; DrawingImage drawingImage = new DrawingImage(geometryDrawing); image.Source = drawingImage; } 

这是Window1.xaml:

                                               

我认为这里的问题确实是WPF的软件渲染器不执行mip-mapping和多级抗锯齿。 您可以创建一个DrawingImage ,而不是使用RanderTargetBitmap ,其ImageSource是您要渲染的3D场景。 理论上,硬件渲染应生成场景图像,然后您可以从DrawingImage编程方式提取。

使用SlimDX,尝试访问ViewPort3D渲染到的DirectX表面,
然后执行读取像素以将缓冲区从图形卡的像素缓冲区读取到常规存储器中。
获得(非托管)缓冲区后,使用不安全的代码或编组将其复制到现有的可写位图中。

我还在Windows Presentation Foundation论坛上找到了一些有用的答案, url是http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5- d034cb2a498b / 。

特别是,我将尝试这两个答案,都来自Marco Zhou:

或者您可以尝试将Viewport3D渲染到屏幕外的HwndSource中,然后获取其HWND,并将其提供给Bitblt函数。 Bitblt会将硬件光栅化器已经渲染的内容复制回您自己的内存缓冲区。 我自己并没有尝试这种方法,但值得尝试,从理论上讲,它应该有效。

我认为一个简单的方法是不将video放入WIN32 API,将Viewport3D放入ElementHost,并调用它的ElementHost.DrawToBitmap(),这里需要注意的是你需要在Viewport3D之后的正确时间调用DrawToBitmap()完全渲染“,你可以通过调用System.Windows.Forms.Application.DoEvents()来手动抽取消息,并挂钩CompositionTarget.Rendering事件以从合成线程获取回调(这可能有效,因为我不确定是否在这种典型情况下,渲染事件是可靠的)。 顺便说一下,上面的方法是基于你不需要在屏幕上显示ElementHost的假设。 ElementHost将显示在屏幕上,您可以直接调用DrawToBitmap()方法。