C# RenderTargetBitmap和Viewport3D-质量问题

C# RenderTargetBitmap和Viewport3D-质量问题,c#,.net,wpf,3d,rendertargetbitmap,C#,.net,Wpf,3d,Rendertargetbitmap,我想将3D场景从Viewport3D导出到位图 实现这一点的明显方法是使用RenderTargetBitmap——但是,当我这样做时,导出位图的质量明显低于屏幕上的图像。环顾互联网,RenderTargetBitmap似乎没有利用硬件渲染。这意味着渲染是在。这意味着没有mip映射等,因此导出图像的质量降低 有人知道如何以屏幕质量导出Viewport3D位图吗 澄清 尽管下面给出的示例没有显示这一点,我最终需要将Viewport3D的位图导出到一个文件。据我所知,唯一的方法是将图像导出到从Bitm

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

实现这一点的明显方法是使用RenderTargetBitmap——但是,当我这样做时,导出位图的质量明显低于屏幕上的图像。环顾互联网,RenderTargetBitmap似乎没有利用硬件渲染。这意味着渲染是在。这意味着没有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>

我认为这里的问题确实是WPF的软件渲染器不执行mip映射和多级抗锯齿。与使用
RanderTargetBitmap
不同,您可以创建一个
DrawingImage
,其
ImageSource
是要渲染的3D场景。理论上,硬件渲染应该生成场景图像,然后您可以通过编程从
DrawingImage
中提取该图像。我不知道mip映射是什么(或者软件渲染器是否执行该操作和/或多级抗锯齿),但我确实记得Charles Petzold不久前写的一篇关于打印高分辨率WPF 3D视觉效果的文章

我用您的示例代码进行了测试,它似乎工作得很好。因此,我假设您只需要将事情放大一点

您需要在rtbImage上将“拉伸”设置为“无”,并修改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需要指向可见的视觉对象。其次,也许你忘记了矩形几何体需要有尺寸(我知道我一开始是这样做的)

我确实看到了一些我不太明白的奇怪的事情。也就是说,我不明白为什么我必须在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>

提供!

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

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

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;
}
  • 在屏幕图像中的某个点上调用
    Visual.PointToScreen
  • User32.dll
    中调用
    MonitorFromPoint
    以获取hMonitor
  • d3d9.dll
    中调用
    Direct3DCreate9
    以获取pD3D
  • 调用
    pD3D->GetAdapterCount()
    对适配器进行计数
  • 从0迭代到count-1,调用
    pD3D->GetAdapterMonitor()
    ,并与之前检索到的hMonitor进行比较,以确定适配器索引
  • 调用
    pD3D->CreateDevice()
    创建设备本身

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

    使用SlimDX,尝试访问ViewPort3D渲染到的DirectX曲面,
    然后执行读取像素,将缓冲区从图形卡的像素缓冲区读取到常规内存中。

    一旦有了(非托管)缓冲区,使用不安全的代码或编组将它复制到现有的可写位图中。

    < P>我在Windows演示基金会论坛上也有一些有用的答案。

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

    或者您可以尝试渲染 将Viewport3D转换为屏幕外视图 HwndSource,然后获取其HWND, 并将其输入Bitblt函数。这个 Bitblt将复制已经存在的内容 由硬件光栅化器渲染 返回到您自己的内存缓冲区。我 我自己不尝试这种方法,但是 从理论上讲,这是值得尝试的 说实话,它应该会起作用

    我想有一个简单的方法不必呕吐 在WIN32 API中放置 将Viewport3D导入ElementHost,并调用 它的ElementHost.DrawToBitmap(),一个 这里的警告是你需要打电话 DrawToBitmap()在正确的时间启动 A.
    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(); 
    }