Direct3D ve WPF? Jo!

Jak možná víte, WPF je postaveno na DirectX, což je výhodné hlavně pokud pracujeme s 3D grafikou, protože renderování probíhá na grafické kartě a je o mnoho rychlejší, než kdyby vše měl počítat procesor, který se takto může starat o jiné procesy. Ano jak jsem napsal, ve WPF můžeme pracovat s 3D.
Takže proč používat Direct3D ve WPF?
Co se 3D programování týče, WPF poskytuje pouze podmnožinu DirectX funkcí. Proč? WPF primárně neslouží pro programování pokročilých 3D her nebo jiných složitých 3D grafických programů. Ve WPF se programují aplikace, kde můžeme použít základní prvky trojrozměrné grafiky, jako vykreslení a transformace objektů v prostoru, umísťování světel a kamer a dalších.
Co ve WPF nejde je například načítání .x modelů nebo skeletální animace, které naopak DirectX zvládne bez problémů … naštěstí však můžeme začlenit DirectX do vytyčeného prostoru naší WPF aplikace, což si ukážeme dále.

DirectX ve WPF
“AirSpace” pravidlo
Musíme si uvědomit, že “míchat” dvě technologie dohromady je docela problém, systém by z toho mohl být zmatený. Představte si, že vymezíme pro DirectX v naší WPF aplikaci prostor, přes který WPF vykreslí žlutý obdelník s 50% průhledností. Jaké vrstvě tyto pixely patří, WPF? DirectX?
V aplikacích proto musíme dodržovat tzv. AirSpace pravidlo (pravidlo prostoru).
Operační systém identifikuje každé okno, form nebo kontrolu v aplikaci přiřazením handleru (HWND). HWND, který je obsluhován některou z technologií musí mít vlastní autonomní “prostor” (AirSpace), z toho vyplývá, že každý pixel aplikace může být vykreslen pouze jednou z technologií!
Příklad vykreslení žlutého (50% průhlednost) obdelníku:

V pořádku. Obdelník vykreslený WPF nezasahuje do prostoru DirectX.

Porušení pravidla! Obdelník vykreslený WPF zasahuje do prostoru DirectX.
Jak tedy vložit Direct3D do WPF aplikace?
Pokud již máte zkušenosti s Direct3D jistě víte, že prvním krokem je vytvoření DirectX “device”, kde v konstruktoru předáme handler (HWND) prostoru, kam bude renderován výstup. Ve WPF nemůžeme použít handler žádné z jeho kontrol. Jedinou možností je využít způsob “hostování” Win32 okna ve WPF pomocí HwndHost (obdobným způsobem bychom mohli vložit WPF obsah do Win32 aplikace pomocí HwndSource) a handler tohoto okna následně předáme do konstruktoru třídy Device.
Co budeme programovat?
Pomocí DirectX načteme model “tiny.x”, kterou následně rozpohybujeme pomocí skeletální animace a zobrazíme v naší WPF aplikaci. Musíme mít nainstalované DirectX SDK (já mám verzi ze srpna 2006), jehož součástí je i ukázkový model “tiny.x”.
Začínáme
Ve Visual Studiu vytvoříme projekt “Windows Application (WPF)”. Nejdříve musíme přidat reference do našeho projektu na následující komponenty.
- Microsoft.DirectX
- Microsoft.DirectX.Direct3D
- Microsoft.DirectX.Direct3DX
- System.Drawing
- System.Windows.Forms
Nyní můžeme vytvořit vzhled aplikace (Window1.xaml)
<!--
********************
*** Window1.xaml ***
********************
-->
<Window x:Class="WpfDirectXSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WpfDirectXSample" SizeToContent="WidthAndHeight"
Background="LightGreen"
>
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0"
Grid.ColumnSpan="2">
WPF (WinFX) application =
<Bold Foreground="DarkGreen">green</Bold>
background
</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1">
Win32 Window =
<Bold Foreground="DarkBlue">blue</Bold>
background (DirectX skeletal animation)
</TextBlock>
<Border Name="Win32Host"
Grid.Column="1" Grid.Row="2"
BorderBrush="Black"
BorderThickness="1" />
</Grid>
</Window>
Takto vypadá naše aplikace nyní.

Jak možná tušíte, rámeček “Win32Host” nám vymezí prostor pro obsah Win32 okna s DirectX.
Hostování Win32 okna ve WPF
Nyní přejděme k vytvoření Win32 okna. Vytvořme nový soubor “Win32Window.cs”. Pro vytvoření a zavření okna použijeme funkce Windows API CreateWindowEx a DestroyWindowCore ze souboru user32.dll.
/****************** * Win32Window.cs * ******************/ using System;using System.Drawing; using System.Windows; using System.Windows.Controls; using System.Windows.Interop; using System.Runtime.InteropServices;namespace WpfDirectXSample { class Win32Window {//PInvoke declarations [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Auto)] internal static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hwndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam); [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Auto)] internal static extern bool DestroyWindow(IntPtr hwnd); internal const int WS_CHILD = 0x40000000, WS_VISIBLE = 0x10000000, HOST_ID = 0x00000002;} }
A nyní podědíme naší třídu Win32Host od třídy HwndHost, která slouží k hostování Win32 kontrol ve WPF aplikacích a naimplementujeme její abstraktní metody.
/******************
* Win32Window.cs *
******************/
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace WpfDirectXSample
{
class Win32Window : HwndHost
{
IntPtr hwndControl;
public IntPtr hwndHost;
int windowHeight, windowWidth;
public Win32Window(double height, double width)
{
windowHeight = (int)height;
windowWidth = (int)width;
}
protected override
HandleRef BuildWindowCore(HandleRef hwndParent)
{
hwndControl = IntPtr.Zero;
hwndHost = IntPtr.Zero;
hwndHost =
CreateWindowEx(0, "static", "",
WS_CHILD | WS_VISIBLE,
0, 0,
windowHeight, windowWidth,
hwndParent.Handle,
(IntPtr)WINDOW_ID,
IntPtr.Zero,
0);
return new HandleRef(this, hwndHost);
}
protected override IntPtr WndProc(
IntPtr hwnd, int msg, IntPtr wParam,
IntPtr lParam, ref bool handled)
{
handled = false;
return IntPtr.Zero;
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(hwnd.Handle);
}
public IntPtr hwndListBox
{
get { return hwndControl; }
}
//PInvoke declarations
[DllImport(”user32.dll”, EntryPoint = “CreateWindowEx”,
CharSet = CharSet.Auto)]
internal static extern
IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport(”user32.dll”, EntryPoint = “DestroyWindow”,
CharSet = CharSet.Auto)]
internal static extern bool DestroyWindow(IntPtr hwnd);
internal const int
WS_CHILD = 0×40000000,
WS_VISIBLE = 0×10000000,
WINDOW_ID = 0×00000002;
}
}
Metoda BuildWindowCore vytváří Win32 okno (to že se jedná o okno a ne nějakou jinou kontrolu určuje WINDOW_ID) a vrátí jeho handler (HWND). Jako každé okno musí i to naše mít svého rodiče. Jeho handler předáváme v parametru hwndParent. Výška a šířka okna je uložena v proměných windowHeight, windowWidth.
Direct3D - skeletální animace
Vložíme nový soubor “DXModel.cs” do projektu, naimplementujeme skeletální animaci.
Pokud pracujeme s Direct3D, musíme vždy nejdříve vytvořit instanci třídy “Device”, ve které jako parametr uvedeme handler našeho vytvořeného Win32 okna. Dále načteme model (tiny.x), kterou budeme renderovat každých 50 milisekund (= 20fps).
Nebudu zde vysvětlovat celou implementaci skeletální animace, protože se to netýka tohoto tématu. Kompletní kód “DXModel.cs” a ostatních souborů jsou ke stažení na konci tutoriálu.
Pozn.: nezapomeňte nahrát model k vaší aplikaci. Stačí nahrát obsah složky “C:\Program Files\Microsoft DirectX SDK\Samples\Media\Tiny” do “…\[slozka_projektu]\bin\Debug\”.
Spojme to dohromady
Nyní máme třídu Win32Window, která vytvoří Win32 okno a třídu Model, která načte animovaný model a vykreslí do okna definovaného parametrem.
Vše spojíme zhruba následovně:
/*******************
* Window1.xaml.cs *
*******************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WpfDirectXSample
{
public partial class Window1 : System.Windows.Window
{
int width = 500;
int height = 400;
Win32Window window;
public Window1()
{
InitializeComponent();
// velikost rámečku Win32Host
Win32Host.Width = width;
Win32Host.Height = height;
// vytvoření Win32 okna
window = new Win32Window(width, height);
Win32Host.Child = window;
this.Loaded +=
new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
Model myModel = new Model();
myModel.Initialize(window.Handle);
}
}
}
Nyní by měla Tiny bez problému kráčet ve vaší WPF aplikaci.

autor: Aleš Šturala
datum: 9. 12. 2006
kontakt: 
zdrojové kódy |
![]() článek |



Naučte se WPF - český tutoriál