RenderTargetBitmap和Viewport3D - 质量问题

时间:2010-02-01 18:24:44

标签: c# .net wpf 3d rendertargetbitmap

我想将一个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;
    }

6 个答案:

答案 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设备,如下所示:

  1. 在屏幕图片中的某个点上调用Visual.PointToScreen
  2. MonitorFromPoint中呼叫User32.dll以获取hMonitor
  3. Direct3DCreate9中呼叫d3d9.dll以获取pD3D
  4. 调用pD3D->GetAdapterCount()来计算适配器
  5. 从0迭代到count-1并调用pD3D->GetAdapterMonitor()并与之前检索的hMonitor进行比较以确定适配器索引
  6. 致电pD3D->CreateDevice()以创建设备本身
  7. 我可能会在用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()   方法