Sponsored By

Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs.

How To Properly Render an X-Ray Silhouette Effect in Away3D

How to render an X-ray effect for occluded objects in Flash graphics engine Away3D. I show an elegant way of implementing this as an effect method for multi pass materials.

Max Knoblich, Blogger

July 12, 2013

17 Min Read

Back in February, I published an article which described how the silhouette/X-ray effect works that is used in some games for concealed objects. In the first half of the article, I described the principle behind the effect, in the second part I described how to implement the effect in Away3D.

This is a follow-up to that article, introducing a more elegant, improved way to implement the effect in Away3D.

Rendered Silhouette (XRay) Effect in Away3D

Rendered Silhouette (XRay) Effect in Away3D

The method described in the original article involved complicated geometric transformations and sharing the 3D context between several separate View3D objects.

The version shown in this article only requires a single class containing a few methods and a single View3D.

What Went Wrong In The Original Article

Why did I choose such a unwieldy solution in the original article anyways?

I was assuming two things which touch on central principles necessary for implementing the effect in Away3D. Turns out, that both assumptions were wrong.

  1. It’s necessary to keep the depth buffer from being written to when rendering the X-ray silhouette. From all I knew back then, it seemed like this was not possible with Flash and Stage3D.

    Google searches turned up nothing, my question on the official Adobe forum was never answered and the answer on stackoverflow suggested resetting the backbuffer which wouldn’t have worked.

    Because of this, I devised the complicated workaround which involved flattening the geometry of the object and putting it in front of the original.

    Turns out, all I needed to know about was the setDepthTest method which can be used to set the appropriate flag.

  2. It’s important to render the objects for which the silhouette should be displayed after the potentially occluding objects. This means you probably want to explicitly set the render order of objects.

    The problem is that Away3D gives you basically no explicit way of doing that. The engine uses a lot of internal mechanisms to determine the order in which to draw objects.

    Since you barely have access to these mechanisms by default, I split the scene into several separated views which share a Stage3DProxy. This way, I was able to render the contents of the scenes explicitly.

    In this implementation, we still don’t set the render order explicitly, but rely on the implicit mechanisms implemented in Away3D.

Setup

This article is based on the Away3D 4.1.1 Beta release.

The most work for this example actually is setting up the simple scene we have seen above.

This is the code I used to create that scene in Away3D in the project’s constructor.

// stage properties 
this.stage.scaleMode = StageScaleMode.NO_SCALE; 
this.stage.align = StageAlign.TOP_LEFT; 
this.stage.frameRate = 60; 
 
// init stage3d 
view3D = new View3D();
view3D.antiAlias = 8;
this.addChild(view3D);
 
// init light
var light:DirectionalLight = new DirectionalLight();
light.diffuse = 1.4;
light.ambient = 0.1;
light.direction = new Vector3D(8, -19, -6);
light.castsShadows = true;
view3D.scene.addChild(light);
 
var lightPicker:StaticLightPicker = new StaticLightPicker([light]);
 
// init camera
view3D.camera.position = new Vector3D(700, 600, 800);
view3D.camera.lookAt(new Vector3D(), Vector3D.Y_AXIS);
view3D.camera.lens.near = 550;
view3D.camera.lens.far = 2500;
 
// init materials			
var whiteMaterial:ColorMultiPassMaterial
	= new ColorMultiPassMaterial(0xe1e0e2);
whiteMaterial.shadowMethod = new HardShadowMapMethod(light);
whiteMaterial.lightPicker = lightPicker;
 
var redMaterial:ColorMultiPassMaterial
	= new ColorMultiPassMaterial(0xd16643);
redMaterial.shadowMethod = new HardShadowMapMethod(light);
redMaterial.lightPicker = lightPicker;
 
var yellowMaterial:ColorMultiPassMaterial
	= new ColorMultiPassMaterial(0xe1e022);
yellowMaterial.shadowMethod = new HardShadowMapMethod(light);
yellowMaterial.lightPicker = lightPicker;
 
// init 3D objects
var plane:Mesh
	= new Mesh(new PlaneGeometry(2000, 2000), whiteMaterial);
plane.castsShadows = true;
view3D.scene.addChild(plane);
 
var xrayCube:Mesh
	= new Mesh(new CubeGeometry(200, 200, 200), yellowMaterial);
xrayCube.y = 100;
xrayCube.castsShadows = true;
xrayCube.rotationY = 112;
view3D.scene.addChild(xrayCube);
 
var cubeGeometry:CubeGeometry = new CubeGeometry(300, 300, 300);
 
var cube:Mesh = new Mesh(cubeGeometry, redMaterial);
cube.y = 150;
cube.x = -10;
cube.z = 350;
cube.castsShadows = true;
cube.rotationY = -12;
view3D.scene.addChild(cube);
 
cube = new Mesh(cubeGeometry, redMaterial);
cube.y = 150;
cube.x = 50;
cube.z = -350;
cube.castsShadows = true;
cube.rotationY = 37;
view3D.scene.addChild(cube);
 
