Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
November 1, 2014
arrowPress Releases
November 1, 2014
PR Newswire
View All
View All     Submit Event





If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 
How To Properly Render an X-Ray Silhouette Effect in Away3D
by Max Knoblich on 07/12/13 08:47:00 am   Expert Blogs   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

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

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.


Related Jobs

Twisted Pixel Games
Twisted Pixel Games — Austin, Texas, United States
[10.31.14]

Senior Graphics and Systems Engineer
Twisted Pixel Games
Twisted Pixel Games — Austin, Texas, United States
[10.31.14]

Mid-level Tools and Systems Engineer
Sega Networks Inc.
Sega Networks Inc. — Madison, Wisconsin, United States
[10.31.14]

Mobile Game Engineer
Forio
Forio — San Francisco, California, United States
[10.31.14]

Web Application Developer Team Lead






Comments



none
 
Comment: