In mijn vorige post liet ik zien hoe we bij Detrio Excel grafieken vervangen door 3D interactieve grafieken. Het wordt tijd om te kijken hoe we dit soort applicaties nu maken. Hoe kun je nu 3D plaatjes maken in WPF?
WPF heeft een enorm rijke ondersteuning voor 3D. In de eerste versies van onze 3D toolkit, waar we dit soort systemen mee maken, was helemaal van de grond af aan geschreven in Delphi en C++, waarbij we gebruik maakten van OpenGL om de 3D plaatjes te maken. Veel van het werk dat we gedaan hebben in die library hebben we weggegooid: alle low-level code zit nu standaard in WPF (zodat wij ons kunnen orienteren op de dingen die echt belangrijk zijn).
Maar laten we beginnen bij het begin.
In Visual Studio (Express, Professional of hoger: maakt niet uit welke versie je hebt) maak je een nieuw WPF project aan. Je krijgt een XAML bestand met daarin de volgende code:
<Window x:Class="WpfApplication10.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
</Grid>
</Window>
Dit is onze basis. We krijgen een Window met daarin een Grid. Die Grid kan weg, die hebben we nu niet nodig.
Om 3D plaatjes te tekenen moeten we als eerste een soort van Canvas hebben. Een canvas is het stuk waarop getekend kan gaan worden. WPF heeft daarvoor de Viewport3D class: dit is ons tekengebied. Onze code ziet er nu als volgt uit:
<Window x:Class="WpfApplication10.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<!-- De Viewport3D is ons tekengebied -->
<Viewport3D>
</Viewport3D>
</Window>
Tot zover is het niet echt spannend. Als je dit runt krijg je een leeg, wit scherm. Had je anders verwacht?
Laten we eens iets toevoegen. Er zijn een paar dingen die je altijd moet doen. Alles wat je in 3D WPF tekent wordt geplaatst in een soort virtuele wereld. Deze wereld bestaat alleen in het geheugen van je computer: dit wordt niet weergegeven op je scherm. Om te laten zien wat je getekend hebt zul je een camera moeten toevoegen: deze camera zorgt ervoor dat alles wat in het bereik van deze camera is ook daadwerkelijk op het scherm geplaatst wordt. Je kunt dus meerdere camera's maken en in je applicatie tussen camera's switchen!
WPF heeft twee soorten camera's: de OrthographicCamera en de PerspectiveCamera. De eerste is een camera waarbij geen rekening gehouden wordt met perspectief, de tweede doet dat wel. In de praktijk komt het neer op het volgende: als je twee blokken van dezelfde grootte hebt, waar er een dicht bij de camera staat en de tweede staat ver weg, dan zal bij de OrthographicCamera het tweede blok net zo groot getekend worden als de eerste. Bij de PerspectiveCamera zal de tweede kleiner worden getekend dan de eerste (wat meer overeenkomt hoe wij mensen de wereld zien).
De OrthographicCamera wordt voornamelijk gebruikt voor architectuur toepassingen: daar is het belangrijk dat alle objecten weergegeven worden op het formaat dat ze ook echt zijn. Natuurlijk zien wij het anders: wij zien twee blokken waarbij er een groter lijkt dan de ander. Kijk eens naar het volgende voorbeeld. Als eerste zie je een scene met twee blokken en een PerspectiveCamera. Daarnaast zie je dezelfde scene maar met de OrthographicCamera.
Ik heb hier alleen de camera verandert: je ziet dat het hele perspectief vertekend wordt (en het 'bovenste' blok lijkt groter!).
Voor realistische weergave is het beter om een PerspectiveCamera te gebruiken: OrthographicCamera wordt niet al te vaak toegepast.
We voegen deze camera toe aan onze ViewPort3D:
<Window x:Class="WpfApplication10.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera Position="0 0 5"
LookDirection="0 0 -1"
UpDirection="0 1 0"
FieldOfView="60.0"
/>
</Viewport3D.Camera>
</Viewport3D>
</Window>
De camera heeft een aantal properties die je moet zetten. Als eerste de position. Zoals ik al zei worden alle object in het geheugen van je computer geplaatst, dit geldt ook voor de camera. Je moet wel leren om in 3 dimensies te denken: er is een z-positie bijgekomen. Als je gewend bent aan het tekenen in een 2D omgeving is er nog iets wat je moet weten: de coordinaten zijn anders dan je gewend bent. Ten eerste is bij een 2D applicatie het punt (0,0) linksboven in je tekening, bij 3D is (0,0,0) linksonder. Daarnaast is er dus de 3e dimensie bijgekomen, dit is de z-as. Hoe je die definieert is eigenlijk iets wat je zelf kunt bepalen, maar meestal zie je dat de z-as naar je toe een positieve waarde geeft en van je af (dus de diepte in) een negatieve. Dit is even wennen, maar als snel weet je niet beter. Als laatste moet je weten dat bij 2D applicaties je de coordinaten meestal in pixels opgeeft: bij 3D in WPF is dat niet zo. Als we zeggen dat we een box hebben met een breedte van 1, dan zegt dat eigenlijk niets over de grootte op het scherm. Hoeveel pixels hij op het scherm in gebruik heeft is afhankelijk van de locatie van de box in de 3D wereld, van de plaatsing van de camera en van het type camera. Alle locaties en punten in WPF 3D zijn dan ook doubles. In de tekeningen hierboven zijn de boxen 1 logische unit hoog, breed en diep. Ze staan 4 units van elkaar. Zoals je ziet heeft dat niets met pixels te maken!
Goed. Onze camera staat dus op locatie 0, 0, 5, oftewel op de X en Y as, maar 5 punten naar ons toe. Als we dat bepaalt hebben moeten we ook opgeven waar de camera naar kijkt. Hier geven we een vector op: we zeggen dat de LookDirection recht langs de z-as naar achteren kijkt (dus vooruit gezien vanuit ons: negatieve z-waardes gaan de diepte in!): LookDirection is 0,0,-1. Als we een andere waarde voor X opgeven dan kijkt de camera meer naar links of rechts, bij andere waardes van Y omhoog of omlaag.
Dan kunnen we ook nog aangeven wat de bovenkant van de camera is: we kunnen de camera immers ook op z'n kop houden! In ons geval is de UpDirection (0,1,0) oftewel rechtomhoog langs de Y-as.
Bij de PerspectiveCamera geven we als laatste ook nog de FieldOfView op: dit geeft aan hoe groot de hoek is waarmee we kijken (60 graden in ons geval). Eigenlijk bepalen we hier of we een groothoek of zoomlens gebruiken. Ook kun je door deze waarde te varieren een zoom-effect bereiken (verkleinen van de FieldOfView haalt objecten dichterbij).
Als we nu onze applicatie runnen zien we.... niets.
Nee, natuurlijk niet: met een virtuele camera een lege ruimte fotograferen levert een foto van een lege ruimte op.
Ik ga in een volgende post in op de objecten die daadwerkelijk zichtbaar zijn, maar om je alvast iets te geven om mee te spelen, moet je even de volgende code toevoegen aan je applicatie:
<Window x:Class="WpfApplication10.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Viewport3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions="0 0 0 1 0 0 1 1 0 0 1 0"
TriangleIndices="0 1 2 0 2 3"/>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial Brush="Black"/>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
<Viewport3D.Camera>
<PerspectiveCamera Position="0 0 5"
LookDirection="0 0 -1"
UpDirection="0 1 0"
FieldOfView="60.0"
/>
</Viewport3D.Camera>
</Viewport3D>
</Window>
We tekenen hier een vierkant, die als het goed is op het scherm verschijnt.
Speel eens met verschillende waardes in de PerspectiveCamera en kijk wat er gebeurt! De volgende post gaat meer in op de Geometry die je hier in de code ziet.