RenderTargetBitmap и Viewport3D - Проблемы с качеством
Я хочу экспортировать 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;
}
Ответы
Ответ 1
На RenderTargetBitmap
нет настроек, чтобы передать его на использование с помощью аппаратного обеспечения, поэтому вам придется вернуться к использованию Win32 или DirectX. Я бы рекомендовал использовать технологию DirectX, приведенную в в этой статье. Следующий код из статьи и показывает, как это можно сделать (это код С++):
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, следующим образом:
- Вызов
Visual.PointToScreen
в точке вашего экранного изображения
- Вызов
MonitorFromPoint
в User32.dll
для получения hMonitor
- Вызов
Direct3DCreate9
в d3d9.dll
для получения pD3D
- Вызов
pD3D->GetAdapterCount()
для подсчета адаптеров
- Итерация от 0 до count-1 и вызов
pD3D->GetAdapterMonitor()
и сравнение с ранее полученным hMonitor для определения индекса адаптера
- Вызов
pD3D->CreateDevice()
для создания самого устройства
Я бы, вероятно, сделал большую часть этого в отдельной библиотеке, закодированной в С++/CLR, потому что этот подход мне знаком, но вы можете легко перевести его на чистый С# и управляемый код, используя SlimDX. Я еще не пробовал.
Ответ 2
Я не знаю, что такое mip-mapping (или это средство визуализации программного обеспечения делает это и/или многоуровневое сглаживание), но я помню post от Charles Petzold некоторое время назад, это было все о печати привет-res 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;
}
Надеюсь, что решает вашу проблему!
Ответ 3
Я думаю, что вы получили пустой экран по нескольким причинам. Во-первых, VisualBrush должен был указывать на видимый Визуальный. Во-вторых, возможно, вы забыли, что 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>
Ответ 4
Я думаю, что проблема здесь в том, что программный рендеринг для WPF не выполняет mip-mapping и многоуровневое сглаживание. Вместо использования RanderTargetBitmap
вы можете создать DrawingImage
, чей ImageSource
- это 3D-сцена, которую вы хотите отобразить. Теоретически аппаратная рендеринга должна создавать изображение сцены, которое затем можно программно извлечь из DrawingImage
.
Ответ 5
Используя SlimDX, попробуйте получить доступ к поверхности DirectX, которую визуализирует ViewPort3D,
затем выполняет чтение-пиксель для считывания буфера из буфера пикселей графической карты в обычную память.
После того, как у вас есть (неуправляемый) буфер, скопируйте его в существующее записываемое растровое изображение с использованием небезопасного кода или сортировки.
Ответ 6
У меня также было несколько полезных ответов на этот вопрос на форумах Windows Presentation Foundation на http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/50989d46-7240-4af5-a8b5-d034cb2a498b/.
В частности, я собираюсь попробовать эти два ответа, как от Марко Чжоу:
Или вы можете попробовать выполнить Viewport3D в заставке HwndSource, а затем захватить его HWND, и подавать его в функцию Bitblt. Bitblt скопирует то, что уже предоставляемый аппаратным растеризатором обратно в свой собственный буфер памяти. я Я сам не пробовал этот метод, но это стоит попробовать и теоретически говоря, он должен работать.
и
Я думаю, что один простой способ без скрещивания в API WIN32 - разместить Viewport3D в ElementHost и вызовите его ElementHost.DrawToBitmap(), один оговорка здесь в том, что вам нужно позвонить DrawToBitmap() в нужное время после того, как Viewport3D "полностью рендеринга", вы можете вручную перекачать сообщения по телефону System.Windows.Forms.Application.DoEvents(), и подключить CompositionTarget.Rendering событие для получить обратный вызов из композиции (Это может сработать, так как я не точно, если событие Rendering надежным в этом типичном обстоятельство). BTW, вышеупомянутый метод основывается на предположении, что вы не нужно отображать ElementHost на экране. ElementHost будет отображается на экране, вы можете прямо вызовите DrawToBitmap() Метод.