c# .net wpf 3d rendertargetbitmap

c# - RenderTargetBitmap y Viewport3D-Problemas de calidad



.net wpf (6)

Quiero exportar una escena 3D de un Viewport3D a un mapa de bits.

La forma obvia de hacer esto sería usar RenderTargetBitmap; sin embargo, cuando lo hago, la calidad del mapa de bits exportado es significativamente inferior a la imagen en pantalla. Mirando a través de Internet, parece que RenderTargetBitmap no aprovecha la representación de hardware. Lo que significa que la representación se realiza en el Nivel 0 . Lo que significa que no hay mapas mip, por lo tanto, la calidad reducida de la imagen exportada.

¿Alguien sabe cómo exportar un mapa de bits de un Viewport3D con calidad de pantalla?

Aclaración

Aunque el ejemplo dado a continuación no muestra esto, debo exportar el mapa de bits de Viewport3D a un archivo. Según entiendo, la única forma de hacer esto es obtener la imagen en algo que se derive de BitmapSource. Los gráficos que se muestran a continuación muestran que el aumento de la calidad de la exportación con RenderTargetBitmap mejora la imagen, pero como el renderizado todavía se realiza en el software, es prohibitivamente lento.

¿Hay una manera de exportar una escena 3D renderizada a un archivo, utilizando la representación de hardware? Seguramente eso debería ser posible?

Puedes ver el problema con este 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>

Y este código:

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


Creo que el problema aquí es que el renderizador de software para WPF no realiza el mapeo mip y el suavizado de niveles múltiples. En lugar de usar RanderTargetBitmap , puede crear una DrawingImage cuya ImageSource es la escena 3D que desea representar. En teoría, el procesamiento de hardware debe producir la imagen de la escena, que luego puede extraer de la DrawingImage .


Creo que obtuviste una pantalla en blanco por un par de razones. Primero, el VisualBrush necesitaba estar apuntando a un Visual visible. En segundo lugar, tal vez olvidó que la geometría Rectangle necesitaba tener dimensiones (sé que lo hice al principio).

Vi algunas cosas extrañas que no entiendo muy bien. Es decir, no entiendo por qué tuve que establecer AlineaciónY en Inferior en VisualBrush.

Aparte de eso, creo que funciona ... y creo que debería poder modificar fácilmente el código para su situación real.

Aquí está el botón de hacer clic en el controlador de eventos:

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

Aquí está 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>


No hay ninguna configuración en RenderTargetBitmap para decirle que se renderice con hardware, por lo que tendrá que recurrir a Win32 o DirectX. Recomendaría el uso de la técnica de DirectX dada en este artículo . El siguiente código del artículo y muestra cómo se puede hacer (este es el código 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(); }

Puede crear el dispositivo Direct3D correspondiente al lugar donde se representa el contenido de WPF de la siguiente manera:

  1. Llamar a Visual.PointToScreen en un punto dentro de su imagen en pantalla
  2. Llamando a MonitorFromPoint en User32.dll para obtener el hMonitor
  3. Llamando a Direct3DCreate9 en d3d9.dll para obtener un pD3D
  4. Llamando a pD3D->GetAdapterCount() para contar adaptadores
  5. Iterando de 0 a count-1 y llamando a pD3D->GetAdapterMonitor() y comparando con el hMonitor recuperado anteriormente para determinar el índice del adaptador
  6. Llamando a pD3D->CreateDevice() para crear el propio dispositivo

Probablemente haría la mayor parte de esto en una biblioteca separada codificada en C ++ / CLR porque me resulta familiar este enfoque, pero puede que le resulte fácil traducirlo a C # puro y código administrado usando SlimDX. Todavía no lo he intentado.


No sé qué es mip-mapping (o si el renderizador de software hace eso y / o multi-nivel anti-aliasing), pero recuerdo un post de Charles Petzold hace un tiempo que trataba sobre la impresión de alta resolución WPF Imágenes 3D.

Lo probé con su código de muestra y parece que funciona muy bien. Entonces, asumo que solo necesitabas escalar un poco las cosas .

Debe establecer Estirar en Ninguno en rtbImage y modificar el controlador de eventos Click de la siguiente manera:

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

Espero que resuelva tu problema!


También he recibido algunas respuestas útiles a esta pregunta en los foros de Windows Presentation Foundation en http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/ .

En particular, voy a probar estas dos respuestas, ambas de Marco Zhou:

O podría intentar renderizar el Viewport3D en un HwndSource fuera de pantalla, y luego tomar su HWND, y alimentarlo en la función Bitblt. El Bitblt copiará lo que ya ha sido renderizado por el rasterizador de hardware en el búfer de memoria. No estoy probando este método por mí mismo, pero vale la pena intentarlo y, teóricamente, debería funcionar.

y

Creo que una manera fácil sin pinvoking en la API de WIN32 es colocar el Viewport3D en ElementHost y llamar a su ElementHost.DrawToBitmap (), una advertencia aquí es que debe llamar al DrawToBitmap () en el momento adecuado después de que Viewport3D sea " completamente renderizado ", podría bombear manualmente los mensajes llamando a System.Windows.Forms.Application.DoEvents (), y conectar el evento CompositionTarget.Rendering para obtener una devolución de llamada desde el hilo de la composición (Esto podría funcionar ya que no estoy exactamente seguro de si El evento de renderizado es confiable en esta circunstancia típica). Por cierto, el método anterior se basa en el supuesto de que no es necesario mostrar el ElementHost en la pantalla. El ElementHost se mostrará en la pantalla, puede llamar directamente al método DrawToBitmap ().


Usando SlimDX, intente acceder a la superficie DirectX a la que se procesa ViewPort3D,
luego, realice una lectura de píxeles para leer el búfer de la tarjeta de gráficos en la memoria regular.
Una vez que tenga el búfer (no administrado), cópielo en un mapa de bits de escritura existente utilizando código inseguro o ordenación.