Having obtained an interactive, texture mapped scene I’d now like to a bit more realism into it by adding a light source and shading the objects. The price of course for having a more realistic scene is at the cost of reduced performance as you’ll find many more calculations are necessary to render the scene. You can find the equivalent article for Papervision3D in my post on First steps in Papervision3D : Part 4 – lighting and shading, however I find that there is quite a difference for Away3D, at least compared to the other tutorials in this series.
Previous articles summary :
- First steps in Away3D : Part 1 – Getting started
Creation of a new Away3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Away3D classes. - First steps in Away3D : Part 2 – Animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Away3D : Part 3 – Texture mapping
Create materials using bitmap data with the aim of having more detailed or realistic objects. - First steps in Away3D : Part 4 – Scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects.
Lighting in Away3D can be split into three basic components:
- the light source(s) defining the properties of the light(s)
- shaders providing algorithms that determine how a light source interacts with a material
- a material type combining different shaders along with base colours or bitmaps
The first difference I notice compared to Papervision3D is that we can specify more than one light source. This of course increases the cpu load but in certain cases may produce a more realistic rendering of a scene.
There are three main types of light sources:
- AmbientLight3D which has no position and provides a uniform ambient light to all objects
- DirectionalLight3D which provides light from a specified position but with a strength that does not diminish with distance
- PointLight3D providing light from a specified position that weakens in intensity with distance
In terms of shaders we have the following posibilities:
- AmbientShader which provides ambient shading to a triangle face
- DiffusePhongShader which, depending on the angle between the light source and a triangle face, produces a diffuse shading pattern
- SpecularPhongShader which produces specular shading on triangles to imitate a surface that is reflecting light, depending on the observer’s position and the light position
- EnviroShader allowing for environment mapping, producing a reflection from a surrounding environment on an object, independent of a light source. Requires bitmap data for the environment
- DiffuseDot3Shader to increase scene reality by adding more small scale structure through the use of normal mapping to an object. Requires bitmap data for the normal map
The above shaders, with the exception of the EnviroShader assume that the light source is a DirectionalLight3D.
We then have a choice of CompositeMaterials that uses a combination of different shaders and base materials as shown in the following list:
- PhongBitmapMaterial used to create ambient, diffuse and specular lighting on a texture-mapped material
Base material : BitmapMaterial
Shaders : AmbientShader, DiffusePhongShader and SpecularPhongShader - PhongMovieMaterial used to create ambient, diffuse and specular lighting on a material based on another Flash movie
Base material : MovieMaterial
Shaders : AmbientShader, DiffusePhongShader and SpecularPhongShader - PhongColorMaterial used to create ambient, diffuse and specular lighting on a simple coloured material
Base material : ColorMaterial
Shaders : AmbientShader, DiffusePhongShader and SpecularPhongShader - EnviroBitmapMaterial used to create environmental lighting on a texture-mapped material
Base material : BitmapMaterial
Shaders : EnviroShader - Dot3BitmapMaterial used to create ambient and normal-mapped lighting on a texture-mapped material
Base material : BitmapMaterial
Shaders : AmbientShader and DiffuseDot3Shader, - Dot3MovieMaterial used to create ambient and normal-mapped lighting on a material based on another Flash movie
Base material : MovieMaterial
Shaders : AmbientShader and DiffuseDot3Shader
The above materials all produce smoothly shaded objects which naturally has a price in terms of performance. If we want to have objects that are more simply shaded then we can use a CenterLightingMaterial. This type of material produces the equivalent of flat-shaded materials in Papervision3D. We have in this category two choices:
- ShadingColorMaterial for simple flat shading of a ColorMaterial
- WhiteShadingBitmapMaterial for simple flat shading of a BitmapMaterial, but assuming that the light source is always white (independent of colour given to light source).
In the example code for this article I’m going to look at two of these materials: the PhongBitmapMaterial and the WhiteShadingBitmapMaterial. These two materials are very simple to use and provide a lot of control over the lighting effects on the material. One advantage here, personally speaking, over Papervision3D is the ability to mix ambient, diffuse and specular lighting all together which is necessary to produce real Phong reflection. Papervision3D provides materials for ambient plus diffuse and ambient plus specular but not all three together. This obviously is quicker to render but is less realistic when drawing shiny materials for example. Mixing all three is still possible but requires more effort as I showed in my post on phong reflection with Papervision3D.
For the light source I’m simply going to use a DirectionalLight3D which, as I mentioned before, correctly renders these materials. Another aspect that I like with Away3D is that the light source itself can be given a number of characteristics. Specifically we can give it a colour and define relative strengths of ambient, diffuse and specular shading, none of which are possible with Papervision3D. Being able to specify a light colour allows us to create new effects with materials of different colours. Specifying the ambient level directly with the light source, for example, also eliminates the need to specify the ambient light with every material created, as is the case with Papervision3D.
Away3D also allows us to create more than one light source and have a material rendered appropriately, which is not possible in Papervision3D. This, again, is obviously a drain on computational resources, but, in certain cases, this can open up new possibilities for rendering a more realistic scene.
I guess what I like most is that, conceptually, Away3D allows us to disassociate the light source from the material meaning that we can create a specific material without thinking about the environment and lighting in which it will be placed.
Anyway, enough of the theory and personal opinions, lets move onto the code. I’m using exactly the same scene as for the previous example only this time adding a DirectionalLight3D and replacing the BitmapMaterial with PhongBitmapMaterial and WhiteShadingBitmapMaterial.
package { import away3d.cameras.Camera3D; import away3d.containers.ObjectContainer3D; import away3d.containers.Scene3D; import away3d.containers.View3D; import away3d.core.base.Object3D; import away3d.core.math.Number3D; import away3d.core.render.Renderer; import away3d.core.utils.Cast; import away3d.events.MouseEvent3D; import away3d.lights.DirectionalLight3D; import away3d.materials.PhongBitmapMaterial; import away3d.materials.WhiteShadingBitmapMaterial; import away3d.primitives.Cube; import away3d.primitives.Cylinder; import away3d.primitives.Sphere; import away3d.primitives.Torus; import caurina.transitions.Tweener; import flash.display.Bitmap; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; [SWF(backgroundColor="#000000")] public class Example005 extends Sprite { [Embed(source="/../assets/earth.jpg")] private var EarthImage:Class; private var earthBitmap:Bitmap = new EarthImage(); [Embed(source="/../assets/away3D.png")] private var Away3DImage:Class; private var away3DBitmap:Bitmap = new Away3DImage(); [Embed(source="/../assets/checker.jpg")] private var CheckerImage:Class; private var checkerBitmap:Bitmap = new CheckerImage(); private static const ORBITAL_RADIUS:Number = 150; private static const CAMERA_ORBIT:Number = 600; private var scene:Scene3D; private var camera:Camera3D; private var view:View3D; private var group:ObjectContainer3D; private var sphere:Sphere; private var cube:Cube; private var centerCube:Cube; private var cylinder:Cylinder; private var torus:Torus; 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() { // set up the stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // Add resize event listener stage.addEventListener(Event.RESIZE, onResize); // Listen to mouse up and down events on the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); // Initialise Papervision3D init3D(); // Create the 3D objects createScene(); // Initialise frame-enter loop this.addEventListener(Event.ENTER_FRAME, loop); } private function init3D():void { // Create a new scene where all the 3D object will be rendered scene = new Scene3D(); // Create a new camera, passing some initialisation parameters camera = new Camera3D({zoom:25, focus:30}); setCameraPosition(); // Create a new view that encapsulates the scene and the camera view = new View3D({scene:scene, camera:camera, renderer:Renderer.CORRECT_Z_ORDER}); // center the viewport to the middle of the stage view.x = stage.stageWidth / 2; view.y = stage.stageHeight / 2; //view.renderer = Renderer.CORRECT_Z_ORDER; addChild(view); } private function createScene():void { // create a new directional white light source with specific ambient, diffuse and specular parameters var light:DirectionalLight3D = new DirectionalLight3D({color:0xFFFFFF, ambient:0.25, diffuse:0.75, specular:0.9}); light.x = 100; light.z = 500; light.y = 500; scene.addChild(light); // Create an object container to group the objects on the scene group = new ObjectContainer3D(); scene.addChild(group); // Create a new sphere object using a very shiny phong-shaded bitmap material representing the earth var earthMaterial:PhongBitmapMaterial = new PhongBitmapMaterial(Cast.bitmap(earthBitmap)); earthMaterial.shininess = 100; sphere = new Sphere({material:earthMaterial, radius:50, segmentsW:10, segmentsH:10}); sphere.x = ORBITAL_RADIUS; sphere.ownCanvas = true; group.addChild(sphere); // Create a new cube object using a tiled, phong-shaded bitmap material var tiledAway3DMaterial:PhongBitmapMaterial = new PhongBitmapMaterial(Cast.bitmap(away3DBitmap), {repeat:true, scaleX:.5, scaleY:.5}); cube = new Cube({material:tiledAway3DMaterial, width:75, height:75, depth:75}); cube.z = -ORBITAL_RADIUS; cube.ownCanvas = true; group.addChild(cube); // Create a cylinder mapping the earth data again cylinder = new Cylinder({material:earthMaterial, radius:25, height:100, segmentsW:16}); cylinder.x = -ORBITAL_RADIUS; cylinder.ownCanvas = true; group.addChild(cylinder); // Create a torus object and use a checkered, flat-shaded (from white light) bitmap material var checkerBitmapMaterial:WhiteShadingBitmapMaterial = new WhiteShadingBitmapMaterial(Cast.bitmap(checkerBitmap)); torus = new Torus({material:checkerBitmapMaterial, radius:40, tube:10, segmentsT:8, segmentsR:16}); torus.z = ORBITAL_RADIUS; torus.ownCanvas = true; group.addChild(torus); // Create a new cube object using a smoothed, precise, phong-shaded, mat (not shiny) bitmap material var away3DMaterial:PhongBitmapMaterial = new PhongBitmapMaterial(Cast.bitmap(away3DBitmap), {smooth:true, precision:2}); away3DMaterial.shininess = 0; centerCube = new Cube({material:away3DMaterial, width:75, height:75, depth:75}); centerCube.ownCanvas = true; group.addChild(centerCube); // add mouse listeners to all the 3D objects sphere.addOnMouseDown(onMouseDownOnObject); cube.addOnMouseDown(onMouseDownOnObject); cylinder.addOnMouseDown(onMouseDownOnObject); torus.addOnMouseDown(onMouseDownOnObject); centerCube.addOnMouseDown(onMouseDownOnObject); } private function loop(event:Event):void { // rotate the group of objects group.yaw(2); // rotate the objects sphere.yaw(-4); cube.yaw(-4); cylinder.yaw(-4); torus.yaw(-4); // update the camera position updateCamera(); // Render the 3D scene view.render(); } // updates the camera position private function updateCamera():void { // 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 setCameraPosition(); } } // sets the camera position given pitch and yaw angles private function setCameraPosition():void { camera.y = CAMERA_ORBIT * Math.cos(cameraPitch * Math.PI / 180); camera.x = CAMERA_ORBIT * Math.sin(cameraPitch * Math.PI / 180) * Math.cos(cameraYaw * Math.PI / 180); camera.z = CAMERA_ORBIT * Math.sin(cameraPitch * Math.PI / 180) * Math.sin(cameraYaw * Math.PI / 180); // keep the camera looking at the origin camera.lookAt(new Number3D(0, 0, 0)); } // 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 3D object private function onMouseDownOnObject(event:MouseEvent3D):void { var object:Object3D = event.object; 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:Object3D):void { Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"}); } // called when the window is resized private function onResize(event:Event):void { view.x = stage.stageWidth / 2; view.y = stage.stageHeight / 2; } } }
When compiled you should get the same result as below (click on the image). As before, the scene is interactive so you can click on the background and move the mouse to move the camera and click on an object to make it bounce.
Since the code is so similar to the previous example I’m going to concentrate solely on the function createScene – this is the only function that has been modified.
In this example we use a simple white light source and see four different types of materials rendered: one that is very shiny, another that has a mat texture, one that takes default lighting parameters (being quite shiny) and another that is flat shaded.
Starting with the light source, we use a DirectionLight3D for which we specify its position and lighting properties.
// create a new directional white light source with specific ambient, diffuse and specular parameters var light:DirectionalLight3D = new DirectionalLight3D({color:0xFFFFFF, ambient:0.25, diffuse:0.75, specular:0.9}); light.x = 100; light.z = 500; light.y = 500; scene.addChild(light);
As you can see the constructor takes an array of initialisation parameters in which we set the colour to white and give it ambient, diffuse and specular factors (being between 0 and 1).
The first object that we create is the rotating globe. Here I wanted the object to appear to be very shiny so modify its shininess.
// Create a new sphere object using a very shiny phong-shaded bitmap material representing the earth var earthMaterial:PhongBitmapMaterial = new PhongBitmapMaterial(Cast.bitmap(earthBitmap)); earthMaterial.shininess = 100; sphere = new Sphere({material:earthMaterial, radius:50, segmentsW:10, segmentsH:10}); sphere.x = ORBITAL_RADIUS; sphere.ownCanvas = true; group.addChild(sphere);
First of all, the PhongBitmapMaterial takes bitmap data for the texture mapping, as is the case for an ordinary, unshaded BitmapMaterial. Next we modify the shininess property (by default this is set at 20). This property, on the rendered scene, in effect modifies the extent of the specular lighting. The bright white dot that we see on the globe is reduced by increasing this property as if the light source is reflected off a more concentrated region of the material.
An important point to note is the line
sphere.ownCanvas = true;
Without this line the scene is not correctly rendered as objects remain partially visible when they pass behind others.
Moving on to the tiled material object, I wanted to show that the initialisation parameters that we send to a material are still taken into account for the base material. The PhongBitmapMaterial is composed of a TransformBitmapMaterial (as well as the shaders) as we discussed in the previous example. The initialisation parameters {repeat:true, scaleX:.5, scaleY:.5} are passed directly to this element when it is constructed and therefore the texture map is still tiled. This object uses the default shading parameters of the PhongBitmapMaterial and is therefore appears less shiny than the globe.
The cylinder uses the same material as the globe just to illustrate that we can use the same material more than once.
Next, for the torus, we implement flat shading by using a WhiteShadingBitmapMaterial. As its name indicates this assumes a white light source. You can test this by experimenting with the light source colour. You’ll notice that all the other objects correctly reflect the change in light colour but this object remains white and red. This material is obviously less complex in terms of necessary calculations and therefore provides better rendering performance. However, we see that smoothly shaded materials, as with the other objects, allow us to reduce the number of rendered triangles to produce more rounded objects.
Finally, the cube in the center is rendered with shininess set to zero. Only diffuse and ambient lighting remain in this case. This is useful for rendering mat materials but unfortunately has no performance benefits. We can see however that there is a difference in the visible result for the two cubes.
So thats it for this tutorial! I hope this has given a useful overview of lighting and shading in Away3D. Of course I’ve not gone into detail for all the different types of lights and shaders – you’ll discover a lot by experimenting with this example, for example by changing the materials, the type of lighting, its properties and its position. I’ve purposely ignored the Dot3BitmapMaterial and EnviroBitmapMaterial for this post because I felt that it was important to look at simple lighting and shading first – they should appear in the next article. But for now, at least, I hope this provides a useful starting point – comments and suggestions, as always, are very welcome!
Next article:
- First steps in Away3D : Part 6 – Normal mapping and environment mapping
Additional realism is added to the scene by applying normal maps (to give a higher level of shading characteristics) and environment maps (as an alternative to simple lighting models).

can that kind of shading and lighting be applied to a 3ds model with textures automatically loaded?
hi,
how can i create a light source that act as a lamp?
thanks.