RenderTargetBitmap 和 Viewport3D - 质量问题
我想要将 3D 场景从 Viewport3D 导出到位图。
执行此操作的明显方法是使用 RenderTargetBitmap - 但是,当我这样做时,导出的位图的质量明显低于屏幕上的图像。在互联网上查找,似乎 RenderTargetBitmap 没有利用硬件渲染。这意味着渲染是在 第 0 层 完成的。这意味着没有 mip 映射等,因此导出图像的质量会降低。
有谁知道如何以屏幕质量导出 Viewport3D 位图?
说明
虽然下面给出的示例没有显示这一点,我最终需要将 Viewport3D 的位图导出到文件中。据我所知,执行此操作的唯一方法是将图像转换为派生自 BitmapSource 的内容。下面的 Cplotts 显示,使用 RenderTargetBitmap 提高导出质量可以改善图像,但由于渲染仍然在软件中完成,因此速度非常慢。
有没有办法使用硬件渲染将渲染的 3D 场景导出到文件?这当然应该可行吗?
您可以看到此 xaml 的问题:
<Window x:Class="RenderTargetBitmapProblem.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="400" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Viewport3D Name="viewport3D">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,3"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White"/>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions="-1,-10,0 1,-10,0 -1,20,0 1,20,0"
TextureCoordinates="0,1 0,0 1,1 1,0"
TriangleIndices="0,1,2 1,3,2"/>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource="http://www.wyrmcorp.com/galleries/illusions/Hermann%20Grid.png"
TileMode="Tile" Viewport="0,0,0.25,0.25"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
<ModelVisual3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1,0,0" Angle="-82"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</ModelVisual3D.Transform>
</ModelVisual3D>
</Viewport3D>
<Image Name="rtbImage" Visibility="Collapsed"/>
<Button Grid.Row="1" Click="Button_Click">RenderTargetBitmap!</Button>
</Grid>
</Window>
和此代码:
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;
}
I'm wanting to export a 3D scene from a Viewport3D to a bitmap.
The obvious way to do this would be to use RenderTargetBitmap -- however when I this the quality of the exported bitmap is significantly lower than the on-screen image. Looking around on the internet, it seems that RenderTargetBitmap doesn't take advantage of hardware rendering. Which means that the rendering is done at Tier 0. Which means no mip-mapping etc, hence the reduced quality of the exported image.
Does anyone know how to export a bitmap of a Viewport3D at on-screen quality?
Clarification
Though the example given below doesn't show this, I need to eventually export the bitmap of the Viewport3D to a file. As I understand the only way to do this is to get the image into something that derives from BitmapSource. Cplotts below shows that increasing the quality of the export using RenderTargetBitmap improves the image, but as the rendering is still done in software, it is prohibitively slow.
Is there a way to export a rendered 3D scene to a file, using hardware rendering? Surely that should be possible?
You can see the problem with this xaml:
<Window x:Class="RenderTargetBitmapProblem.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="400" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Viewport3D Name="viewport3D">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,3"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White"/>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions="-1,-10,0 1,-10,0 -1,20,0 1,20,0"
TextureCoordinates="0,1 0,0 1,1 1,0"
TriangleIndices="0,1,2 1,3,2"/>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource="http://www.wyrmcorp.com/galleries/illusions/Hermann%20Grid.png"
TileMode="Tile" Viewport="0,0,0.25,0.25"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
<ModelVisual3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1,0,0" Angle="-82"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</ModelVisual3D.Transform>
</ModelVisual3D>
</Viewport3D>
<Image Name="rtbImage" Visibility="Collapsed"/>
<Button Grid.Row="1" Click="Button_Click">RenderTargetBitmap!</Button>
</Grid>
</Window>
And this code:
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
RenderTargetBitmap
上没有设置告诉它使用硬件进行渲染,因此您必须回退到使用 Win32 或 DirectX。我建议使用本文中给出的 DirectX 技术。本文中的以下代码显示了如何完成此操作(这是 C++ 代码):您可以创建与呈现 WPF 内容的位置相对应的 Direct3D 设备,如下所示:
Visual.PointToScreen
在屏幕图像内的某个点上User32.dll
中的MonitorFromPoint
以获取 hMonitord3d9.dll
中的Direct3DCreate9
code> 获取 pD3DpD3D->GetAdapterCount()
对适配器进行计数pD3D->GetAdapterMonitor()
并与之前检索到的 hMonitor 来确定适配器索引pD3D->CreateDevice()
来创建设备本身我可能会在用 C++/CLR 编码的单独库中完成大部分工作,因为这种方法很熟悉对我来说,但您可能会发现使用 SlimDX 将其转换为纯 C# 和托管代码很容易。 我还没有还没试过。
There is no setting on
RenderTargetBitmap
to tell it to render using hardware, so you will have to fall back to using Win32 or DirectX. I would recommend using the DirectX technique given in this article. The following code from the article and shows how it can be done (this is C++ code):You can create the Direct3D device corresponding to the place where the WPF content is being rendered as follows:
Visual.PointToScreen
on a point within your onscreen imageMonitorFromPoint
inUser32.dll
to get the hMonitorDirect3DCreate9
ind3d9.dll
to get a pD3DpD3D->GetAdapterCount()
to count adapterspD3D->GetAdapterMonitor()
and comparing with the previously retrieved hMonitor to determine the adapter indexpD3D->CreateDevice()
to create the device itselfI would probably do most of this in a separate library coded in C++/CLR because that approach is familiar to me, but you may find it easy to translate it to pure C# and managed code using using SlimDX. I haven't tried that yet.
我不知道 mip 映射是什么(或者软件渲染器是否执行此操作和/或多级抗锯齿),但我确实记得 Charles Petzold 不久前发布的帖子,内容都是关于打印高分辨率 WPF 3D 视觉效果。
我用你的示例代码尝试了一下,它看起来效果很好。所以,我假设你只需要扩大规模一点。
您需要将 rtbImage 上的 Stretch 设置为 None 并修改 Click 事件处理程序,如下所示:
希望能解决您的问题!
I don't know what mip-mapping is (or whether the software renderer does that and/or multi-level anti-aliasing), but I do recall a post by Charles Petzold a while ago that was all about printing hi-res WPF 3D visuals.
I tried it out with your sample code and it appears to work great. So, I assume you just needed to scale things up a bit.
You need to set Stretch to None on the rtbImage and modify the Click event handler as follows:
Hope that solves your problem!
我认为您出现黑屏的原因有几个。首先,VisualBrush 需要指向可见的 Visual。其次,也许您忘记了 RectangleGeometry 需要有尺寸(我知道我一开始就这样做了)。
我确实看到了一些我不太明白的奇怪的事情。也就是说,我不明白为什么必须在 VisualBrush 上将 AlignmentY 设置为 Bottom。
除此之外,我认为它有效......而且我认为您应该能够轻松地根据您的实际情况修改代码。
这是按钮单击事件处理程序:
这是 Window1.xaml:
I think you were getting a blank screen for a couple reasons. First, the VisualBrush needed to be pointing to a visible Visual. Second, maybe you forgot that the RectangleGeometry needed to have dimensions (I know I did at first).
I did see some odd things that I don't quite understand. That is, I do not understand why I had to set AlignmentY to Bottom on the VisualBrush.
Other than that, I think it works ... and I think you should easily be able to modify the code for your real situation.
Here is the button click event handler:
Here is Window1.xaml:
我认为这里的问题确实是 WPF 的软件渲染器不执行 mip 映射和多级抗锯齿。您可以创建一个
DrawingImage
,其ImageSource
是您要渲染的 3D 场景,而不是使用RanderTargetBitmap
。理论上,硬件渲染应该生成场景图像,然后您可以通过编程方式从DrawingImage
中提取该图像。I think the issue here indeed is that the software renderer for WPF does not perform mip-mapping and multi-level anti aliasing. Rather than using
RanderTargetBitmap
, you may be able to create anDrawingImage
whoseImageSource
is the 3D scene you want to render. In theory, the hardware render should produce the scene image, which you can then programmatically extract from theDrawingImage
.使用 SlimDX,尝试访问 ViewPort3D 渲染到的 DirectX 表面,
然后执行读取像素以将缓冲区从显卡的像素缓冲区读取到常规内存中。
一旦获得(非托管)缓冲区,请使用不安全代码或编组将其复制到现有的可写位图中。
Using SlimDX, try accessing the DirectX surface that the ViewPort3D renders to,
then performing a read-pixel to read the buffer from the graphics card's pixel buffer into regular memory.
Once you have the (unmanaged) buffer, copy it into an existing writable bitmap using unsafe code or marshalling.
我还在 Windows Presentation Foundation 论坛上找到了这个问题的一些有用答案,网址为 http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/。
特别是,我将尝试这两个答案,均来自 Marco Zhou:
和
I've also had a few useful answers to this question over at the Windows Presentation Foundation forums at http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/.
In particular, I'm going to try these two answers, both from Marco Zhou:
and