Sky box implementation

Topics: Developer Forum
Mar 5, 2008 at 2:12 AM
Edited Mar 5, 2008 at 2:32 AM
If you know what a sky box is, this is it. If not, search around the net; it's easy to find good explanations.

Here's the code:

public class SkyBox : ModelVisual3D
{
#region Fields

private ScaleTransform3D scale;

#endregion

#region Dependency Properties

public double Size
{
get { return (double)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}

public static readonly DependencyProperty SizeProperty =
DependencyProperty.Register("Size", typeof(double),
typeof(SkyBox),
new FrameworkPropertyMetadata(OnSizeChanged)
);

private static void OnSizeChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
SkyBox sb = sender as SkyBox;

double size = sb.Size;

sb.scale.ScaleX = size;
sb.scale.ScaleY = size;
sb.scale.ScaleZ = size;
}

#endregion

#region Constructor

public SkyBox()
{
Model3DGroup sides = new Model3DGroup();

Point3D [] p = new Point3D [] {
new Point3D(-1, 1, -1),
new Point3D(-1, -1, -1),
new Point3D(1, -1, -1),
new Point3D(1, 1, -1),
new Point3D(1, 1, 1),
new Point3D(1, -1, 1),
new Point3D(-1, -1, 1),
new Point3D(-1, 1, 1)
};

Int32Collection triangleIndices = new Int32Collection(
new int[] { 0, 1, 2, 2, 3, 0 });

PointCollection textCoords = new PointCollection(
new Point [] {
new Point(0,0),
new Point(0,1),
new Point(1,1),
new Point(1,0)
});

MeshGeometry3D quad = new MeshGeometry3D();
quad.Positions.Add(p0);
quad.Positions.Add(p1);
quad.Positions.Add(p2);
quad.Positions.Add(p3);
quad.TriangleIndices = triangleIndices;
quad.TextureCoordinates = textCoords;
sides.Children.Add(new GeometryModel3D(quad, GetSideMaterial("north")));

quad = new MeshGeometry3D();
quad.Positions.Add(p4);
quad.Positions.Add(p5);
quad.Positions.Add(p6);
quad.Positions.Add(p7);
quad.TriangleIndices = triangleIndices;
quad.TextureCoordinates = textCoords;
sides.Children.Add(new GeometryModel3D(quad, GetSideMaterial("south")));

quad = new MeshGeometry3D();
quad.Positions.Add(p1);
quad.Positions.Add(p6);
quad.Positions.Add(p5);
quad.Positions.Add(p2);
quad.TriangleIndices = triangleIndices;
quad.TextureCoordinates = textCoords;
sides.Children.Add(new GeometryModel3D(quad, GetSideMaterial("down")));

quad = new MeshGeometry3D();
quad.Positions.Add(p7);
quad.Positions.Add(p6);
quad.Positions.Add(p1);
quad.Positions.Add(p0);
quad.TriangleIndices = triangleIndices;
quad.TextureCoordinates = textCoords;
sides.Children.Add(new GeometryModel3D(quad, GetSideMaterial("west")));

quad = new MeshGeometry3D();
quad.Positions.Add(p3);
quad.Positions.Add(p2);
quad.Positions.Add(p5);
quad.Positions.Add(p4);
quad.TriangleIndices = triangleIndices;
quad.TextureCoordinates = textCoords;
sides.Children.Add(new GeometryModel3D(quad, GetSideMaterial("east")));

quad = new MeshGeometry3D();
quad.Positions.Add(p7);
quad.Positions.Add(p0);
quad.Positions.Add(p3);
quad.Positions.Add(p4);
quad.TriangleIndices = triangleIndices;
quad.TextureCoordinates = textCoords;
sides.Children.Add(new GeometryModel3D(quad, GetSideMaterial("up")));


this.scale = new ScaleTransform3D(1, 1, 1);
this.Transform = this.scale;
this.Content = sides;
}

private Material GetSideMaterial(string sideFilename)
{
ImageBrush ib = new ImageBrush(
new BitmapImage(
new Uri(
"pack://application:,,,/MyAssemblyName;"
+ "component/SkyBoxImages/" + sideFilename + ".jpg",
UriKind.Absolute)
));

ib.ViewportUnits = BrushMappingMode.Absolute;
ib.TileMode = TileMode.None;
return new DiffuseMaterial(ib);
}

#endregion
}

      • In the code above, since I don't know how to designate code blocks, the square-bracketed array indices appear as hyperlinks...please beware when you copy/paste the code, and make sure the square brackets are restored. Sorry about that... ***

VERY IMPORTANT NOTES:

You need skybox images. I used six images from this thread:

http://blenderartists.org/forum/showthread.php?t=96131

The images start halfway down the page. There are six of them, named "up," "north," "east," and so on.

Also, check out Terragen; it's a fantastic, amazing tool for creating sky boxes and terrain.

Place these images in a folder in your project, and set the Build Action to "Resource."

Next, you must change the URI used by this code to point to the correct folder in your assembly. In the code above, see "MyAssemblyName." This URI must be changed to match not only your assembly name, but the path to the folder in which you place the six image files. In my example, I placed the image files in a folder called "SkyBoxImages."

To use the skybox in your app, assuming you have a Viewport3D called viewport3D:

SkyBox sb = new SkyBox();
sb.Size = 10000; // or whatever you'd like. This sets the bounds of the box.
viewport3D.Children.Add(sb);

Make sure your viewport's FarPlaneDistance isn't less than your sky box's size.

One more thing: If your app uses the WPF 3D Tools' Trackball or TrackballDecorator, you may see distortion of the skybox as you zoom in and out. This is because the trackball implementation, rather than move the camera about the 3D world, changes a scale transformation to zoom in and out. Instead, try the skybox in conjunction with the Roam3DDecorator that I provided in a previous thread. That decorator actually moves the camera around in the 3D world, and does not affect the scale factor, so the skybox won't be distorted.

Enjoy!

Kary