3D Graphics using WPF

We show you how to develop a program to let you explore the effect of changing some of the many variables in a 3D application. You might like to create your own version with XAML code exported from a tool such as Blender. Enthusiasts might decide to convert .3ds Models to XAML Resources.

Microsoft provides an excellent introduction to 3D graphics entitled 3-D Graphics Overview. It has clear explanations, diagrams and sample code. We base the XAML code on the C# cone demonstration on that page and add Oxygene key-handling code to show the effect of changing the camera position (WASD, Q and Ctrl+Q keys) its LookDirection (X, Ctrl+X Y, Ctrl+Y, Z and Ctrl+Z), UpDirection (I, J, K, L) and field of view (up and down arrows). Also, the user can control the light direction with the T,F,G,H, R and Ctrl+R keys. The design view of the form is useful; you can see immediately the result of changing values in the XAML code.

To compile this example, start a new Oxygene for .Net console application named WPF_3D_Demo and paste in the Oxygene code below to replace the default code of Window1.xaml.pas and the XAML code to replace the default code in Window1.xaml. The created window, after clicking a few buttons, appears as follows.

Output

Output

Oxygene code of Window1.xaml.pas

namespace WPF_3D_Demo;

interface

uses
  System.Collections.Generic,
  System.Linq,
  System.Windows,
  System.Windows.Input,
  System.Windows.Controls,
  System.Windows.Data,
  System.Windows.Documents,
  System.Windows.Media,
  System.Windows.Media.Media3D,
  System.Windows.Navigation,
  System.Windows.Shapes;

type
  Window1 = public partial class(System.Windows.Window)
  private
    method Window_KeyDown(sender: System.Object; e: System.Windows.Input.KeyEventArgs);
    lookX, lookY, lookZ, camX, camY, camZ, upX, upY, upZ, lightX, lightY, lightZ: Real;
  public
    constructor;
  end;
  
implementation

constructor Window1;
begin
  InitializeComponent();
  lookX := 0;
  lookY := 1;
  lookZ := 15;
  camX := 0;
  camY := 0;
  camZ := -3;
  upX := 0;
  upY := 1;
  upZ := 0;
  lightX := 3;
  lightY := -4;
  lightZ := 5;
end;

method Window1.Window_KeyDown(sender: System.Object; e: System.Windows.Input.KeyEventArgs);
begin
  case e.Key of   
    Key.Up: pcam.FieldOfView := pcam.FieldOfView - 5;
    Key.Down: pcam.FieldOfView := pcam.FieldOfView + 5;
    Key.X: begin
             if Keyboard.IsKeyDown(Key.LeftCtrl) or Keyboard.IsKeyDown(Key.RightCtrl) then 
               dec(lookX)
             else
               inc(lookX);
             pcam.LookDirection := new Vector3D(lookX, lookY, lookZ);
           end;
    Key.Y: begin
             if Keyboard.IsKeyDown(Key.LeftCtrl) or Keyboard.IsKeyDown(Key.RightCtrl) then 
               dec(lookY)
             else
               inc(lookY);
             pcam.LookDirection := new Vector3D(lookX, lookY, lookZ);
           end;
    Key.Z: begin
             if Keyboard.IsKeyDown(Key.LeftCtrl) or Keyboard.IsKeyDown(Key.RightCtrl) then 
               dec(lookZ)
             else
               inc(lookZ);
             pcam.LookDirection := new Vector3D(lookX, lookY, lookZ);
           end;
    Key.D: begin
             camX := camX + 0.2;
             pcam.Position := new Point3D(camX, camY, camZ);
           end;     
    Key.A: begin
             camX := camX - 0.2;    
             pcam.Position := new Point3D(camX, camY, camZ);
           end;
    Key.W: begin
             camY := camY - 0.2;
             pcam.Position := new Point3D(camX, camY, camZ);
           end;     
    Key.S: begin
             camY := camY + 0.2;    
             pcam.Position := new Point3D(camX, camY, camZ);
           end;
    Key.Q: begin
             if Keyboard.IsKeyDown(Key.LeftCtrl) or Keyboard.IsKeyDown(Key.RightCtrl) then
               camZ := camZ - 0.5
             else 
               camZ := camZ + 0.5; 
             pcam.Position := new Point3D(camX, camY, camZ);
           end; 
    Key.L: begin
             upX := upX + 0.2;
             pcam.UpDirection := new Vector3D(upX, upY, upZ);
           end;     
    Key.J: begin
             upX := upX - 0.2;    
             pcam.UpDirection := new Vector3D(upX, upY, upZ);
           end;
    Key.I: begin
             upY := upY - 0.2;
             pcam.UpDirection := new Vector3D(upX, upY, upZ);
           end;     
    Key.K: begin
             upY := upY + 0.2;    
             pcam.UpDirection := new Vector3D(upX, upY, upZ);
           end;

    Key.H: begin
             lightX := lightX + 1;
             light.Direction := new Vector3D(lightX, lightY, lightZ);
           end;     
    Key.F: begin
             lightX := lightX - 1;
             light.Direction := new Vector3D(lightX, lightY, lightZ);
           end;
    Key.T: begin
             lightY := lightY + 1;
             light.Direction := new Vector3D(lightX, lightY, lightZ);
           end;     
    Key.G: begin
             lightY := lightY - 1;
             light.Direction := new Vector3D(lightX, lightY, lightZ);
           end;
    Key.R: begin
             if Keyboard.IsKeyDown(Key.LeftCtrl) or Keyboard.IsKeyDown(Key.RightCtrl) then
               lightZ := lightZ - 1
             else 
               lightZ := lightZ + 1; 
             light.Direction := new Vector3D(lightX, lightY, lightZ);
           end; 
  end;    
