我想将一个3D场景从Viewport3D导出到位图。
显而易见的方法是使用RenderTargetBitmap - 但是当我这样做时,导出的位图的质量明显低于屏幕上的图像。环顾四周,似乎RenderTargetBitmap没有利用硬件渲染。这意味着渲染是在Tier 0完成的。这意味着没有mip-mapping等,因此降低了导出图像的质量。
有谁知道如何以屏幕质量导出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;
}
答案 0 :(得分:5)
RenderTargetBitmap
上没有任何设置告诉它使用硬件进行渲染,因此您必须回退使用Win32或DirectX。我建议使用this article中给出的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设备,如下所示:
Visual.PointToScreen
MonitorFromPoint
中呼叫User32.dll
以获取hMonitor Direct3DCreate9
中呼叫d3d9.dll
以获取pD3D pD3D->GetAdapterCount()
来计算适配器pD3D->GetAdapterMonitor()
并与之前检索的hMonitor进行比较以确定适配器索引pD3D->CreateDevice()
以创建设备本身我可能会在用C ++ / CLR编写的单独库中完成大部分工作,因为我很熟悉这种方法,但您可能会发现使用SlimDX.我很容易将其转换为纯C#和托管代码尚未尝试过。
答案 1 :(得分:4)
我不知道mip-mapping是什么(或软件渲染器是否进行了这种操作和/或多级抗锯齿),但我确实记得Charles Petzold前一段post那是所有关于打印高分辨率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;
}
希望能解决你的问题!
答案 2 :(得分:1)
我认为你因为几个原因而得到了一个空白屏幕。首先,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:
<Window
x:Class="RenderTargetBitmapProblem.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SizeToContent="WidthAndHeight"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="400"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<Viewport3D
x:Name="viewport3D"
Grid.Row="0"
Grid.Column="0"
>
<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
x:Name="image"
Grid.Row="0"
Grid.Column="0"
/>
<Button Grid.Row="1" Click="Button_Click">Render!</Button>
</Grid>
</Window>
答案 3 :(得分:0)
我认为这里的问题确实是WPF的软件渲染器不执行mip-mapping和多级抗锯齿。您可以创建一个RanderTargetBitmap
DrawingImage
,而不是使用ImageSource
,而DrawingImage
是您要渲染的3D场景。理论上,硬件渲染应生成场景图像,然后您可以从{{1}}以编程方式提取。
答案 4 :(得分:0)
使用SlimDX,尝试访问ViewPort3D呈现的DirectX表面,
然后执行读取像素将缓冲区从图形卡的像素缓冲区读入常规存储器
获得(非托管)缓冲区后,使用不安全的代码或编组将其复制到现有的可写位图中。
答案 5 :(得分:0)
我在http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/的Windows Presentation Foundation论坛上也对这个问题有了一些有用的答案。
特别是,我将尝试这两个答案,都来自Marco Zhou:
或者您可以尝试渲染 Viewport3D进入屏幕外 HwndSource,然后抓住它的HWND, 并将其输入Bitblt函数。该 Bitblt将复制已经存在的内容 由硬件光栅器渲染 回到你自己的内存缓冲区。一世 我不是自己尝试这种方法,但是 它值得尝试,理论上也是如此 说,它应该工作。
和
我认为一种简单的方法没有消除 进入WIN32 API就是放置了 Viewport3D进入ElementHost,并调用 它的ElementHost.DrawToBitmap(),一个 这里需要注意的是你需要打电话 DrawToBitmap()在合适的时间 在Viewport3D完全“之后” 渲染“,你可以手动泵送 通过呼叫消息 System.Windows.Forms.Application.DoEvents() 并勾起 CompositionTarget.Rendering事件到 从作文中获得回调 线程(这可能有效,因为我不是 确定Rendering事件是否正确 在这个典型的可靠 环境)。顺便说一下,上面的方法 是基于你的假设 不需要显示ElementHost 屏幕上。 ElementHost将是 显示在屏幕上,你可以 直接调用DrawToBitmap() 方法