Discover Nine Inch Nails!
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 – animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Papervision3D : Part 4 – lighting and shading
A point light source is added to the scene. Fours materials with different characteristics are added to spheres to illustrate how Papervision3D performs shading. - First steps in Papervision3D : Part 5 – scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects. - First steps in Papervision3D : Part 6 – Texture mapping
Create materials using bitmap data with the aim of having more detailed or realistic objects.
This article is essentially a continuation of the previous one, building on the texture mapped materials. As I said in my last post, the aim now is to add lighting to add more realism and provide depth to the scene – much like we did in Part 4.
Previously for the shading we used specific materials to account for the different shading methods such as GouraudMaterial or PhongMaterial (as you will find in the package org.papervision3d.materials.shadematerials of the pv3d source). These however are based on coloured materials rather than textured materials.
To mix both texture maps and shading we need to create a Shader and add this with the BitmapMaterial (like we created in Part 6) together in a ShadedMaterial. Papervision3D does all the magic necessary to mix the two to create a shaded bitmap material.
As you can see if you look in the org.papervision3d.materials.shaders package in the pv3d source there are a number of different shaders available – some of which we recognise from Part 4. The list includes FlatShader, GouraudShader, PhongShader, CellShader and EnvMapShader.
The last one of the above allows us to add an environment map to the material which essentially is like adding lighting from a surrounding environment and makes the material look like it is highly reflective. Take a look at this wikipedia page on reflection mapping for more details.
Another concept that is important in producing more realistic scenes is bump mapping. This is basically an optimised way of modifying the lighting of a surface so that it appears as if the surface has bumps in it. It can make a big difference to a rendered object in terms of realism without having to specify many object vertices. Again, wikipedia has lots of information on bump mapping. With Papervision3D we can add bump maps to enhance both the Phong and environment shaders.
So, on with the code… As usual, I’m modifying the code from the previous article so there’s not a huge change. However to account for the different number of shaders, rather than show all of them at once I’m only showing one sphere at a time so that you get a better idea of what each shader does. To change the shader I’ve modified the scene interaction so that clicking on the sphere changes the material dynamically – another great feature of Papervision3D! Anyway here’s the code:
package { import flash.display.Bitmap; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import org.papervision3d.events.InteractiveScene3DEvent; import org.papervision3d.lights.PointLight3D; import org.papervision3d.materials.BitmapMaterial; import org.papervision3d.materials.shaders.CellShader; import org.papervision3d.materials.shaders.EnvMapShader; import org.papervision3d.materials.shaders.FlatShader; import org.papervision3d.materials.shaders.GouraudShader; import org.papervision3d.materials.shaders.PhongShader; import org.papervision3d.materials.shaders.ShadedMaterial; import org.papervision3d.materials.shaders.Shader; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.objects.primitives.Sphere; import org.papervision3d.view.BasicView; public class Example007 extends BasicView { [Embed(source="/../assets/pv3d.png")] private var Pv3dBitmapImage:Class; [Embed(source="/../assets/randomBump.png")] private var BumpImage:Class; [Embed(source="/../assets/mountains.png")] private var EnvImage:Class; private var pv3dBitmap:Bitmap = new Pv3dBitmapImage(); private var bumpMap:Bitmap = new BumpImage(); private var envMap:Bitmap = new EnvImage(); private var bitmapMaterial:BitmapMaterial; private var sphere:Sphere; private var light:PointLight3D; private var doRotation:Boolean = false; private var lastMouseX:int; private var lastMouseY:int; private var cameraPitch:Number = 60; private var cameraYaw:Number = -60; private var shaders:Array = ["flat", "cell", "gouraud", "phong", "phongBump", "env", "envBump"]; private var shaderIndex:int = 0; private var shaderText:TextField; private var textFormat:TextFormat; public function Example007() { super(0, 0, true, true); // set up the stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // Initialise Papervision3D init3D(); // Create the 3D objects createScene(); // Listen to mouse up and down events on the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); stage.addChild(shaderText); // Start rendering the scene startRendering(); } private function init3D():void { // position the camera camera.z = -500; camera.orbit(60, -60); } private function createScene():void { // create text and format to display current shader type textFormat = new TextFormat(); textFormat.size = 20; textFormat.font = "Arial"; shaderText = new TextField(); shaderText.x = 50; shaderText.y = 50; shaderText.textColor = 0xFFFFFF; shaderText.text = "flat"; shaderText.setTextFormat(textFormat); shaderText.autoSize = TextFieldAutoSize.LEFT; // Specify a point light source and its location light = new PointLight3D(true); light.x = 500; light.y = 500; light.z = -200; // create bitmap material with smoothing bitmapMaterial = new BitmapMaterial(pv3dBitmap.bitmapData, false); bitmapMaterial.smooth = true; // create sphere sphere = new Sphere(getShadedBitmapMaterial(bitmapMaterial, "flat"), 150, 20, 20); // Add a listener to the spheres to listen to InteractiveScene3DEvent events sphere.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject); // Add the light and sphere to the scene scene.addChild(sphere); scene.addChild(light); } private function getShadedBitmapMaterial(bitmapMaterial:BitmapMaterial, shaderType:String):ShadedMaterial { var shader:Shader; if (shaderType == "flat") { // create new flat shader shader = new FlatShader(light, 0xFFFFFF, 0x333333); } else if (shaderType == "cell") { // create new cell shader with 5 colour levels shader = new CellShader(light, 0xFFFFFF, 0x333333, 5); } else if (shaderType == "gouraud") { // create new gouraud shader shader = new GouraudShader(light, 0xFFFFFF, 0x333333); } else if (shaderType == "phong") { // create new phong shader shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50); } else if (shaderType == "phongBump") { // create new phong shader with bump map shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50, bumpMap.bitmapData); } else if (shaderType == "env") { // create new environment map shader shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333); } else if (shaderType == "envBump") { // create new environment map shader with bump map shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333, bumpMap.bitmapData); } // create new shaded material by combining the bitmap material with shader var shadedMaterial:ShadedMaterial = new ShadedMaterial(bitmapMaterial, shader); shadedMaterial.interactive = true; return shadedMaterial; } override protected function onRenderTick(event:Event=null):void { // rotate the sphere sphere.yaw(-1); // If the mouse button has been clicked then update the camera position if (doRotation) { // convert the change in mouse position into a change in camera angle var dPitch:Number = (mouseY - lastMouseY) / 2; var dYaw:Number = (mouseX - lastMouseX) / 2; // update the camera angles cameraPitch -= dPitch; cameraYaw -= dYaw; // limit the pitch of the camera if (cameraPitch <= 0) { cameraPitch = 0.1; } else if (cameraPitch >= 180) { cameraPitch = 179.9; } // reset the last mouse position lastMouseX = mouseX; lastMouseY = mouseY; // reposition the camera camera.orbit(cameraPitch, cameraYaw); } // call the renderer super.onRenderTick(event); } // called when mouse down on stage private function onMouseDown(event:MouseEvent):void { doRotation = true; lastMouseX = event.stageX; lastMouseY = event.stageY; } // called when mouse up on stage private function onMouseUp(event:MouseEvent):void { doRotation = false; } // called when mouse down on a sphere private function onMouseDownOnObject(event:InteractiveScene3DEvent):void { var object:DisplayObject3D = event.displayObject3D; // calculate index of next shader shaderIndex++; if (shaderIndex == shaders.length) { shaderIndex = 0; } // dynamically modify the material of the object and update text object.material = getShadedBitmapMaterial(bitmapMaterial, shaders[shaderIndex]); shaderText.text = shaders[shaderIndex]; shaderText.setTextFormat(textFormat); } } }
Click on the image below to see this in action. Clicking on the sphere will change the material – in this case change the shader associated with the textured material – and moving the mouse while clicking anywhere in the window will rotate the camera around the sphere.
Compared to the previous posts the construction, initialisation of 3D and scene renderer remain virually the same so I won’t go into details here. You’ll see that, as with the texture map, the bump map and environment map are embedded in the flash animation and come from image files. These are then converted into Bitmap objects.
[Embed(source="/../assets/pv3d.png")] private var Pv3dBitmapImage:Class; [Embed(source="/../assets/randomBump.png")] private var BumpImage:Class; [Embed(source="/../assets/mountains.png")] private var EnvImage:Class; private var pv3dBitmap:Bitmap = new Pv3dBitmapImage(); private var bumpMap:Bitmap = new BumpImage(); private var envMap:Bitmap = new EnvImage();
For sake of completeness, the images I use are below. The pv3d symbol comes directly from the papervision3d developer site, the bump map I made myself just by adding random noise to a blank image and blurring it (thanks to Gimp) and the environment image comes from my neighbourhood Alps.
Texture map image:

Bump map:

Environment map:

Remark: Actually I had to invert the environment map image for this demo to have it appear the right way up when rendered. If anyone can tell me why I’d be happy to know…
The main modification to the code compared to the previous post occurs in the createScene method. Firstly a simple Text object is created to display the current shader type – nothing really interesting here!
// create text and format to display current shader type textFormat = new TextFormat(); textFormat.size = 20; textFormat.font = "Arial"; shaderText = new TextField(); shaderText.x = 50; shaderText.y = 50; shaderText.textColor = 0xFFFFFF; shaderText.text = "flat"; shaderText.setTextFormat(textFormat); shaderText.autoSize = TextFieldAutoSize.LEFT;
We then put the light back in that we removed in the last article – each shader is associated with a single light source.
// Specify a point light source and its location light = new PointLight3D(true); light.x = 500; light.y = 500; light.z = -200;
As with Part 6, we create a BitmapMaterial using the texture map data. As before I’m using normal rather than accurate perspective (the second parameter of the constructor set to false) to improve the performance. I am however choosing to smooth the material which produces a much nicer looking texture map. This is of course at the cost of performance…
// create bitmap material with smoothing bitmapMaterial = new BitmapMaterial(pv3dBitmap.bitmapData, false); bitmapMaterial.smooth = true;
The sphere is then created using a call to getShadedBitmapMaterial to enhance the bitmap material with a shader – to start off with, just a simple flat shader – and an event listener added to allow us to change the material.
// create sphere sphere = new Sphere(getShadedBitmapMaterial(bitmapMaterial, "flat"), 150, 20, 20); // Add a listener to the spheres to listen to InteractiveScene3DEvent events sphere.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
So how do we add a shader to a BitmapMaterial? The different types of shaders used in this demo are created in the getShadedBitmapMaterial method
private function getShadedBitmapMaterial(bitmapMaterial:BitmapMaterial, shaderType:String):ShadedMaterial { var shader:Shader; if (shaderType == "flat") { // create new flat shader shader = new FlatShader(light, 0xFFFFFF, 0x333333); } else if (shaderType == "cell") { // create new cell shader with 5 colour levels shader = new CellShader(light, 0xFFFFFF, 0x333333, 5); } else if (shaderType == "gouraud") { // create new gouraud shader shader = new GouraudShader(light, 0xFFFFFF, 0x333333); } else if (shaderType == "phong") { // create new phong shader shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50); } else if (shaderType == "phongBump") { // create new phong shader with bump map shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50, bumpMap.bitmapData); } else if (shaderType == "env") { // create new environment map shader shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333); } else if (shaderType == "envBump") { // create new environment map shader with bump map shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333, bumpMap.bitmapData); } // create new shaded material by combining the bitmap material with shader var shadedMaterial:ShadedMaterial = new ShadedMaterial(bitmapMaterial, shader); shadedMaterial.interactive = true; return shadedMaterial; }
As you can see, the simple shaders (“flat”, “gouraud”, “cell” and “phong”) are created exactly the same as for the ShadeMaterials we introduced in Part 4 using essentially a light colour and an ambient colour. The new types of shaders here come from using the bump map and environment map.
The PhongShader allows us to add bump map data. This is added very simply by using the bump map image data we loaded before. As you can see with the demo this changes a lot the appearance of the sphere – we really get the impression that it has a bumpy surface.
The EnvMapShader is also very simple to use. It takes a light source as for all the other shaders then two texture maps for the environment – a front and back. Here for simplicity I just used the same image for both. It then takes an ambient colour for when it is not facing the light source. To add a bump map we simple add as well the bitmap data of the bump map image.
Finally, what we create is a ShadedMaterial: this simply takes the material texture map and the shader and combines the two.
The only other major difference from previous articles is the dynamic changing of object materials. This is performed when a mouse click event is detected on the sphere.
// called when mouse down on a sphere private function onMouseDownOnObject(event:InteractiveScene3DEvent):void { var object:DisplayObject3D = event.displayObject3D; // calculate index of next shader shaderIndex++; if (shaderIndex == shaders.length) { shaderIndex = 0; } // dynamically modify the material of the object and update text object.material = getShadedBitmapMaterial(bitmapMaterial, shaders[shaderIndex]); shaderText.text = shaders[shaderIndex]; shaderText.setTextFormat(textFormat); }
Dynamically changing the material of an object is a new feature in Papervision3D and is very simple to implement: simply change the value of the material member of the object! In the above method we just cycle through the array of shader types and update the text associated… almost too easy!
And that’s all there is too it! I’ve not really gone into the mechanics behind bump mapping and environment mapping because I’m sure you can find explanations much better that I could give elsewhere on the web. But hopefully this has given some insight into how just a few simple lines can totally change the appearance of a rendered object. Comments and suggestions, as always, are very welcome!
Next article:
- First steps in Papervision3D : Part 8 – Movie materials
Show animated and interactive Flash video streams and movies on surfaces of 3D objects.
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 – animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Papervision3D : Part 4 – lighting and shading
A point light source is added to the scene. Fours materials with different characteristics are added to spheres to illustrate how Papervision3D performs shading. - First steps in Papervision3D : Part 5 – scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects.
In this article I’d like to start taking a look at texture mapping : taking bitmap data and mapping it to a surface. In the previous animations in this series, the objects have had materials of a particular colour, enhanced by different shading models. Using bitmap data we can add much more realism to a scene.
To start with some useful links that provide a good overview of texture mapping with Papervision3D :
- dispatchEvent() : Papervision3D Part2 : Features
Provides a good introduction to some of the essentials of texture mapping (as well as other features in Papervision3D) - insideRIA : Textures (WireFrame, Bitmap, MovieAsset, Video, etc.) PaperVision3D
Lots of useful information on different material types including some essential texture mapping details.
I’d really recommend reading the first of these links because it gives a good description of UV coordinates related with images, allowing us to map rectangular images to displayed triangles. This is also an important concept involved in tiling. Tiling allows us to have a particular image repeated over a surface.
What I’d like to do in this article is to start off very simply and show how to add a bitmap texture to cubes and spheres, with and without tiling. So, no shading for the moment – that’ll be covered in the next article – and lets just see how to cover a shape with a bitmap material.
The code below is based on code from the previous article. However, I’m removing the light source for the time being, adding cubes as well as spheres and, of course, changing the materials to use bitmap data.
package { import caurina.transitions.Tweener; import flash.display.Bitmap; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import org.papervision3d.events.InteractiveScene3DEvent; import org.papervision3d.materials.BitmapMaterial; import org.papervision3d.materials.utils.MaterialsList; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.objects.primitives.Cube; import org.papervision3d.objects.primitives.Sphere; import org.papervision3d.view.BasicView; public class Example006 extends BasicView { [Embed(source="/../assets/pv3d.png")] private var MyTextureImage:Class; private static const ORBITAL_RADIUS:Number = 200; private var bitmap:Bitmap = new MyTextureImage(); private var cube1:Cube; private var cube2:Cube; private var sphere1:Sphere; private var sphere2:Sphere; private var objectGroup:DisplayObject3D; private var doRotation:Boolean = false; private var lastMouseX:int; private var lastMouseY:int; private var cameraPitch:Number = 60; private var cameraYaw:Number = -60; public function Example006() { super(0, 0, true, true); // set up the stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // Initialise Papervision3D init3D(); // Create the 3D objects createScene(); // Listen to mouse up and down events on the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); // Start rendering the scene startRendering(); } private function init3D():void { // position the camera camera.z = -500; camera.orbit(60, -60); } private function createScene():void { // create interactive bitmap material var bitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false); bitmapMaterial.interactive = true; // create an interactive tiled bitmap material (bitmap tiled as 2 x 2) var tiledBitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false); tiledBitmapMaterial.interactive = true; tiledBitmapMaterial.tiled = true; tiledBitmapMaterial.maxU = 2; tiledBitmapMaterial.maxV = 2; // create cube with simple bitmap material cube1 = new Cube(getBitmapMaterials(bitmapMaterial), 100, 100, 100); cube1.x = ORBITAL_RADIUS; // create cube with tiled bitmap material cube2 = new Cube(getBitmapMaterials(tiledBitmapMaterial), 100, 100, 100); cube2.x = -ORBITAL_RADIUS; // create sphere with simple bitmap material sphere1 = new Sphere(bitmapMaterial, 50, 10, 10); sphere1.z = ORBITAL_RADIUS; // create sphere with tiled bitmap material sphere2 = new Sphere(tiledBitmapMaterial, 50, 10, 10); sphere2.z = -ORBITAL_RADIUS; // Create a 3D object to group the spheres objectGroup = new DisplayObject3D(); objectGroup.addChild(cube1); objectGroup.addChild(cube2); objectGroup.addChild(sphere1); objectGroup.addChild(sphere2); // Add a listener to each of the spheres to listen to InteractiveScene3DEvent events cube1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject); cube2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject); sphere1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject); sphere2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject); // Add the light and spheres to the scene scene.addChild(objectGroup); } private function getBitmapMaterials(bitmapMaterial:BitmapMaterial):MaterialsList { // create list of materials for all faces of the cube, // all with the same bitmap material var materials:MaterialsList = new MaterialsList(); materials.addMaterial(bitmapMaterial, "all"); return materials; } override protected function onRenderTick(event:Event=null):void { // rotate the objects cube1.yaw(-3); cube2.yaw(-3); sphere1.yaw(-3); sphere2.yaw(-3); // rotate the group of objects objectGroup.yaw(1); // If the mouse button has been clicked then update the camera position if (doRotation) { // convert the change in mouse position into a change in camera angle var dPitch:Number = (mouseY - lastMouseY) / 2; var dYaw:Number = (mouseX - lastMouseX) / 2; // update the camera angles cameraPitch -= dPitch; cameraYaw -= dYaw; // limit the pitch of the camera if (cameraPitch <= 0) { cameraPitch = 0.1; } else if (cameraPitch >= 180) { cameraPitch = 179.9; } // reset the last mouse position lastMouseX = mouseX; lastMouseY = mouseY; // reposition the camera camera.orbit(cameraPitch, cameraYaw); } // call the renderer super.onRenderTick(event); } // called when mouse down on stage private function onMouseDown(event:MouseEvent):void { doRotation = true; lastMouseX = event.stageX; lastMouseY = event.stageY; } // called when mouse up on stage private function onMouseUp(event:MouseEvent):void { doRotation = false; } // called when mouse down on a sphere private function onMouseDownOnObject(event:InteractiveScene3DEvent):void { var object:DisplayObject3D = event.displayObject3D; Tweener.addTween(object, {y:200, time:1, transition:"easeOutSine", onComplete:function():void {goBack(object);} }); } // called when a tween created in onMouseDownOnObject has terminated private function goBack(object:DisplayObject3D):void { Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"}); } } }
This produces the following Flash animation (click on image below) where we see the four spinning objects rotating about the origin. As with Part 5, you can interact with the scene by clicking and moving the mouse (to rotate the scene) and by clicking on the objects to make them jump. What we now see though are the objects covered with a material using bitmap data.
The first thing to note with this code is the embedding of an image in the compiled Flash file :
[Embed(source="/../assets/pv3d.png")] private var MyTextureImage:Class;
This is then converted into a Bitmap as follows :
private var bitmap:Bitmap = new MyTextureImage();
The embed meta-tag allows assets to be embedded in to the swf file. Rather than going into the details of embedding here, you can find some useful links at assertTrue and with the Flex livedocs. You’ll note that the source is “../assets” : this is because I have my assets folder at the same level as the src folder – when the Actionscript is compiled, the root folder is the src folder. If you want to compile the above source then simply create an assets folder, put any image file in it and change the above line to point to your own image.
The constructor, intialisation of the the 3D and the event handlers are all identical to the previous article in this series so I won’t go into details here. The main changes are in the createScene method.
Rather than coat our objects with shaded materials, we are going to create BitmapMaterials. A BitmapMaterial simply takes bitmap data (which comes from our embedded image) :
// create interactive bitmap material var bitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false); bitmapMaterial.interactive = true;
The BitmapMaterial constructor takes the bitmap data from the image we created earlier and a boolean value to indicate whether we want normal or accurate rendering to occur. Texture mapping requires a lot of CPU so Papervision3D gives the possibility to speed up the rendering at the cost of less accurate perspective. You can see an example of the distortion that occurs with this Papervision3D demo. I’ve selected false here to have quick rendering.
To create a tiled bitmap material, we create another BitmapMaterial and modify certain parameters :
// create an interactive tiled bitmap material (bitmap tiled as 2 x 2) var tiledBitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false); tiledBitmapMaterial.interactive = true; tiledBitmapMaterial.tiled = true; tiledBitmapMaterial.maxU = 2; tiledBitmapMaterial.maxV = 2;
Here we set tiled to true to allow tiling to occur. maxU and maxV are then modified to indicate how many tiles we want to cover the material. I’ve specified 2 by 2 tiling.
These materials are then simply added to the cubes and spheres exactly as we did with the shaded materials before.
// create cube with simple bitmap material cube1 = new Cube(getBitmapMaterials(bitmapMaterial), 100, 100, 100); cube1.x = ORBITAL_RADIUS; // create cube with tiled bitmap material cube2 = new Cube(getBitmapMaterials(tiledBitmapMaterial), 100, 100, 100); cube2.x = -ORBITAL_RADIUS; // create sphere with simple bitmap material sphere1 = new Sphere(bitmapMaterial, 50, 10, 10); sphere1.z = ORBITAL_RADIUS; // create sphere with tiled bitmap material sphere2 = new Sphere(tiledBitmapMaterial, 50, 10, 10); sphere2.z = -ORBITAL_RADIUS;
Actually, this is the first time that I’ve put a cube into one of these examples and I should note that rather than taking a simple material object, they take a MaterialsList that allows us to specify a different material for each face. For this demo I want all the faces to be the same so the material is added to the list using the “all” tag.
private function getBitmapMaterials(bitmapMaterial:BitmapMaterial):MaterialsList { // create list of materials for all faces of the cube, // all with the same bitmap material var materials:MaterialsList = new MaterialsList(); materials.addMaterial(bitmapMaterial, "all"); return materials; }
You can add different materials using “front”, “back”, “top”, “bottom”, “left” and “right” tags.
So, at its very simplest, that’s all there is to do to create texture mapped objects. As always I’d recommend taking a look at the Papervision3D sources as there’s a lot of stuff not covered here. For example you’ll see that there are a number of different Bitmap materials in the org.papervision3d.materials package, for example the BitmapFileMaterial that allows you to load a bitmap at run time from a URL.
In the next article I’ll introduce (or rather put back) shading on top of the texture map. To do this we need in effect a kind of composite material : one that has the texture map and another that has the shaded colour… anyway, I’ll leave that for next time! I hope at least that for the time being this gives some help in getting started!
Next article:
- First steps in Papervision3D : Part 7 – Texture mapping with lighting, bump mapping and environment mapping
Advanced materials using specific shaders to enhance texture mapped materials with bump mapping and environment mapping.
With this post I’m taking a quick step back from my First Steps in Papervision3D series to examine the zoom, focus and field of view camera parameters. With OpenGL, which is what I’m used to, the viewport is specified with a field of view (FoV), being the vertical visible angle, and then translations are made to bring the observer closer or further away from a particular subject. With a camera, as in Papervision3D, its only normal that you can modify the zoom and change the focus, but what do these variables mean and how do these change the visible aspects of the rendered scene?
I decided to write a little demo to explore the effects of each of these variables to get a better feeling of what they do visually. I have to admit that before doing this it wasn’t immediately obvious what was happening when I changed the focus : zoom I could understand because objects got larger when I increased the zoom. What I didn’t realise was that for a particular value of zoom, changing the focus changes the field of view and vice versa. So, unlike OpenGL, the field of view isn’t constant : zooming and changing the focus are related through the field of vision.
Looking in the code of Papervision3D, specifically the class CameraObject3D, you’ll see that the equation governing the three variables is :
f = h / (z * tan α)
where f = focus, h = half the viewport height and α is half the vertical field of view. This is shown more clearly in this diagram, where, if we ignore the zoom then tan α = h / f from simple trigonometry.
So we see that clearly focus, in Papervision3D, is simply the distance between the camera and the viewport, given by the field of vision (being 2 * α).
Zooming is equivalent to magnifying elements at the viewport. With a 2* zoom, an object appears twice as big. Hence, without changing focus, we need to diminish the field of vision so that an object has an effective size twice its actual. As seen below this relates to tan α = h / (2 * f)
Generalising this rule we get tan α = h / (z * f).
So what does this mean? When you create a camera object in Papervision3D, the field of vision does not remain fixed if you zoom or change the focus. If you change the field of vision (manually), this relates to a change in focus (keeping the zoom fixed). Visually, if you reduce the zoom (keeping focus fixed) or decrease the focus (keeping zoom fixed) you will increase the field of vision and can easily have a fish-eye lens effect occurring on the screen – a distorted, extreme perspective view. Similarly, by increasing either the zoom or focus you decrease the field of vision, so for a large focus and large zoom you can have a very small field of vision at which point the perspective becomes much less noticeable.
So, here’s the demo (click on the image below) that illustrates these characteristics. You can use the sliders to change the zoom and focus – you’ll see the field of vision change. Similarly you can specify the field of vision and see the focus change. Also, to illustrate another way of “zooming” you can simply modify the position of the camera (keeping zoom and focus fixed) – this is what I’ve been used to in OpenGL transformations. You can move the camera around the scene by clicking on the scene and moving the mouse. You can see quite clearly that decreasing either zoom or focus can easily produce a fish-eye lens effect on the scene.
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 – animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Papervision3D : Part 4 – lighting and shading
A point light source is added to the scene. Fours materials with different characteristics are added to spheres to illustrate how Papervision3D performs shading.
In this post, rather than adding more 3D rendering to the scene, I’d like to illustrate how Papervision3D provides us with a very simple interface so that users can interact with objects that we’ve created. As someone coming from an OpenGL background this is real magic ! Each triangle that we draw in a scene can listen to mouse events and call a specified function for example when we click on it or move the mouse over it.
The code sample that I show below is based on the previous post : four spheres with different shaded materials rotating about the origin. I’m now adding two different types of event listeners : a listener for standard Flash mouse events allowing the user can move around the scene by changing the camera position and a listener for Papervision3D InteractiveScene3DEvent events. The first of these is added to the stage, the second to individual DisplayObject3D objects.
To illustrate the interactions with the scene objects, I’m adding a third new element to code : tweens. Without having to calculate and specify the change in positions (for example) of objects within the code, a tween does all the hard work for us. A tween works on a particular property of an object and at each frame of an animation modifies this property in a particular manner. I’m not going to go into much detail here as its not the purpose of this post but you’ll find a lot of information on the web – here’s wikipedia‘s definition for what its worth. The tweens I use here come from the opensource library Tweener which provides tweens for Actionscript2 and Actionscript3 as well as JavaScript and HaXE. To install it in your Eclipse/Flex Builder 3 environment follow the same guidelines that I gave with installing Papervision3D but rather than importing the source using SVN, simply download the zip file of the source from the Tweener homepage and extract it into the src folder of a new Tweener project. When its compiled link your own Actionscript project to the Tweener project.
Here’s the full code for the interactive scene and including the imports of the new Tweener library.
package {
import caurina.transitions.Tweener;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import org.papervision3d.core.proto.MaterialObject3D;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.materials.shadematerials.CellMaterial;
import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
import org.papervision3d.materials.shadematerials.GouraudMaterial;
import org.papervision3d.materials.shadematerials.PhongMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Sphere;
import org.papervision3d.view.BasicView;
public class Example005 extends BasicView {
private static const ORBITAL_RADIUS:Number = 200;
private var sphere1:Sphere;
private var sphere2:Sphere;
private var sphere3:Sphere;
private var sphere4:Sphere;
private var sphereGroup:DisplayObject3D;
private var light:PointLight3D;
private var doRotation:Boolean = false;
private var lastMouseX:int;
private var lastMouseY:int;
private var cameraPitch:Number = 60;
private var cameraYaw:Number = -60;
public function Example005() {
// specify that we want the scene to be interactive
super(0, 0, true, true);
// set up the stage
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
// Initialise Papervision3D
init3D();
// Create the 3D objects
createScene();
// Listen to mouse up and down events on the stage
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
// Start rendering the scene
startRendering();
}
private function init3D():void {
// position the camera
camera.z = -500;
camera.orbit(cameraPitch, cameraYaw);
}
private function createScene():void {
// Specify a point light source and its location
light = new PointLight3D(true);
light.x = 400;
light.y = 1000;
light.z = -400;
// Create a new material (flat shaded) and make it interactive
var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x6654FF, 0x060433);
flatShadedMaterial.interactive = true;
sphere1 = new Sphere(flatShadedMaterial, 50, 10, 10);
sphere1.x = -ORBITAL_RADIUS;
// Create a new material (flat shaded) and make it interactive
var gouraudMaterial:MaterialObject3D = new GouraudMaterial(light, 0x6654FF, 0x060433);
gouraudMaterial.interactive = true;
sphere2 = new Sphere(gouraudMaterial, 50, 10, 10);
sphere2.x = ORBITAL_RADIUS;
// Create a new material (flat shaded) and make it interactive
var phongMaterial:MaterialObject3D = new PhongMaterial(light, 0x6654FF, 0x060433, 150);
phongMaterial.interactive = true;
sphere3 = new Sphere(phongMaterial, 50, 10, 10);
sphere3.z = -ORBITAL_RADIUS;
// Create a new material (flat shaded) and make it interactive
var cellMaterial:MaterialObject3D = new CellMaterial(light, 0x6654FF, 0x060433, 5);
cellMaterial.interactive = true;
sphere4 = new Sphere(cellMaterial, 50, 10, 10);
sphere4.z = ORBITAL_RADIUS;
// Create a 3D object to group the spheres
sphereGroup = new DisplayObject3D();
sphereGroup.addChild(sphere1);
sphereGroup.addChild(sphere2);
sphereGroup.addChild(sphere3);
sphereGroup.addChild(sphere4);
// Add a listener to each of the spheres to listen to InteractiveScene3DEvent events
sphere1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere);
sphere2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere);
sphere3.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere);
sphere4.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere);
// Add the light and spheres to the scene
scene.addChild(sphereGroup);
scene.addChild(light);
}
override protected function onRenderTick(event:Event=null):void {
// rotate the spheres
sphere1.yaw(-8);
sphere2.yaw(-8);
sphere3.yaw(-8);
sphere4.yaw(-8);
// rotate the group of spheres
sphereGroup.yaw(3);
// If the mouse button has been clicked then update the camera position
if (doRotation) {
// convert the change in mouse position into a change in camera angle
var dPitch:Number = (mouseY - lastMouseY) / 2;
var dYaw:Number = (mouseX - lastMouseX) / 2;
// update the camera angles
cameraPitch -= dPitch;
cameraYaw -= dYaw;
// limit the pitch of the camera
if (cameraPitch <= 0) {
cameraPitch = 0.1;
} else if (cameraPitch >= 180) {
cameraPitch = 179.9;
}
// reset the last mouse position
lastMouseX = mouseX;
lastMouseY = mouseY;
// reposition the camera
camera.orbit(cameraPitch, cameraYaw);
}
// call the renderer
super.onRenderTick(event);
}
// called when mouse down on stage
private function onMouseDown(event:MouseEvent):void {
doRotation = true;
lastMouseX = event.stageX;
lastMouseY = event.stageY;
}
// called when mouse up on stage
private function onMouseUp(event:MouseEvent):void {
doRotation = false;
}
// called when mouse down on a sphere
private function onMouseDownOnSphere(event:InteractiveScene3DEvent):void {
var object:DisplayObject3D = event.displayObject3D;
Tweener.addTween(object, {y:200, time:1, transition:"easeOutSine", onComplete:function():void {goBack(object);} });
}
// called when a tween created in onMouseDownOnSphere has terminated
private function goBack(object:DisplayObject3D):void {
Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"});
}
}
}
This produces the following Flash animation (click on image below) where we see the four spinning spheres rotating about the origin. You can rotate the scene by clicking and moving the mouse. You can interact with the spheres by clicking on them to make them jump.

Now lets look at the two different types of interaction that occur with this scene.
Stage interaction and modifying the camera position
The objective is to look at the scene from different angles by clicking the mouse button and moving the mouse. The interaction with the mouse is initialised by listening to the standard Flash event listeners. In the constructor we add the following lines :
// Listen to mouse up and down events on the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
When a mouse click occurs on the stage, calls are made to onMouseDown and onMouseUp. These methods modify a boolean value to indicate that the user wants to translate the camera and initialises the position of the mouse, necessary to determine the amount of movement that occurs during a frame.
// called when mouse down on stage
private function onMouseDown(event:MouseEvent):void {
doRotation = true;
lastMouseX = event.stageX;
lastMouseY = event.stageY;
}
// called when mouse up on stage
private function onMouseUp(event:MouseEvent):void {
doRotation = false;
}
The movement of the camera occurs by implementing code in the onRenderTick function which is called systematically at the beginning of every frame.
// If the mouse button has been clicked then update the camera position
if (doRotation) {
// convert the change in mouse position into a change in camera angle
var dPitch:Number = (mouseY - lastMouseY) / 2;
var dYaw:Number = (mouseX - lastMouseX) / 2;
// update the camera angles
cameraPitch -= dPitch;
cameraYaw -= dYaw;
// limit the pitch of the camera
if (cameraPitch <= 0) {
cameraPitch = 0.1;
} else if (cameraPitch >= 180) {
cameraPitch = 179.9;
}
// reset the last mouse position
lastMouseX = mouseX;
lastMouseY = mouseY;
// reposition the camera
camera.orbit(cameraPitch, cameraYaw);
}
The amount of horizontal and vertical movement of the mouse since the last frame (or the mouse down event was captured) are converted into angular displacements. These displacements are converted into a change in camera position. Rather than specifying and x, y and z values for the camera we are able to take advantage of its orbit function. The orbit takes two angles : yaw (or angle in the x-z plane) and pitch angle from the y axis which we limit to be between 0 and 180 degrees.
The orbit occurs around any given 3D object – in this case the origin as default – and has a particular distance from the object. This distance was initialised during the init3D method when we specified camera.z = -500. All subsequent orbital positions therefore have a distance of 500 from the origin. If you look into the code of the function orbit, you’ll see that these angles are subsequently converted into positional values for the camera.
After updating the camera position, the call to super.onRenderTick performs the necessary actions to render the scene at the modified viewing position.
Object interaction and tweening
The second type of interaction that we use takes advantage of Papervision3D’s power of detecting which 3D object is beneath the mouse. We need to explicitly indicate to Papervision3D that we want to have an interactive scene when we create the viewport and for each interactive material that we create, the interactions themselves are handled by adding listeners to the 3D objects.
First of all, to specify that we want an interactive scene we modify the call to the constructor of BasicView which is done by passing the value of true to the interactive scene argument (the second boolean shown below, the first being to indicate that we want the viewport to resize to the stage).
public function Example005() {
// specify that we want the scene to be interactive
super(0, 0, true, true);
Secondly, each object that we want to be interactive must have an interactive material. This is done by setting the variable interactive to true for the materials that we have created. For example, the flat shaded sphere is as such :
// Create a new material (flat shaded) and make it interactive var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x6654FF, 0x060433); flatShadedMaterial.interactive = true; sphere1 = new Sphere(flatShadedMaterial, 50, 10, 10);
Once the 3D objects have been created we add listeners to listen to Papervision3D interactive scene events, in this case when the user clicks on a sphere.
// Add a listener to each of the spheres to listen to InteractiveScene3DEvent events sphere1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere); sphere2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere); sphere3.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere); sphere4.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnSphere);
All of these objects use the same function to handle the event.
// called when mouse down on a sphere
private function onMouseDownOnSphere(event:InteractiveScene3DEvent):void {
var object:DisplayObject3D = event.displayObject3D;
Tweener.addTween(object, {y:200, time:1, transition:"easeOutSine", onComplete:function():void {goBack(object);} });
}
The object that has been clicked on is sent as part of the InteractiveScene3DEvent data. Here we then see the implementation of Tweener.
Tweener contains static methods that allow us to modify object properties. In this case we simply modify the y position of the object so that it goes to a value of 200 in 1 second using the easeOutSine transition function. I’d recommend looking at the Tweener documentation on the different transition functions. The onComplete argument allows us to put the sphere back to its original position when the tween has completed.
// called when a tween created in onMouseDownOnSphere has terminated
private function goBack(object:DisplayObject3D):void {
Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"});
}
}
This then uses another tween with an easeOutBounce transition to move the sphere back to the x-z plane.
As you can see, adding user interactions to the scene is very easy. Papervision3D provides a very simple interface to modify both camera position and produce interactions with individual objects in the scene. What I’ve shown here is just is a simple introduction to object interaction : there are different types of interactions for example when an object is added to a scene, the mouse moves over the object or the object is moved. Look inside the source of InteractiveScene3DEvent to get an idea of all the possibilities. Hopefully this at least has given a taster of what can be produced. As always comments and feedback are very welcome !
Next article:
- First steps in Papervision3D : Part 6 – Texture mapping
Create materials using bitmap data with the aim of having more detailed or realistic objects.
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 – animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame.
I’d like to illustrate in this post how we can add another essential ingredient to the scene : lighting. Lighting provides a much more realistic rendering of 3D objects and with Papervision3D is very simple to add. The example that I give here shows how lighting is added to a specific material. Papervision3D allows us to specify how the light interacts with the material and provides different qualities of rendering.
First its useful to have a brief summary of the commonly used terms used to describe lighting and shading in 3D graphics.
- Ambient
The ambient term in lighting describes non-directional light that is present everywhere in the scene. In the absence of direct light a material will take the colour given by the ambient term. - Diffuse
The diffuse term typically describes how lighting behaves on rough surfaces. When light from a directional light source hits a surface, the light is reflected in many random directions producing a diffuse pattern. For example paper can have a high diffuse term. - Specular
Specular lighting is used to describe shiny objects. Light is typically reflected off these surfaces in a particular direction (as opposed to the diffuse lighting). For example plastics have a high specular term.
Typically objects are made up of a combination of all these lighting characteristics. Now, as I’ve said since the beginning, I’m a beginner in Papervision3D with an OpenGL background and am merely illustrating the progress I’ve personally made over the last few weeks. But, as far as I can tell, the basic shading models available combine ambient lighting with either diffuse or specular and all three is not possible. I’d be very happy if someone could correct me on this… In any case the shading available, produces some beautiful results as we can see in these examples :
- A demo of different shader models
- Cell shading for a cartoon effect
- Shaded weird planet
- Phong shaded rhino
- Rotating Earth
- Phong shaded rhino
You can also find here a useful article on textures, shading and materials which illustrates many techniques possible.
In this post I’d like to show simply how we can add a light source to a scene and implement the four basic types of shading on different materials. These are :
- Flat shaded
The colour of a triangle is calculated simply between the face normal and the light source position. Includes diffuse and ambient terms. - Gouraud shaded
Shading is smoothed is smoothed over a surface and is not just dependednt on the normal of the triangle. Uses diffuse and ambient terms. - Phong shaded
Shading is smoothed as for the Goraud, but rather than a diffuse term uses specular and ambiant characteristics. - Cell shaded
Given two colours, the surface is smooth shaded by a specific number of steps.
The code to illustrate these shading models is as follows :
package {
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import org.papervision3d.core.proto.MaterialObject3D;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.materials.shadematerials.CellMaterial;
import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
import org.papervision3d.materials.shadematerials.GouraudMaterial;
import org.papervision3d.materials.shadematerials.PhongMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Sphere;
import org.papervision3d.view.BasicView;
public class Example004 extends BasicView {
private static const ORBITAL_RADIUS:Number = 200;
private var sphere1:Sphere;
private var sphere2:Sphere;
private var sphere3:Sphere;
private var sphere4:Sphere;
private var sphereGroup:DisplayObject3D;
public function Example004() {
super(0, 0, true, false);
// set up the stage
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
// Initialise Papervision3D
init3D();
// Create the 3D objects
createScene();
// Start rendering the scene
startRendering();
}
private function init3D():void {
// position the camera
camera.x = -200;
camera.y = 200;
camera.z = -500;
}
private function createScene():void {
// Specify a point light source and its location
var light:PointLight3D = new PointLight3D(true);
light.x = 400;
light.y = 1000;
light.z = -400;
// Create a new material (flat shaded) and apply it to a sphere
var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x6654FF, 0x060433);
sphere1 = new Sphere(flatShadedMaterial, 50, 10, 10);
sphere1.x = -ORBITAL_RADIUS;
// Create a new material (Gouraud shaded) and apply it to a sphere
var gouraudMaterial:MaterialObject3D = new GouraudMaterial(light, 0x6654FF, 0x060433);
sphere2 = new Sphere(gouraudMaterial, 50, 10, 10);
sphere2.x = ORBITAL_RADIUS;
// Create a new material (Phong shaded) and apply it to a sphere
var phongMaterial:MaterialObject3D = new PhongMaterial(light, 0x6654FF, 0x060433, 150);
sphere3 = new Sphere(phongMaterial, 50, 10, 10);
sphere3.z = -ORBITAL_RADIUS;
// Create a new material (cell shaded) and apply it to a sphere
var cellMaterial:MaterialObject3D = new CellMaterial(light, 0x6654FF, 0x060433, 5);
sphere4 = new Sphere(cellMaterial, 50, 10, 10);
sphere4.z = ORBITAL_RADIUS;
// Create a 3D object to group the spheres
sphereGroup = new DisplayObject3D();
sphereGroup.addChild(sphere1);
sphereGroup.addChild(sphere2);
sphereGroup.addChild(sphere3);
sphereGroup.addChild(sphere4);
// Add the light and spheres to the scene
scene.addChild(sphereGroup);
scene.addChild(light);
}
override protected function onRenderTick(event:Event=null):void {
// rotate the spheres
sphere1.yaw(-8);
sphere2.yaw(-8);
sphere3.yaw(-8);
sphere4.yaw(-8);
// rotate the group of spheres
sphereGroup.yaw(3);
// call the renderer
super.onRenderTick(event);
}
}
}
This results in four rotating spheres, each one shaded differently and all rotating about the center of the display (click on image to launch animation).

This example uses the same code as in the previous post as a starting block. The initialisation of the 3D is identical : the viewport and the camera position are the same as before. The scene creation is however different to create the four spheres and include a light source.
Adding a light source is very simple in Papervision3D : all we need to do is create a point light source and give it a position.
// Specify a point light source and its location var light:PointLight3D = new PointLight3D(true); light.x = 400; light.y = 1000; light.z = -400;
The value of true in the constructor simply indicates to Papervision3D that we want to artificially render an object representing the light source itself. In this example its not really necessary, but its useful as a debugging tool.
To use the light source we need to create materials that provide shading. The calculation of the colours that we see on the screen are made by these materials rather than the light itself. We can assume that the light source is white light : colours are specified with the materials.
// Create a new material (flat shaded) and apply it to a sphere var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x6654FF, 0x060433); sphere1 = new Sphere(flatShadedMaterial, 50, 10, 10); sphere1.x = -ORBITAL_RADIUS; // Create a new material (Gouraud shaded) and apply it to a sphere var gouraudMaterial:MaterialObject3D = new GouraudMaterial(light, 0x6654FF, 0x060433); sphere2 = new Sphere(gouraudMaterial, 50, 10, 10); sphere2.x = ORBITAL_RADIUS; // Create a new material (Phong shaded) and apply it to a sphere var phongMaterial:MaterialObject3D = new PhongMaterial(light, 0x6654FF, 0x060433, 150); sphere3 = new Sphere(phongMaterial, 50, 10, 10); sphere3.z = -ORBITAL_RADIUS; // Create a new material (cell shaded) and apply it to a sphere var cellMaterial:MaterialObject3D = new CellMaterial(light, 0x6654FF, 0x060433, 5); sphere4 = new Sphere(cellMaterial, 50, 10, 10); sphere4.z = ORBITAL_RADIUS;
This code creates the four identical spheres positioned on the x-z plane, each one with a different material (being one of the four main shaded material types mentioned above). As you can see, each material takes a light as the first parameter. The two colours are then diffuse and ambient for FlatShaded and Gouraud, specular and ambient for Phong respectively. For the Cell shaded material the two colours are the two extremes which are then split into the n different values. The Phong material takes an additional parameter specifying the level of specular reflection being between 0 and 255 – higher numbers result in more point-like reflections.
Finally I’d like to mention the use of a DisplayObjet3D to group the spheres together :
// Create a 3D object to group the spheres sphereGroup = new DisplayObject3D(); sphereGroup.addChild(sphere1); sphereGroup.addChild(sphere2); sphereGroup.addChild(sphere3); sphereGroup.addChild(sphere4); // Add the light and spheres to the scene scene.addChild(sphereGroup); scene.addChild(light);
This object acts as a layer that can be manipulated as with any other 3D object. However this manipulation performs an action on all the child objects together as we’ll see when we look at the code for the animation. This object, as well as the light, are then added to the main scene to be rendered.
To animate the scene I wanted all the spheres to rotate about the origin. Rather than calculating the coordinates of each sphere (as I did in my last post), the sphereGroup object allows me to rotate all the spheres together : Papervision3D does all the hard work in calculating their coordinates.
// rotate the spheres sphere1.yaw(-8); sphere2.yaw(-8); sphere3.yaw(-8); sphere4.yaw(-8); // rotate the group of spheres sphereGroup.yaw(3);
As with the previous example, each sphere is individually rotated on its axis. The rotation of all four about the origin now just takes a single line : sphereGroup.yaw(3) : much simpler to code than before !
So there you are ! This is really just a simple example of basic lighting and shading. You’ll find more complex examples on the web, for example including techniques such as bump mapping, to create more realistic scene lighting. Anyway, as always, I hope this has given a few insights into lighting with Papervision3D and please send any comments or suggestions as this article really shows only the tip of the iceberg !
Next article:
- First steps in Papervision3D : Part 5 – scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects.


