-->

Previous articles summary :

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:

19 Responses to “First steps in Papervision3D : Part 7 - Texture mapping with lighting, bump mapping and environment mapping”

Thomas said...

Fantastic series. Most helpful I’ve found, for sure.

I’m curious about the visible wires on the model on this page at the poles of the sphere. Any idea why they’re there or how to fix them?

tartiflop said...

Hi Thomas,

Thanks for your comment - good to hear you’ve found this useful!

As for the visible wires near the poles: yes, it is indeed curious. I think it comes from a slight difference in the coordinates of the shader and bitmap triangles. If you try with a simple BitmapMaterial (not shaded) then the wires disappear. For a ShadedMaterial , the drawing process for a triangle occurs in two stages: first the bitmap then the shader (take a look at drawTriangle in the ShadedMaterial class). I guess at the pole the calculated coordinates are not perfectly matched so the two triangles do not overlap correctly. At least that’s the impression I get…

Thomas said...

Right. That’s what I thought.

Any ideas on how to fix it?

I notice it doesn’t happen when I import a collada object with a shaded material.

tartiflop said...

Sorry… the Papervision3D forum might be of more help.

Jonny said...

Hi, tartiflop.

I copy the code and run it. But there’s a problem is

org.papervision3d.cameras.FrustumCamera3D.orbit(target:DisplayObject3D,
pitch:Number, yaw:Number,
distance:Number=1000):void

the function orbit, the first parameter is different.
I try to make the target point to the sphere.
However, the result doesn’t like yours.
How can I current it?

tartiflop said...

Hi Jonny,

You’re not the first one to mention the FrustrumCamera3D… are you sure you are using the latest version of the Papervision3D source? I have the impression that this class has been removed from SVN. The function orbit that I’m using applies to the Camera3D object which has a different signature :

org.papervision3d.cameras.Camera3D.orbit(pitch:Number, yaw:Number, useDegrees:Boolean=true,
target:DisplayObject3D=null):void

which matches the code in the demo for this post.

Jonny said...

Yes, I download the source from

http://papervision3d.googlecode.com/files/Papervision3D_2.0_beta_1_src.zip

and I really don’t know which edition of the papervision is the newest one and where can download. As well as the papervision documentation. I usually check the documentation from

http://www.paultondeur.com/files/papervision3d/documentation/Public_Alpha_2.0/

Could you give me a URL which can download the source directly or by the SVN or send the source to my e-mail(net_zhb@hotmail.com).

I will appreciate very much.

tartiflop said...

The link for the source seems to be fine but I think its been updated since you downloaded it: I’ve just checked it and there is no FrustrumCamera3D.

The link for the documentation is out of date. Try http://www.papervision3d.org/docs/as3/

To download the source directly from SVN, try my previous post on Downloading and compiling Papervision3D v2.0 sources with SVN in Eclipse. Check out the comments at the bottom because the repository structure has changed since the post was published.

Hope this helps…

Jonny said...

Thank you tartiflop!
I’m a jackeroo in Papervision3D. So I really need you help. It’s my problem. Could you tell me what’s wrong? It confused me a couple of
days~~~

http://www.nabble.com/Add-Plane-to-a-Canvas-component-tt19668921.html

Doccie said...

Excellent series. I’d taken my first little steps in pv3d before, but this really helped a lot to better understand the library.

Great work!

[...] blog.tartiflop » Blog Archive » First steps in Papervision3D : Part 7 - Texture mapping with light… [...]

steven101 said...

wa!so good ,Thank you very much!

bioRex said...

thank you! this helped me a lot!

zhaiduo said...

Really Cool~great tutorial!

dave said...

Hey, thanks for the great series. It’s really helpful.

However, I’ve noticed this example “lags” every second or so as the sphere rotates, or as the camera rotates. After quite a while, the lagging seems to stop. Any ideas on why this might be happening?

卡卡西 said...

不错的教程,对的帮助太大了。谢谢你朋友。

Kevin said...

any ideas how to get this working with BitmapFileMaterial? I have made all my calls after the file has loaded to add the shaders to no avain. Any help appreciated..

paddy said...

very good info, thanks.

running the flash seems to crash firefox in xp after the phong_bump. operal and ie seem to be ok. haven’t looked to see why.

gino8080 said...

great example thank you!!

Is possible to load BUMP material directly from COLLADA FILES?

or we have to inject them only in Actionscript?

thank you very much

-->