Discover Nine Inch Nails!
blog.tartiflop
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.