end;
  
end.

XAML code of Window1.xaml

<?xml version='1.0' encoding='utf-8' ?>
<Window x:Class="WPF3DMS.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Cone" Height="300" Width="300" KeyDown="Window_KeyDown">
  <Grid>     
    <Viewport3D Name="vp" ><!-- The rendering surface -->
      <!-- Add camera. -->
      <Viewport3D.Camera>
        <PerspectiveCamera x:Name="pcam" FarPlaneDistance="20"	LookDirection="0,1,15" UpDirection="0,1,0" 
						   NearPlaneDistance="1" Position="0,0,-3" FieldOfView="40" />
      </Viewport3D.Camera>
      <!-- Add models. -->
      <Viewport3D.Children>
        <ModelVisual3D>
          <ModelVisual3D.Content>
            <Model3DGroup >
              <Model3DGroup.Children>
                <!-- Add DirectionalLight, MeshGeometry3D and DiffuseMaterial -->
                <DirectionalLight x:Name="light"  Color="#FFFFFFFF" Direction="3, -5, 5" />
                <!-- Define a red cone. -->
                <GeometryModel3D>
                  <GeometryModel3D.Geometry>
                    <MeshGeometry3D 
                      Positions="0.293 -0.5 0.404  0.475 -0.5 0.155  0 0.5 0  0.476 -0.5 0.154 0 0.5
                                 0  0 0.5 0  0.476 -0.5 0.155  0.476 -0.5 -0.155  0 0.5 0  0.476 -0.5
                                 -0.155  0 0.5 0  0 0.5 0  0.476 -0.5 -0.155  0.294 -0.5 -0.405  0 0.5 0
                                 0.294 -0.5 -0.405  0 0.5 0  0 0.5 0  0.294 -0.5 -0.405  0 -0.5 -0.5  0 0.5
                                 0  0 -0.5 -0.5  0 0.5 0  0 0.5 0  0 -0.5 -0.5  -0.294 -0.5 -0.405  0 0.5 0 
                                -0.294 -0.5 -0.405  0 0.5 0  0 0.5 0  -0.294 -0.5 -0.405  -0.476 -0.5 -0.155
                                 0 0.5 0  -0.476 -0.5 -0.155  0 0.5 0  0 0.5 0  -0.476 -0.5 -0.155  -0.476 -0.5
                                 0.155  0 0.5 0  -0.476 -0.5 0.155  0 0.5 0  0 0.5 0  -0.476 -0.5 0.155  -0.294 -0.5
                                 0.405  0 0.5 0  -0.294 -0.5 0.405  0 0.5 0  0 0.5 0  -0.294 -0.5 0.405  0 -0.5 0.5
                                 0 0.5 0  0 -0.5 0.5  0 0.5 0  0 0.5 0  0 -0.5 0.5  0.294 -0.5 0.405  0 0.5 0  0.294
                                 -0.5 0.405  0 0.5 0  0 0.5 0" 
                      Normals="0.724,0.447,0.526  0.276,0.447,0.851  0.531,0.429,0.731  0.276,0.447,0.851
                               0,0.429,0.903  0.531,0.429,0.731  0.276,0.447,0.851 -0.276,0.447,0.851  0,0.429,0.903
                               -0.276,0.447,0.851  -0.531,0.429,0.731  0,0.429,0.903  -0.276,0.447,0.851  -0.724,0.447,
                               0.526  -0.531,0.429,0.731  -0.724,0.447,0.526  -0.859,0.429,0.279 -0.531,0.429,0.731  -0.724,0.447,
                               0.526  -0.894,0.447,0  -0.859,0.429,0.279  -0.894,0.447,0  -0.859,0.429,-0.279  -0.859,0.429,0.279  -0.894,
                               0.447,0  -0.724,0.447,-0.526  -0.859,0.429,-0.279  -0.724,0.447,-0.526  -0.531,0.429,-0.731  -0.859,
                               0.429,-0.279  -0.724,0.447,-0.526  -0.276,0.447,-0.851  -0.531,0.429,-0.731  -0.276,0.447,-0.851 0,0.429,
                               0.903  -0.531,0.429,-0.731  -0.276,0.447,-0.851  0.276,0.447,-0.851  0,0.429,-0.903  0.276,0.447,-0.851  0.531,
                               0.429,-0.731  0,0.429,-0.903  0.276,0.447,-0.851  0.724,0.447,-0.526  0.531,0.429,-0.731  0.724,0.447,
                               -0.526  0.859,0.429,-0.279  0.531,0.429,-0.731  0.724,0.447,-0.526  0.894,0.447,0  0.859,0.429,-0.279  0.894,0.447,
                                0  0.859,0.429,0.279  0.859,0.429,-0.279  0.894,0.447,0  0.724,0.447,0.526  0.859,0.429,0.279  0.724,0.447,
                                0.526  0.531,0.429,0.731  0.859,0.429,0.279"
                      TriangleIndices="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
                                       36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 " />
                  </GeometryModel3D.Geometry>
                  <GeometryModel3D.Material>
                    <DiffuseMaterial>
                      <DiffuseMaterial.Brush>
                        <SolidColorBrush Color="Red" Opacity="1.0"/>
                      </DiffuseMaterial.Brush>
                    </DiffuseMaterial>
                  </GeometryModel3D.Material>
                </GeometryModel3D>
              </Model3DGroup.Children>
            </Model3DGroup>
          </ModelVisual3D.Content>
        </ModelVisual3D>
      </Viewport3D.Children>
    </Viewport3D>
  </Grid>
</Window>
Programming - a skill for life!

How to write programs in the Oxygene for .Net dialect of Pascal