RenderTargetBitmap и Viewport3D - вопросы качества - PullRequest
16 голосов
/ 01 февраля 2010

Я хочу экспортировать 3D-сцену из Viewport3D в растровое изображение.

Очевидный способ сделать это - использовать RenderTargetBitmap, однако при этом качество экспортируемого растрового изображения значительно ниже, чем изображение на экране. Оглядываясь в Интернете, кажется, что RenderTargetBitmap не использует преимущества аппаратного рендеринга. Это означает, что рендеринг выполняется на Tier 0 . Это означает отсутствие mip-mapping и т. Д., Следовательно, снижение качества экспортируемого изображения.

Кто-нибудь знает, как экспортировать растровое изображение Viewport3D с качеством на экране?

Разъяснение

Хотя приведенный ниже пример этого не показывает, Мне нужно в конечном итоге экспортировать растровое изображение Viewport3D в файл. Как я понимаю, единственный способ сделать это - вставить изображение в нечто это происходит от BitmapSource. Приведенные ниже примеры показывают, что повышение качества экспорта с использованием 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 ]

5 голосов
/ 17 февраля 2010

Нет настройки на 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(); 
}

Вы можете создать устройство Direct3D, соответствующее месту, в котором отображается содержимое WPF, следующим образом:

  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() для создания самого устройства

Вероятно, я бы сделал большую часть этого в отдельной библиотеке, написанной на C ++ / CLR, потому что этот подход мне знаком, но вам может быть легко перевести его на чистый C # и управляемый код, используя SlimDX. Я еще не пробовал.

4 голосов
/ 16 февраля 2010

Я не знаю, что такое mip-mapping (или делает ли это программный рендерер и / или многоуровневое сглаживание), но я вспоминаю сообщение Чарльза Петцольда некоторое время назад это все о печати высококачественных изображений WPF 3D.

Я попробовал его с вашим примером кода, и он, кажется, отлично работает. Итак, я полагаю, , что вам нужно было немного увеличить .

Вам необходимо установить Stretch в None на 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;
}

Надеюсь, что решит вашу проблему!

1 голос
/ 16 февраля 2010

Я думаю, что вы получили пустой экран по нескольким причинам. Во-первых, VisualBrush должен был указывать на видимый Visual. Во-вторых, может быть, вы забыли, что у RectangleGeometry должны быть размеры (я знаю, что сначала это сделал).

Я видел некоторые странные вещи, которые я не совсем понимаю. То есть я не понимаю, почему мне пришлось установить AlignmentY на Bottom в VisualBrush.

Кроме этого, я думаю, что это работает ... и я думаю, что вы легко сможете изменить код для вашей реальной ситуации.

Вот обработчик события нажатия кнопки:

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>
0 голосов
/ 20 февраля 2010

У меня также было несколько полезных ответов на этот вопрос на форумах Windows Presentation Foundation по адресу http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/.

В частности, я собираюсь попробовать эти два ответа, оба от Марко Чжоу:

Или вы можете попробовать отрендерить Viewport3D в закадровый HwndSource, а затем захватить его HWND, и передать его в функцию Bitblt. Bitblt скопирует то, что уже визуализируется аппаратным растеризатором вернуться к своему собственному в буфере памяти. я Я не пробую этот метод сам, но стоит попробовать, и теоретически говоря, это должно работать.

и

Я думаю, что один простой способ без пинвокинга в WIN32 API, чтобы разместить Просмотрите Port3D в ElementHost и позвоните его ElementHost.DrawToBitmap (), один будьте осторожны DrawToBitmap () в нужное время после того, как Viewport3D "полностью оказано ", вы можете вручную накачать сообщения по телефону System.Windows.Forms.Application.DoEvents (), и подключить Событие CompositionTarget.Rendering для получить ответный звонок от композиции Тема (это может работать, так как я не точно уверен, если событие рендеринга надежный в этом типичном обстоятельство). Кстати, вышеуказанный метод основан на предположении, что вы не нужно отображать ElementHost на экране. ElementHost будет отображается на экране, вы могли бы напрямую вызвать DrawToBitmap () способ.

0 голосов
/ 17 февраля 2010

Используя SlimDX, попробуйте получить доступ к поверхности DirectX, к которой обращается ViewPort3D,
затем выполнение чтения пикселя для чтения буфера из пиксельного буфера видеокарты в обычную память.
Как только у вас есть (неуправляемый) буфер, скопируйте его в существующий доступный для записи растровый файл, используя небезопасный код или сортировку.

0 голосов
/ 01 февраля 2010

Я думаю, что проблема здесь заключается в том, что программный рендер для WPF не выполняет mip-mapping и многоуровневое сглаживание. Вместо использования RanderTargetBitmap вы можете создать DrawingImage, чья ImageSource - это 3D-сцена, которую вы хотите визуализировать. Теоретически аппаратный рендер должен создавать изображение сцены, которое вы затем программно извлекаете из DrawingImage.

...