Question RenderTargetBitmap et Viewport3D - Problèmes de qualité


Je veux exporter une scène 3D d'un Viewport3D vers un bitmap.

La manière évidente de le faire serait d'utiliser RenderTargetBitmap - toutefois, la qualité du bitmap exporté est nettement inférieure à celle de l'image à l'écran. En regardant sur Internet, il semble que RenderTargetBitmap ne profite pas du rendu matériel. Ce qui signifie que le rendu se fait à Niveau 0. Ce qui signifie pas de mip-mapping, d'où la qualité réduite de l'image exportée.

Est-ce que quelqu'un sait comment exporter un bitmap d'un Viewport3D à la qualité à l'écran?

Clarification

Bien que l'exemple ci-dessous ne le montre pas, Je dois éventuellement exporter le bitmap du Viewport3D dans un fichier. Si je comprends bien, la seule façon de procéder est de transformer l'image en quelque chose qui dérive de BitmapSource. Cplotts ci-dessous montre que l'augmentation de la qualité de l'exportation à l'aide de RenderTargetBitmap améliore l'image, mais que le rendu est encore effectué dans le logiciel, il est trop lent.

Existe-t-il un moyen d'exporter une scène 3D rendue dans un fichier, en utilisant le rendu matériel? Cela devrait sûrement être possible?

Vous pouvez voir le problème avec ce 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>

Et ce code:

    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;
    }

16
2018-02-01 18:24


origine


Réponses:


Il n'y a pas de réglage sur RenderTargetBitmap pour lui dire de rendre en utilisant du matériel, vous devrez donc utiliser Win32 ou DirectX. Je recommanderais d'utiliser la technique DirectX donnée dans Cet article. Le code suivant de l'article et montre comment cela peut être fait (c'est du code 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(); 
}

Vous pouvez créer le périphérique Direct3D correspondant à l'emplacement où le contenu WPF est rendu comme suit:

  1. Appel Visual.PointToScreen sur un point de votre image à l'écran
  2. Appel MonitorFromPoint dans User32.dll pour obtenir le hMonitor
  3. Appel Direct3DCreate9 dans d3d9.dll obtenir un pD3D
  4. Appel pD3D->GetAdapterCount() compter les adaptateurs
  5. Itération de 0 à 1 et appel pD3D->GetAdapterMonitor() et comparer avec le hMonitor précédemment récupéré pour déterminer l'index de l'adaptateur
  6. Appel pD3D->CreateDevice() créer l'appareil lui-même

Je ferais probablement la plupart de cela dans une bibliothèque séparée codée en C ++ / CLR, car cette approche m’est familière, mais vous trouverez peut-être facile de le traduire en code C # pur et en code géré en utilisant SlimDX.  Je n'ai pas encore essayé.


5
2018-02-16 22:30



Je ne sais pas ce qu'est le mip-mapping (ou si le moteur de rendu fait cela et / ou l'anti-aliasing à plusieurs niveaux), mais je me souviens d'un poster par Charles Petzold, il y a quelque temps, il s'agissait d'imprimer des images 3D haute résolution de WPF.

Je l'ai essayé avec votre exemple de code et il semble fonctionner correctement. Donc, je suppose tu avais juste besoin de faire évoluer les choses un peu.

Vous devez définir Stretch sur None sur rtbImage et modifier le gestionnaire d'événements Click comme suit:

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;
}

Espérons que cela résout votre problème!


4
2018-02-15 22:09



Je pense que vous obteniez un écran vide pour quelques raisons. Tout d'abord, VisualBrush devait pointer sur un visuel visible. Deuxièmement, vous avez peut-être oublié que le RectangleGeometry devait avoir des dimensions (je sais que je l'ai fait au début).

J'ai vu des choses étranges que je ne comprends pas très bien. Autrement dit, je ne comprends pas pourquoi j'ai dû définir AlignmentY sur Bottom sur VisualBrush.

A part ça, je pense que ça marche ... et je pense que vous devriez pouvoir facilement modifier le code pour votre situation réelle.

Voici le gestionnaire d'événements du bouton click:

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;
}

Voici 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>

1
2018-02-16 15:31



Je pense que le problème ici est que le moteur de rendu de WPF n’effectue pas de mip-mapping et d’anti-aliasing à plusieurs niveaux. Plutôt que d'utiliser RanderTargetBitmap, vous pouvez être en mesure de créer un DrawingImage dont ImageSource est la scène 3D que vous souhaitez rendre. En théorie, le rendu du matériel devrait produire l’image de la scène, que vous pouvez ensuite extraire par programmation du DrawingImage.


0
2018-02-01 18:49



En utilisant SlimDX, essayez d'accéder à la surface DirectX vers laquelle ViewPort3D est rendu,
puis effectuer une lecture-pixel pour lire le tampon du tampon de pixels de la carte graphique dans la mémoire régulière.
Une fois que vous avez le tampon (non géré), copiez-le dans un bitmap inscriptible existant à l'aide d'un code non sécurisé ou d'un marshalling.


0
2018-02-17 07:40



J'ai aussi eu quelques réponses utiles à cette question sur les forums de Windows Presentation Foundation à http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/.

En particulier, je vais essayer ces deux réponses, toutes deux de Marco Zhou:

Ou vous pouvez essayer de rendre le   Viewport3D dans un hors écran   HwndSource, puis prenez son HWND,   et alimentez-le en fonction Bitblt. le   Bitblt va copier ce qui est déjà   rendu par le rasterizer matériel   de retour dans votre propre mémoire tampon. je   Je n'essaie pas cette méthode moi-même, mais   cela vaut la peine d'essayer, et théoriquement   en parlant, ça devrait marcher.

et

Je pense un moyen facile sans se moquer   dans l'API WIN32 est de placer le   Viewport3D dans ElementHost, et appelez   son ElementHost.DrawToBitmap (), un   attention ici est que vous devez appeler   le DrawToBitmap () au bon moment   après le Viewport3D est "entièrement   rendu ", vous pouvez pomper manuellement   messages en appelant   System.Windows.Forms.Application.DoEvents (),   et brancher   CompositionTarget.Rendering event to   obtenir un rappel de la composition   thread (cela pourrait fonctionner puisque je ne suis pas   exactement si l'événement de rendu est   fiable dans ce typique   circonstance). BTW, la méthode ci-dessus   est basé sur l'hypothèse que vous   n'a pas besoin d'afficher ElementHost   sur l'écran. Le ElementHost sera   affiché à l'écran, vous pouvez   appeler directement le DrawToBitmap ()   méthode.


0
2018-02-20 11:55