Reflections in Papervision3D


reflect3.png

After posting my shadow experiment, Patrick Matte posed a question wondering if I would be able to do real-time reflections in a similar manner. The next day I had it done, along with some nice iterations along the way: orthographic and perspective projection (I can release those later if anyone really wants them). I've been sitting on it every since and finally decided I would take the time to write a little description into how its done and give the code to those who are interested (and I fixed up some code for backface culling in the reflection this morning).

So, the concept of shooting a ray onto a plane still holds true with the reflection stuff, but we handle the rays a little differently than we do shadows. With shadows we simply project the vertices directly onto the plane. This works because we don't care about the perspective distortion we will see on the plane face, since the shadow is black. A reflection is more than a projection. The reflected object is actually cast "inside" the material - so it maintains its own perspective despite the perspective of the plane you are projecting into. So, the problem was, how can i negate the perspective of the plane to make a real reflection?

The answer was pretty cool, and I am very happy with the result. Instead of projecting a vertex directly onto the plane, I multiply the vertex by a reflection matrix. The reflection matrix allows me to flip any object over any plane. Once i get the vertex in "reflected" space, I then cast a ray from my camera to the reflected vertex. Once I have this ray, I can find where it intersects the plane i want to show the reflection on - and then determine where on that material the ray hits. Since the material will be drawn from the camera's perspective, I can draw my reflected space from the camera's perspective as well - directly into the material. It's a little confusing, but it works awesome. Here is a little graphic I made to try and help explain:

reflectui.png

The red square is the object we want to reflect. The blue cube is the object in "reflected" space. The blue cube doesn't actually exist, mind you, all the vertices are flipped there via matrices. The thin red lines shooting from the camera are the rays, and the faint red box is the reflection drawn into the plane. Sorry its not a better graphic - I don't use Illustrator that much...

Anyways - thats about all there is to say about the theory behind it. Here is a brief rundown of the class:

Actionscript:
  1. //CREATE A NEW PlaneReflection
  2. // parameters:
  3. // - id: string
  4. // - blendMode : string
  5. // - alpha : Number (0-1)
  6. // - filters : array
  7. var reflector:PlaneReflection = new PlaneReflection("reflex", "multiply", 1, [new BlurFilter()]);
  8.  
  9. //ADD A MODEL TO THE "REFLECTION LIST"
  10. reflector.addModel(sphere);
  11.  
  12. //RENDER THE REFLECTION
  13. // paramaters:
  14. // - camera  : CameraObject3D - camera you are currently using
  15. // - plane : Plane - a plane to draw into - must have a MovieMaterial applied to it (animated = true);
  16. reflector.render(camera, plane);

That should be enough to get you started! Check out the demo, have fun with it. You will notice I used the planes own material as a displacement map for the reflection - that gives it the nice bumpmap feeling. Planes are still backwards (sorry) - so there is a special case to flip culling if you have a plane object.

View the demo (click and drag to orbit)

and

Get the source

[update]

Here is the updated source to work with the new core changes after Revision 754 (QuadTree support).  This also includes Refraction support - I didn't build it out terribly though since i'm too busy, but look at lines 94 and 95 to set what the refraction should look like.  Feel free to update the class to use member variables.

enjoy!

Get the Updated Source

[/update]



Dragging in 3D – The Right Way



world.png

Several months ago I did a blog post titled Dragging in 3D - The Easy Way. It was an no-math solution to being able to drag an object in 3D in multiple directions using the tools already built into Papervision. By creating an invisible Plane object, you could detect where it was hit by the mouse in 3D space - then move your object to that position.

While that was cool and all - it definitely was just a hack and heavy approach to dragging objects in 3D. This is much more elegant.

I introduced some new functions that make dragging in 3D possible. They have alot greater use than JUST for dragging, so make good use of them! The main function, which is essential to being able drag in 3D - is CameraObject3D.unproject. #unproject does just what the name says: it unprojects coordinates. It takes 2D screen coordinates, and gives you back a vector in world space. In lay terms - this basically shoots a ray from the camera, through where the mouse is, into 3D space. You can then do whatever you want knowing where that ray is. I use it in this demo to find where the ray hits a plane i want to be dragging on. When i find the intersection of the unprojected ray with the plane - i move my object there. Its really that easy. And all of it is built into Papervision3D for you.

So lets look at some code. First thing to note is the unproject function. It only takes 2 parameters which are screenX and screenY. Just pass in the mouse coordinates (relative to viewport.containerSprite) and you will get a Number3D that represents your ray back.

Actionscript:
  1. //get the ray shooting from the camera through the mouse position
  2. var ray:Number3D = v.camera.unproject(v.viewport.containerSprite.mouseX, v.viewport.containerSprite.mouseY);

