16

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

执行此操作的明显方法是使用 RenderTargetBitmap - 但是当我这样做时,导出位图的质量明显低于屏幕上的图像。在互联网上环顾四周,似乎 RenderTargetBitmap 没有利用硬件渲染。这意味着渲染是在第 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;
    }
4

6 回答 6

5

没有设置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. 调用MonitorFromPointUser32.dll获取 hMonitor
  3. 打电话获取Direct3DCreate9pD3Dd3d9.dll
  4. 调用pD3D->GetAdapterCount()计算适配器
  5. 从 0 迭代到 count-1 并调用pD3D->GetAdapterMonitor()并与之前检索到的 hMonitor 进行比较以确定适配器索引
  6. 调用pD3D->CreateDevice()创建设备本身

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

于 2010-02-16T22:30:57.363 回答
4

我不知道 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;
}

希望能解决您的问题!

于 2010-02-15T22:09:35.357 回答
1

我认为你得到一个空白屏幕有几个原因。首先,VisualBrush 需要指向一个可见的 Visual。其次,也许你忘记了 RectangleGeometry 需要有尺寸(我知道我一开始就是这样做的)。

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

除此之外,我认为它有效......而且我认为您应该能够轻松地根据您的实际情况修改代码。

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

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>
于 2010-02-16T15:31:55.327 回答
0

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

于 2010-02-01T18:49:02.597 回答
0

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

于 2010-02-17T07:40:12.390 回答
0

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

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

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

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

于 2010-02-20T11:55:34.170 回答