Pixel precision in Papervision3D
I used to work for a company that made of the concept of pixel perfection one of the pillars of their success. Today most of the top clients and agencies demand high quality, and pixel perfection became one of the important components of a great website. Obviously, it concerns 3D as well.
To achieve it, sooner or later in your 3D adventures you will come across one of these problems:
1. A material should be rendered as in 1:1 scale of the original texture
2. A precise, pixel level match between 2d and 3d content is necessary
In order to check how to solve these problems, I created a very simple demo. First, I had to make sure my textures are not scaled. I started to browse the Papervision3D forum and it wasn't long before I found the magic formula:
-
3dobj.z = ((camera.zoom - 1) * camera.focus) - Math.abs(camera.z)
Many thanks to unnormaJH and kjangsa3 for this tip. I would add that it works just as it is only with planes. In my example I worked with a cube, and a cubes Z position is defined by the point in the center of it - which means that the face facing the camera will be closer half the cubes width, so remember that this needs to be adjusted.
UPDATE. With the new Camera classes (GW rev. 639 an higher) the formula changes a bit. The "-1" in "zoom-1" is no longer needed. So it looks like this:
-
3dobj.z = (camera.zoom * camera.focus) - Math.abs(camera.z)
The second problem - a pixel perfect match between 2D and 3D - is mainly about being able to figure out the screen X and Y coordinates of the 3D object. They can be obtained that way:
-
screenPosX = 3dobj.screen.x + viewport.viewportWidth / 2;
-
screenPosY = 3dobj.screen.y + viewport.viewportHeight / 2
That, I found in an article on PV3D.org which shows how to do this. There is also an example of how to get the X and Y coordinates of a single vertex.
In the demo above, knowing the screen coordinates of the cube, I use the BitmapData.draw method to take a snapshot of the 2D background at this position for use as the texture. If you scale the browser window you can see how the texture on the cube is being updated to exactly match the background tile.
In the real world, what you'll need much more often is to do the opposite, that is drawing 2d content exactly on top of a 3d object. An example would be replacing a texture with a Sprite containing some interactive elements. By knowing the screen coordinates of your 3d object, and by making sure it is scaled 1:1 it is a quite easy task to make that look seamless & perfect!
Source code - get it here.
Credits The tile graphics come from Squidfingers.

July 7th, 2008 at 1:18 pm
[…] Everyday Flash / ยป Pixel precision in Papervision3D / by Bartek Drozdz […]
July 7th, 2008 at 3:28 pm
very helpful. Thanks!
July 8th, 2008 at 9:24 am
You should add onStageResize listener to change pixel-background position when changing window size :)
July 8th, 2008 at 9:51 am
Very cool!:)
July 8th, 2008 at 10:53 am
@garrymatteson Actually I did, but there was a bug. Now it’s fixed. Thanks for finding that one!
July 8th, 2008 at 6:19 pm
That’s a clever demo! :D
July 19th, 2008 at 7:05 pm
nice, I always wondered what the 2d pixel perfect equivalent was.
July 25th, 2008 at 3:53 am
hi bartek,
this is a great example and i can see the binary you supplied in the deloy folder working, but when i compile i get a slightly different result.
when compiling, the cube material bitmap seem larger then the background.
im using Papervision3D Public Alpha 2.0 - Great White (24.03.08) and i know theres work being done in the camera classes so just wondering if this could be causing the problem.
which means that the equation,
3dobj.z = ((camera.zoom - 1) * camera.focus) - Math.abs(camera.z);
no longer holds true, for some reason…
anyhoo, if you have any information on this i would be interested to hear it.
thx
Lukasz
July 25th, 2008 at 7:25 pm
how do you reverse the equation to solve for camera.z? i came up with
camera.z = ((camera.zoom - 1) * camera.focus) - 3dobj.z or
camera.z = ((camera.zoom - 1) * camera.focus) - Math.abs(3dobj.z) and best but still did not work right
camera.z = ((camera.zoom - 1) * camera.focus) + 3dobj.z;
but it did not look right. I’m guessing I’m doing something wrong with the Math.abs
algebraically challenged
-df
July 29th, 2008 at 11:15 pm
@lukasz you are right. the all camera classes have been changed after I did that demo. I was able to make it work again with the lates revision of GW. It seems like the ‘zoom - 1′ is not necesarry anymore… just use ‘zoom’ in the equation. I’ll post an updated ZIP with the sources later.
Anyway, since the camera classes have been reworked I will need to check the formula again. Hopefuly I will post something on that soon. I will also see the reversing of the equation that daddfristy is mentionning.
July 30th, 2008 at 5:12 pm
thanks bartek
thats do the trick
August 12th, 2008 at 5:20 pm
hello, i was able to get the results I wanted with:
distance between camera and object = camera.focus * camera.zoom
thanks for a real helpful tutorial
-df
August 19th, 2008 at 4:57 am
Wait, daddyfristy, how did you calculate the camera movement?
- TK
August 19th, 2008 at 5:03 am
Bartek,
I’m trying to solve something similar to this. I have a camera and a plane and I need to make sure that the top edge of the plane is always at the top edge of the viewport/camera’s viewing area. I need to do this with the camera, not the plane, for display purposes. How would I do the math to figure this out?
- TK
August 26th, 2008 at 12:01 am
[…] Pixel precision in Papervision3D […]
November 11th, 2008 at 11:49 pm
great! thanks.
November 21st, 2008 at 12:59 am
You forgot to mention on thing, if you are using a cube, that cube is centered. This equation will work fine with planes but for anything with depth you must use the following equation:
cube.z = (camera.zoom * camera.focus) - (Math.abs(camera.z) - cubeDepth/2)
December 10th, 2008 at 7:01 pm
Wonderful! Saved me a couple of hours :)
December 22nd, 2008 at 4:55 pm
I use this one in your demo to get it working right:
cube.z = -((camera.zoom * camera.focus) - (Math.abs(camera.z) - baseSize / 2));
Basicly the same as Max’s solution, but with the minus in front of the whole equation.