Now that we know the direction the ray is shooting - we can check for an intersection with our plane. To do this, we have to do a few things. First, we must convert our ray into a point in 3D space. This is done by simply adding it to our camera. We then need a Plane3D object. Plane3D is a math utility class in Papervision that lets you perform various operations on "planes" in 3D space. One of the operations is finding where a line intersects the plane. Once we have that intersection, we can move our object to that position. Check it out:

Actionscript:
  1. //make our camera position accessible
  2. var cameraPosition:Number3D = new Number3D(v.camera.x, v.camera.y, v.camera.z);
  3.  
  4. //get the world position of our ray
  5. ray = Number3D.add(ray, cameraPosition);
  6.  
  7. //create a plane3D object
  8. var p:Plane3D = new Plane3D();
  9.  
  10. //define the plane by a normal and a point.  In this case, we want the plane to be going through (0, 0, 0) - with its normal going (0, 1, 0).  This will make the plane an XZ plane, facing upwards.
  11. p.setNormalAndPoint(new Number3D(0, 1, 0), new Number3D(0, 0, 0));
  12.  
  13. //find where the plane intersects.  this function takes 2 points to define a line - we will define it with our ray and camera.
  14. var intersect:Number3D = p.getIntersectionLineNumbers(cameraPosition, ray);
  15.  
  16. //set object position based on results
  17. object.x = intersect.x;
  18. object.y = intersect.y;
  19. object.z = intersect.z;

And thats it! All you need to do is project your ray - add it to your camera to get a real point and not just a direction, then find the intersection with your plane! If you don't understand Plane3D.setNormalAndPoint(), imagine it like this: a normal is basically the axis that is perpendicular to your object (plane). Imagine it as a spoke sticking out of the middle of it. If it is 0, 1, 0 - the spoke goes 0 in the x direction, 1 in the y direction, and 0 in the z direction. That means the spoke sticks straight up, so the plane must be horizontal. If the normal was 1, 0, 0 - the spoke would be going down the x-axis - and the plane would be vertical facing down the x-axis as well.

The demo you see here is a little more complex than the code you see above - instead of just setting the object to the intersection, it adds the difference between them to a velocity, which is then added to the object. This gives it that nice flow. Click and drag the sphere to roll it. Hold CONTROL/CMD to switch to a YZ plane (drag the sphere up). I might do a separate blog post on getting the sphere to roll properly - if anyone is interested in that...

Check out the demo here.

and

Get the source



3d Twist Modifier


twist.jpg

Bartok Drozdz posted a demo the other day of a Bend Modifier. The fact that no modifiers exist in pv3d is a crime - so I decided I would start off an official Modifier set. The first in the line of upcoming modifiers is the Twist modifier. This modifier works just like you would imagine it would - you simply tell it the amount you want to twist, and the axis you want to twist around.

Here's how to use it:

Actionscript:
  1. var mat:BitmapFileMaterial = new WireframeMaterial();
  2. cube = new Cube(new MaterialsList({all:mat}),500, 500, 500, 4, 4, 4);
  3. scene.addChild(cube);
  4.  
  5. var t:Twist = new Twist(cube);
  6.  
  7. var degrees:Number = 30;
  8. var axis:Number3D = new Number3D(0, 1, 0); //rotate around an axis sticking straight up the Y axis
  9. var center:Number3D = new Number3D(0, 0, 0); //the axis starts at 0, 0, 0
  10.  
  11. t.twist(degrees, axis, center);

That's it! Take note, that if you call Twist.twist again - it will twist the vertices from their ORIGINAL positions - not from their already twisted positions. However, you could add another twist after you do the first, and you could twist in multiple directions simultaneously.  This modifier will probably be updated - I'm not convinced my math was perfect - but for now you can do some fun stuff with it.  Take note - it requires you to use an object with geometry in it - it doesn't work with child objects.  So if you want to use it on a DAE for example, you would need to pass the child object that has the actual faces.

I'm planning on developing a more complete API to handle modifiers like you would see in 3D Studio Max.  If there are any good ones you would like me to work on, leave a comment or shoot me an email and let me know!

View the Demo

and

Get the source



Papervision LOD – SimpleLevelOfDetail


lodspheres2.png

There was some talk of being able to do to handle LOD (Level of Detail) in Papervision. While this is a far cry from a true LOD filter, I whipped it together hoping it might help someone out. The idea is this: YOU pass in the models you want to swap between. This allows you to control the geometry you want to keep, and more importantly, it is really easy to use. So implementing it is simple: pass an array of objects you want to use - from most complex to most simple. The SimpleLevelOfDetail object will add/remove them when it is the appropriate distance from the camera. And that is pretty much it. Specify the minDepth and maxDepth you want the swapping to occur between. Alternatively, you can specify an array of distances you want to use for selecting LoD. The index in the distance array that the objects screenZ is greater than will be the index of model that is used. All you need to do then is pass your SimpleLevelOfDetail object to your scene - and it will handle the rest!