cube = new Mesh(cubeGeometry, redMaterial);
cube.y = 150;
cube.x = 450;
cube.z = 120;
cube.castsShadows = true;
cube.rotationY = -37;
view3D.scene.addChild(cube);

Implementing the Silhouette Effect

I implemented the X-ray effect as a shader method. Shader methods store the code for the vertex and/or fragment shader programs and can be used to extend the functionality of materials in Away3D.

In Away3D, you can add method objects extending EffectMethodBase to multi pass materials like ColorMultiPassMaterial. They will be added to that materials effect pass, which is ideal for our purposes.

This is my code for the XRayMethod.

use namespace arcane;
 
public class XRayMethod extends EffectMethodBase
{
	// Member Fields
 
	private var _xrayColor:uint;
 
	private var _xrayR:Number = 0;
	private var _xrayG:Number = 0;
	private var _xrayB:Number = 0;
	private var _xrayA:Number = 1;
 
	// Member Property
 
	public function get xrayColor():uint
	{
		return _xrayColor;
	}
 
	public function set xrayColor(value:uint):void
	{
		_xrayColor = value;
		updateXray();
	}
 
	public function get xrayAlpha():Number
	{
		return _xrayA;
	}
 
	public function set xrayAlpha(value:Number):void
	{
		_xrayA = value;
	}
 
	// Member Functions
 
	arcane override function activate(vo:MethodVO,
				stage3DProxy:Stage3DProxy):void
	{
		super.activate(vo, stage3DProxy);
 
		var index:int = vo.fragmentConstantsIndex;
		var data:Vector.<Number> = vo.fragmentData;
		data[index] = _xrayR;
		data[index + 1] = _xrayG;
		data[index + 2] = _xrayB;
		data[index + 3] = _xrayA;
 
		stage3DProxy._context3D.setDepthTest(false,
				Context3DCompareMode.GREATER);
	}
 
	arcane override function getFragmentCode(vo:MethodVO,
				regCache:ShaderRegisterCache,
				targetReg:ShaderRegisterElement):String
	{
		var code:String = "";
		var output:ShaderRegisterElement = targetReg;
 
		var xrayInputRegister:ShaderRegisterElement
				= regCache.getFreeFragmentConstant();
		vo.fragmentConstantsIndex
				= xrayInputRegister.index * 4;
		code += "mov " + output + ", "
			+ xrayInputRegister + "\n";
 
		return code;
	}
 
	private function updateXray():void
	{
		_xrayR = ((_xrayColor >> 16) & 0xff) / 0xff;
		_xrayG = ((_xrayColor >> 8) & 0xff) / 0xff;
		_xrayB = (_xrayColor & 0xff) / 0xff;
	}
 
}

The central piece of the implementation is the activate function containing the call to setDepthTest(false, Context3DCompareMode.GREATER) setting the proper comparision method and preventing any values from being written into the depth buffer.

The other important method is the getFragmentCode function which assembles the fragment shader code. The code in the example above simply outputs the color stored in xrayColor and xrayAlpha. More elaborate effects could be implemented here.

The rest of the class exposes properties that get passed to the shader program.

Applying the Effect

All you have to do now is to instantiate the XRayMethod and set the xrayColor and xrayAlpha properties.

var xrayMethod:XRayMethod = new XRayMethod();
xrayMethod.xrayColor = 0x3399FF;
xrayMethod.xrayAlpha = 0.5;

Then, you add the method to a multi pass material.

var yellowMaterial:ColorMultiPassMaterial
		= new ColorMultiPassMaterial(0xe1e022);
yellowMaterial.addMethod(xrayMethod);

And that’s it! You should now see the X-ray effect on the objects using the the material.

About the Render Order in Away3D

As described in the original article, it’s important for the effect that any occluding geometry is rendered before the object using the silhouette is drawn.

As of Away3D 4.1.1, the render order of the objects in a scene is not determined by the objects themselves but by the materials they use. To minimize shader program and constant uploads, the objects in the scene are ordered by material.

As far as I can tell after extensively looking at the Away3D source code, the factors for the sort are:

  1. The number of passes on the material. The objects using materials with fewer passes get rendered first.

  2. The point at which the material was instantiated. If two materials have the same number of passes, the objects using the material which was instantiated first get rendered first.

Most standard materials have only one pass. When we add the XRayMethod object to the material, this activates the effect pass, which bumps the number of passes of that material to two. Which, in most situations, is already enough to make sure it’s rendered last, which we need for the effect.

If you have several materials with two passes each, make sure that the ones using the XRayMethod are instantiated last.

If you have materials that should be rendered before the X-ray one which have more than two passes, then… you’re out of luck, at least for now. The function calculating the renderOrderID in Away3D is onPassChange in the MaterialBase class. However, it’s a private event handler, so you can’t override it. You might get somewhere by overriding MaterialBase's addPass function and add another event handler to the added pass that sets the material’s render order ID, but I haven’t tested that yet.

Read more about:

Featured Blogs

About the Author(s)

Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like