3D Graphics using WPF 3D – Part 1

The basic idea of creating 3D graphics is to have a three dimensional model of an object. Because our screen is only two dimensional, we define a camera that takes a picture of the object. The picture is a projection of the object to a planar surface. Camera’s differ based on the projection mechanism. We have PersepectiveCamera and OrthogonalCamera in WPF. Perspective projection is based on the human eye and camera. It provides foreshortening. ie, farther the object smaller they appear on the screen. And parallel lines appears to converge. In orthogonal projection parallel lines stays parallel and they are more used for technical drawings.

In WPF we have Model3D, abstract base class that represents a generic 3-D object. To build a 3-D scene, you need some objects to view, and the objects that make up the scene graph derive from Model3D. And modeling geometries can done with GeometryModel3D. The Geometry property of this model takes a mesh primitive. A surface of a 3D object is called a mesh. For that we have MeshGeometry3D class, which allows you to specify any geometry. A mesh is defined by a number of 3D points. These points are called vertices. Each vertex is specified as a Point3D. The vertices are joined together by a winding pattern to define the triangles. Every triangle has a front and a back side. Only the front side is rendered. The front side is defined by the winding order of the points. WPF uses a counter clockwise winding pattern. Depending on its geometry, your mesh might be composed of many triangles, some of which share the same vertices. To draw the mesh correctly, the WPF needs information about which vertices are shared by which triangles. You provide this information by specifying a list of triangle indices with the TriangleIndices property. This list specifies the winding order.

In the world of 3D graphics, all objects are described by a set of triangles. The reason for this is that a triangle is the most granular geometry to describe a planar surface. They are always co-planar and convex polygons. Because of this property the rendering engine can calculate the colour of each triangle depending on its material and angle to the lights in the scene.

For a mesh to look like a three-dimensional object, it must have an applied texture to cover the surface defined by its vertices and triangles so it can be lit and projected by the camera. In 2-D, you use the Brush class to apply colours, patterns, gradients, or other visual content to areas of the screen. The appearance of 3-D objects, however, is a function of the lighting model, not just of the colour or pattern applied to them. Real-world objects reflect light differently depending on the quality of their surfaces: glossy and shiny surfaces(Specular Lighting) don’t look the same as rough or matte surfaces(Diffused Lighting), and some objects seem to absorb light while others glow.

To define the characteristics of a model’s surface, WPF uses the Material abstract class. The concrete subclasses of Material determine some of the appearance characteristics of the model’s surface, and each also provides a Brush property to which you can pass a Brush.  The material defines how much light is reflected for a specific angle and the brush defines the colour. A brush can either be a simple colour or a gradient or even an image called texture. To specify a Material to apply to the backface of a model like a plane, set the model’s BackMaterial property.

Lights in 3-D graphics do what lights do in the real world: they make surfaces visible. Light objects in WPF create a variety of light and shadow effects and are modelled after the behaviour of various real-world lights. You must include at least one light in your scene, or no models will be visible. Here I uses AmbientLight. It provides ambient lighting that illuminates all objects uniformly regardless of their location or orientation.

Finally, 3-D graphics content in WPF is encapsulated in an element, Viewport3D. The graphics system treats Viewport3D as a two-dimensional visual element like many others in WPF. Viewport3D functions as a window—a viewport—into a three-dimensional scene. More accurately, it is a surface on which a 3-D scene is projected.

Following is the code for simple 3D triangle. You need to add following references into your solution (If you creates a empty project, else create WPF project).

  • Presentation
  • PresenationFramework
  • System.Xaml
  • System
  • WindowsBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace Simple3DScene
{
    class Simple3DScene : Window
    {
        PerspectiveCamera camera = new PerspectiveCamera();

        [STAThread]
        public static void Main()
        {
            Application app = new Application();
            app.Run(new Simple3DScene());
        }

        public Simple3DScene()
        {
            Title = "3D Triangle";

            // Make DockPanel  content of window.
            DockPanel dock = new DockPanel();
            Content = dock;

            // Create scroll bar for moving camera.
            ScrollBar scroll = new ScrollBar();
            scroll.Orientation = Orientation.Horizontal;
            scroll.Value = 0;
            scroll.Minimum = -20;
            scroll.Maximum = 20;
            scroll.ValueChanged += ScrollBarOnValueChanged;
            dock.Children.Add(scroll);
            DockPanel.SetDock(scroll, Dock.Bottom);

            // Create Viewport3D for 3D scene.
            Viewport3D viewport = new Viewport3D();
            dock.Children.Add(viewport);

            // Define the MeshGeometry3D.
            MeshGeometry3D mesh = new MeshGeometry3D();
            mesh.Positions.Add(new Point3D(0, 0, 0));
            mesh.Positions.Add(new Point3D(0.5, 1, 0));
            mesh.Positions.Add(new Point3D(1, 0, 0));
            mesh.TriangleIndices = new Int32Collection(new int[] { 0, 1, 2 });

            // Define the GeometryModel3D.
            GeometryModel3D geomed = new GeometryModel3D();
            geomed.Geometry = mesh;
            geomed.Material = new DiffuseMaterial(Brushes.Magenta);
            geomed.BackMaterial = new DiffuseMaterial(Brushes.Cyan);

            // Create ModelVisual3D for GeometryModel3D.
            ModelVisual3D modvis = new ModelVisual3D();
            modvis.Content = geomed;
            viewport.Children.Add(modvis);

            // Create another ModelVisual3D for light.
            modvis = new ModelVisual3D();
            modvis.Content = new AmbientLight(Colors.White);
            viewport.Children.Add(modvis);

            // Create the camera.
            camera = new PerspectiveCamera();
            camera.Position = new Point3D(0, 0, 5);
            camera.LookDirection = new Vector3D(0, 0, -1);
            camera.FieldOfView = 45;
            viewport.Camera = camera;    }

        private void ScrollBarOnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            camera.Position = new Point3D(e.NewValue, 0, 5);
            camera.LookDirection = new Vector3D(-e.NewValue, 0, -5);
        }
    }
}

Here I have used a scrollbar and changed the camera position according to scrolling. Consider following properties of camera.

  • Position(Point3D): Specifies location of camera in the 3D scene.
  • LookDirection: Specifies the direction that the camera is pointing.
  • FieldOfView: Define camera’s horizontal field of view in degrees.

Try changing these and triangle vertices.