The next step is the real LevelOfDetail object/filter. This will dynamically join faces for you - but I imagine it will have alot more problems. So, for simple LoD that you can have some good control of - check it out. It is now in the repository in GreatWhite under objects.special.SimpleLevelOfDetail.

I've whipped together a quick demo of the SLOD here. You can click and drag to rotate your view, and use the WSAD keys to move around. I've included the source below.

Here is a quick snippet showing how to use the class:

Actionscript:
  1. var objects:Array = new Array();
  2. var bMat:BitmapFileMaterial = new BitmapFileMaterial("texture.jpg");
  3. var distances:Array = null;
  4.  
  5. //UNCOMMENT TO USE A DISTANCE BASED ARRAY TO CONTROL RANGES
  6. //distances = [1000, 1500, 1600, 3000];
  7.  
  8. for(var i:Number=8;i<16;i++){
  9. objects.unshift(new Sphere(bMat, 100, i, i-1));
  10.  
  11. }
  12.  
  13. var simple:SimpleLevelOfDetail = new SimpleLevelOfDetail(objects, 100, 3000, distances);
  14.  
  15. scene.addChild(simple);

You can see we just push more and more complex spheres onto the front of the objects array - using the same material for each. Then, we create our SimpleLevelOfDetail object, passing in the objects, min, and max depths to use for selecting models. Finally we add our SimpleLevelOfDetail object to the scene - and we are done! If you choose to use the distances array, your results will be something like this:

When your model is less than 1500, it will use objects[0], if it is between 1500-1600, it will use objects[1], between 1600-3000 will use objects[2], over 3000 will use objects[3], etc. Pretty straightforward.

enjoy.

Get the source here



Great White, Meet Effects


demo1.jpg

So, after getting asked for the hundredth time the other day when I was going to integrate some of the features of the Effects Branch to Great White - I decided it was about time to do so. Things are settling in with my new job, and I've finished up my other obligations as well - so I put in the the time.

And now... They have been merged!!!

There are A LOT of changes though: creating layers, controlling layers, creating dynamic layers, sorting layers... its all different. Fortunately, I think for the better. I've comped up a set of VERY simple demos (all in one class) and will go over some of the new functionality here - just for you. Keep in mind this is still beta - so PLEASE report any bugs to me here or on the list.

--ViewportLayer has now replaced RenderLayer

Thats right - sorry. Forgot everything you ever knew about RenderLayers and RenderLayer syntax. Now - instead of layers being controlled by the objects - they are entirely controlled by the viewport. This means that you can create a layer for an object across multiple viewports.

Here's how you can create a new ViewportLayer:

Actionscript:
  1. var layer:ViewportLayer = viewport.getChildLayer(do3d, true);
  2. //now you can mess with your ViewportLayer all you want...
  3. layer.alpha = 0.5;

getChildLayer() is the function to receive or create a new ViewportLayer. The second parameter, createNew, specifies if a new ViewportLayer should be created if one is not found for the specified DisplayObject3D. If you set createNew to false- getChildLayer will simply return the ViewportLayer for the DO3D if it exists, otherwise it will return null.

Note that your DO3D is now going to be rendered to the layer created in getChildLayer(). If you want to add other DO3Ds to this layer, you can do so by using the following function of ViewportLayer:

Actionscript:
  1. //add a new DO3D to the layer created above.
  2. layer.addDisplayObject3D(new Sphere(new ColorMaterial()));

Likewise, you can remove DO3Ds from rendering to a specific layer by using removeDisplayObject3D.

--Nesting

Something else cool about ViewportLayers is they can be nested. Each ViewportLayer handles the sorting of its own children. So lets say you want to use INDEX sort for a COLLADA object with a fair number of z-fighting children - but you want that COLLADA to be z-sorted with the rest of your world. Now, you can.

Check out initNestingDemo() from the source. You will notice that we can create an empty ViewportLayer and add it to the viewport.containerSprite (also a ViewportLayer). Then we can create children inside that layer that will be INDEX sorted. Finally we create an external layer that will be Z sorted:

