Matte / shadow material behavior, a 3D mask

Software: Away3D 4.x

Simon BRICHE, Newbie
Posted: 29 March 2012 11:45 AM   Total Posts: 2

Hi all,

I try to have a behavior like the Matte / Shadow material in 3DSMax with Away3d 4.x, i.e. a 3d mask, in order to incrust 3D models in a 2D background.
And I really start to bang my head against the walls ‘^^

The way I try to implement it is to use the stencil actions.
Basically, I want the mask objects be invisible. If a masked object is in front of a mask object, it is visible.
If the masked object is behind a mask object, the parts of the masked object that overlay the parts of a mask object are invisible.
This example illustrate the concept.
Note that the robot (3D) is masked when it’s behind by the floor lamp (2D), but isn’t masked when it’s in front of the floor lamp.

So far, I managed to mask objects with the Context3D.setStencilActions() method, but the objects are masked even if they are in front of a mask object.

In pseudo-code, I would have :

if(pixel depth masked > pixel depth mask) {
return pixel
}
else{
do stencilAction;
}

For testing purpose, I’ve created 2 objects (a sphere and a cube) and added a custom property in their material (material.extra = {mask:masked or mask}).
Here is the code :

public function SimpleTest()
 
{
  
var bgImage:Sprite = new McBackgroundImage();
  var 
bdBackgroundImage:BitmapData = new BitmapData(512512);
  
bdBackgroundImage.draw(bgImage);
  
//create a view with MaskRenderer
  
_view = new View3D(null,null,new MaskRenderer());
  
//add a background
  
_view.background = new BitmapTexture(bdBackgroundImage);
  
_view.antiAlias 2;
  
addChild(_view);
  
  var 
maskMaterial:ColorMaterial = new ColorMaterial(0xff0000);
  var 
maskedMaterial:ColorMaterial = new ColorMaterial(0x00ff00);
  
  var 
cube:CubeGeometry = new CubeGeometry();
  var 
sphere:SphereGeometry = new SphereGeometry();
  
  
_cubeMesh = new Mesh(cubemaskMaterial);
  
_sphereMesh = new Mesh(spheremaskedMaterial);
  
  
_cubeMesh.material.extra {mask:"mask"};
  
_sphereMesh.material.extra { mask:"masked" };
  
  
_view.scene.addChild(_cubeMesh);
  
_view.scene.addChild(_sphereMesh);
  
  
addEventListener(Event.ENTER_FRAMEhandleEnterFrame);
  
 
}


 
private function handleEnterFrame(Event) : void
 {
  
//rotate the cube
  
_cubeMesh.rotationY += .5;
  
_cubeMesh.rotationX += .5;
  
_cubeMesh.rotationZ += .5;
  
  
//rotate the sphere
  
_sphereMesh.Math.cosgetTimer() / 700 ) * 100;
  
_sphereMesh.Math.singetTimer() / 700 ) * 100;
  
  
  
_view.render();
 
}

Then, I’ve created a MaskRenderer that extends DefaultRenderer and override the draw() function. I created one EntityCollector for the mask objects and one for the masked objects.
Before drawing the mask objects, I set the StencilReferenceValue to 1 and set the stencilActions so that the objects aren’t drawn.
Then I set the stencilActions again to draw the parts of the masked objects that are outside the parts of the mask objects.
Here is the code of the draw() method (the other methods of DefaultRenderer are the same):

override protected function draw(entityCollector EntityCollectortarget TextureBase) : void
{
 
// TODO: not used
 
target target;
 
 
_context.setDepthTest(trueContext3DCompareMode.LESS);

 
_context.setBlendFactors(Context3DBlendFactor.ONEContext3DBlendFactor.ZERO);
 
 var 
entityCollectorMask:EntityCollector = new EntityCollector();
 var 
entityCollectorMasked:EntityCollector = new EntityCollector();
 
 
//not optimized, just for testing purpose
 
if (entityCollector.opaqueRenderableHead.renderable.material.extra.mask == "mask"{
  entityCollectorMask
.applyRenderableentityCollector.opaqueRenderableHead.renderable);
  
entityCollectorMasked.applyRenderable(entityCollector.opaqueRenderableHead.next.renderable);
 
}
 
else {
  entityCollectorMasked
.applyRenderableentityCollector.opaqueRenderableHead.renderable);
  
entityCollectorMask.applyRenderable(entityCollector.opaqueRenderableHead.next.renderable);
 
}
 
 
 