Actionscript:
  1. //s, s2, and s3 are three spheres added to the scene.
  2.  
  3. //create an empty viewport layer and add it to the container
  4. var parentLayer:ViewportLayer = new ViewportLayer(viewport, null);
  5. viewport.containerSprite.addLayer(parentLayer);
  6. parentLayer.sortMode = ViewportLayerSortMode.INDEX_SORT;
  7.  
  8. //create new layers inside the empty parentLayer and set some layer indexes for them
  9. parentLayer.getChildLayer(s, true).layerIndex = 1;
  10. parentLayer.getChildLayer(s2, true).layerIndex = 2;
  11.  
  12. //add s3 to a different layer off the main container - this will be z-sorted with parentLayer (with two index sorted children)
  13. viewport.getChildLayer(s3, true);

--useOwnContainer

This property gives you the ability to tell Papervision to dynamically create a layer for your DisplayObject3D when it is rendered in the viewport. By setting DisplayObject3D.useOwnContainer to true, your object will be rendered with its own ViewportLayer. Moreover, you can assign properties to your DisplayObject3D that will be passed along to your generated Layer. These properties are:

  • filters
  • blendMode
  • alpha

By setting these properties, you can add any filter to the layer, change the blendMode of your Layer, and control the alpha of the layer. Keep in mind that you can change these properties at runtime, as a new layer is generated every render, and it will inherit the updated settings. Here is a simple snippet of using these new properties:

Actionscript:
  1. var p:Plane = new Plane(new ColorMaterial());
  2. p.useOwnContainer = true;
  3. p.filters = [new BlurFilter(int(Math.random()*16)+2, int(Math.random()*16)+2)];
  4. p.blendMode= BlendMode.ADD;
  5. p.alpha = Math.random()+0.1;

You can see that we are creating a new plane, setting it to create a unique container, and then set an Additive blend mode, have a random alpha, and a random blur. In my code demo you will see that i create a large number of planes and change their filter's blur based on screenDepth (distance from the camera). Be sure to play with it.

--Effects

Effects have changed somewhat for GreatWhite too - but not too much. You still have BitmapEffectLayer - but it now extends ViewportLayer. You simply need to create a BitmapEffectLayer, at it to the containerSprite, and add your DisplayObject3Ds using addDisplayObject3D:

Actionscript:
  1. //create a new BitmapEffectLayer
  2. var bfx:BitmapEffectLayer = new BitmapEffectLayer(viewport, 500, 500);
  3. viewport.containerSprite.addLayer(bfx);
  4.  
  5. var s:Sphere = new Sphere(new WireframeMaterial());
  6. scene.addChild(s);
  7.  
  8. //add the sphere to the layer
  9. bfx.addDisplayObject3D(s);
  10.  
  11. //add a blur effect to the layer
  12. bfx.addEffect(new BitmapLayerEffect(new BlurFilter(2, 2, 8)));

You can add effects to other ViewportLayers by simply using the filters property inherent to all Sprites.

--Selective Layer Rendering

One other thing that has been added is the ability to render only selected ViewportLayers. I added this with the following scenario in mind: You have a large room, but the camera doesn't move much. You don't want to have to create a new Viewport for the floor, walls, etc - but also don't want them rendered every frame. Well, now you can tell your renderer which layers you want updated, and which to ignore. You can see this in the source under initLayerRenderDemo().

So how do we do it? All we need to do is create an Array with the ViewportLayers we want rendered - then call renderer.renderLayers(). Thats it! Here's a quick look:

Actionscript:
  1. layersToRender = new Array();
  2. layersToRender.push(viewport.getChildLayer(s, true));
  3. viewport.getChildLayer(s2, true);
  4. viewport.getChildLayer(s3, true);
  5. //pass the layer for s only to be rendered
  6. renderer.renderLayers(scene, camera, viewport, layersToRender);

Please note, that at this time, interaction will be lost for objects that aren't rendered again. I'll look into fixing this down the line.

--A Few Final Notes

Those sum up some of the biggest changes - there is some other stuff that might popup - if you have any questions, or need help figuring out whats going on - just let me know. Here are a few final tidbits:

DisplayObject3D.container - this references the last ViewportLayer your DO3D was rendered too. It is null before your object is rendered for the first time.

DisplayObject3D.createViewportLayer() - This function is another way to create a ViewportLayer. The first parameter is the viewport you want the layer created in. The second parameter specifies if you should set this to be the layer for all children of the current DO3D.

ViewportLayer.forceDepth - set this to true to set how far into the Z order your layer is rendered. Say, you know your floor should always be sorted 4500 away from the camera - but other things can sort around that. set forceDepth = true, and screenDepth = 4500 - and you've got it.

ViewportLayer.layerIndex - I mentioned it before - but you set this property when you have your layer.sortMode == RenderLayerSortMode.INDEX_SORT. Higher numbers will be higher on the DisplayList.

And again - this is beta - things will probably change. But let me or the team know if you find any bugs!!!

Get the Source

Enjoy!