// set a value to draw into the stencil mask buffer for each pixel.
 
_context.setStencilReferenceValue(1);
 
_context.setStencilActions"frontAndBack""never""set""set","set");
 
 
// draw the cube, and for each pixel of the cube, will be set to a value of 1 on the mask buffer.
 
drawRenderables(entityCollectorMask.opaqueRenderableHeadentityCollectorMask);
 
 
// change the stencil action to only draw the sphere if the pixel hasn't a value of 1.
 
_context.setStencilActions"frontAndBack""notEqual""set""set","set");
 
 
drawRenderables(entityCollectorMasked.opaqueRenderableHeadentityCollectorMasked);
 
 
// reset the stencil action to the default values to keep drawing the sceen normally.
 
_context.setStencilActions();
 
 
 
_context.setDepthTest(falseContext3DCompareMode.LESS);

 if (
entityCollector.skyBox{
  
if (_activeMaterial_activeMaterial.deactivate(_stage3DProxy);
  
_activeMaterial null;
  
drawSkyBox(entityCollector);
 
}

 drawRenderables
(entityCollector.blendedRenderableHeadentityCollector);

 if (
_activeMaterial_activeMaterial.deactivate(_stage3DProxy);

 
_activeMaterial null;


It’s (strongly) inspired by that entry in the flare3d wiki.

Here is the result.

Is it possible to test the depth of a pixel before the stencil tests ?
Or is it just a matter of parameters in the Context3D.setStencilActions() method ?
Has someone already implement this behavior ?
Is this the right way to do it ?

Of course, it would be great if the mask objects can receive and cast shadows, but that’s another challenge ‘^^

Any help or hint will be much appreciated !

Thanks a lot !

   

John Brookes, Moderator
Posted: 29 March 2012 02:33 PM   Total Posts: 732   [ # 1 ]

I have no idea what anything does but commenting out

else {
  entityCollectorMasked
.applyRenderableentityCollector.opaqueRenderableHead.renderable);
  
//entityCollectorMask.applyRenderable(entityCollector.opaqueRenderableHead.next.renderable);
 

makes the sphere show when in front of the mask and hidden when behind.

   

Simon BRICHE, Newbie
Posted: 29 March 2012 06:14 PM   Total Posts: 2   [ # 2 ]

Thanks for your reply !

The line that you commented actually add the cube for rendering.

I store the mask and masked objects in one EntityCollector each.
To do it, I test the material.extra.mask property to know if the first renderable object to render is a mask or a masked object. Since there is only 2 objects on scene, if this is not a mask object, it’s a masked object (I plan to optimize this code by customizing the EntityCollector object to be able to add a maskRenderableHead property). Finally, I draw the 2 EntiyCollector objects.

So, if the cube is not rendered, the stencil buffer is empty and there is no masking. I think this is why the sphere is visible even if it’s in front of the cube (which is not on scene). But I may be wrong ‘^^

The thing is that I’m not an expert in stencil and depth buffers, even with the documentation. So I don’t know if this is possible this way. And if there is a way.

   

gNikro, Newbie
Posted: 23 January 2013 09:59 AM   Total Posts: 2   [ # 3 ]

I think i know how u can solve your problem. e.g u can create box on position where house should be, then create rendering texture and render house to texture and apply that texture to box. Then shadow will be drop to box but box will be like transparent object.

Example of that mechanism

get Intermediate_RealTimeEnvMap example from away3d git.

change reflectiveMaterial to SkyBoxMaterial, set reflectionTexture to that material.

Or another exmaple.

Change FresnelEnvMapMethod to RefractionEnvMapMethod and set refractionIndex to 1

   
   

X

Away3D Forum

Member Login

Username

Password

Remember_me



X