This tutorial details how to use Away3D 4.x to combine 3D (Away3D) and 2D (Starling) content, allowing them to share the same Stage3D instance.
Contents
Introduction
The Away3D 4.0 Gold release includes an API called Stage3DManager
for integrating the rendering process of Actionscript libraries that use Stage3D. In this tutorial, we see how this API can be used to combine Away3D and Starling frameworks into a single, high-performance rendering loop.
Since the introduction of Stage3D in Air and the Flash Player, numerous actionscript frameworks have been introduced and upgraded to take full advantage of the new found GPU acceleration. Away3D is one of those frameworks which now leverages the new GPU features allowing developers to create incredibly rich 3D interactive experiences. Starling, similarly, is another framework exploiting the power of Stage3D but is focused on providing GPU accelerated 2D graphics.
There has been several requests to combine both 2D and 3D frameworks together in a single application, however, to do this has previously required modification to the APIs themselves or having each framework render within it’s own separate Stage3D instance. Unfortunately, along with a greater performance overhead, there currently isn’t support for transparent Stage3D instances so overlaying them obscures the instance beneath.
With the ‘production ready’ release of Away3D 4.0, comes the ability to easily combine multiple frameworks into a single Stage3D instance. You can now layer any combination and any number of Away3D or Starling instances. In fact, there is no reason any Stage3D framework could be used - See the technical details at the bottom of this page.
This tutorial will take you through an example of having a background Starling layer, a middle Away3D layer and a foreground Starling layer.
Initializing the shared Stage3D instance
Under normal operation, when only Away3D is being used, management of the Stage3D is handled internally. It is this internal management that make it extremely easy to work with, but also causes difficulties in sharing the Stage3D instance. What is necessary is access to a common Stage3D instance where all the rendering will happen, for each framework. This is done through the Stage3DManager
and Stage3DProxy
classes.
// Stage manager and proxy instances private var stage3DManager : Stage3DManager; private var stage3DProxy : Stage3DProxy; /** * Global initialise function */ private function init():void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; initProxies(); } /** * Initialise the Stage3D proxies */ private function initProxies():void { // Define a new Stage3DManager for the Stage3D objects stage3DManager = Stage3DManager.getInstance(stage); // Create a new Stage3D proxy to contain the separate views stage3DProxy = stage3DManager.getFreeStage3DProxy(); stage3DProxy.addEventListener(Stage3DEvent.CONTEXT3D_CREATED, onContextCreated); stage3DProxy.antiAlias = 8; stage3DProxy.color = 0x0; }
The Stage3DManager
works as a singleton so to access it we have to explicitly request the singleton instance using the getInstance()
method. Using the Stage3DManager
instance, we can then make a request to allocate the next available Stage3D for our rendering. This isn’t an immediate process so we need to wait until one is allocated for our purposes. This is done by listening for the Stage3DEvent.CONTEXT3D_CREATED
event.
While we are working with the Stage3DProxy
, this is as good a time as any to set some of the properties such as the overral antialias level and background colour.
Constructing the render layers
When the Stage3DEvent.CONTEXT3D_CREATED
event is fired, we are ready to carry on and construct our layers for rendering.
As mentioned, in this example we’ll have one Away3D scene and two Starling scenes. Rather than focusing on the contents of the scenes, I’ll detail what’s required for setting each one up in preparation for layered rendering.
private function onContextCreated(event : Stage3DEvent) : void { initAway3D(); initStarling(); initMaterials(); initObjects(); initButton(); initListeners(); initListeners(); } /** * Initialise the Away3D views */ private function initAway3D() : void { // Create the first Away3D view which holds the cube objects. away3dView = new View3D(); away3dView.stage3DProxy = stage3DProxy; away3dView.shareContext = true; hoverController = new HoverController(away3dView.camera, null, 45, 30, 1200, 5, 89.999); addChild(away3dView); addChild(new AwayStats(away3dView)); } /** * Initialise the Starling sprites */ private function initStarling() : void { // Create the Starling scene to add the checkerboard-background starlingCheckerboard = new Starling(StarlingCheckerboardSprite, stage, stage3DProxy.viewPort, stage3DProxy.stage3D); // Create the Starling scene to add the particle effect starlingStars = new Starling(StarlingStarsSprite, stage, stage3DProxy.viewPort, stage3DProxy.stage3D); }
The onContextCreated
method goes through the typical setup to define the 3D scene, objects, materials, event listeners and also the Starling setup.
In the initAway3D
method, we construct the View3D object as usual for the Away3D scene, however, we set the .stage3DProxy
property to our shared Stage3DProxy instance and we also instruct the View3D to work with a shared Stage3D by setting .shareContext=true
. Similarly the initStarling
method creates the Starling scenes. The requirement of each scene is to pass a reference to the Starling sprite scene, the Stage and in the case of a shared Stage3D, we also pass a Rectangle defining the size of the view port and also a reference to our Stage3D instance. Fortunately, both of these are accessible directly from the Stage3DProxy instance as .viewPort
and .stage3D
properties.
NOTE: With Starling, if you wish to move or resize the viewport of a Sprite, it is necessary to manage the viewport Rectangle externally to Starling, as referencing the Starling.viewPort
property returns a clone rather than the original object reference. Setting it externally, passing it on the constructor and changing the properties of the external rectangle updates Starling rendering correctly.
Each Away3D scene and Starling sprite layer now has a reference to the shared Stage3D instance. The rest of the scene setup continues as normal. As you can see in the complete demo above, it allows you to cycle through, swapping the layers around. Initially, at the back (the first rendered layer) is a Starling layer that rotates a checkerboard pattern. On top of this is the Away3D scene that has 5 cubes arranged in a cross formation, on a wireframe grid. Finally, the top layer, which is rendered last, is the Starling particle effect.
Rendering the layers
Within Stage3D frameworks, there are certain functions in the rendering process which need disabling when the Stage3D is being shared. In an extremely simplified view, for the purposes of this tutorial, the rendering of a scene requires the scene to be cleared - clear()
, the elements rendered - drawTriangles()
and finally scene displayed - present()
. It is this ‘clearing’ and ‘displaying’ of each framework that stops Stage3Ds being shared, by default.
Now that Away3D and Starling have been instructed to use a shared Stage3D instance, they only need to consider the rendering part of the process, leaving the clearing and presenting responsibility of the Stage3DProxy
object.
There are two rendering options available through the Stage3DProxy
class which are very similar but provide different control of the clearing and presenting. The manual approach requires you to explicitly call the stage3DProxy’s .clear()
and .present()
methods around your layer rendering calls whereas the more automatic approach handles that for you. Both are described here.
Taking control - the manual approach
/** * Set up the rendering processing event listeners */ private function initListeners() : void { stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); } /** * The main rendering loop */ private function onEnterFrame(event : Event) : void { // Clear the Context3D object stage3DProxy.clear(); // Render the Starling animation layer starlingCheckerboard.nextFrame(); // Render the Away3D layer away3dView.render(); // Render the Starling stars layer starlingStars.nextFrame(); // Present the Context3D object to Stage3D stage3DProxy.present(); }
In this piece of code, we listen for an Event.ENTER_FRAME
event on the main stage instance, then within the listener method, we call the stage3DProxy.clear();
method then render each layer in turn, followed by the stage3DProxy.present();
. This gives you complete control of how the layers are rendered and when/if the clear()
and present()
calls need to be made. If nothing is changing then the display need not be re-rendered.
The easy life - the automatic approach
/** * Set up the rendering processing event listeners */ private function initListeners() : void { stage3DProxy.addEventListener(Event.ENTER_FRAME, onEnterFrame); } /** * The main rendering loop */ private function onEnterFrame(event : Event) : void { // Render the Starling animation layer starlingCheckerboard.nextFrame(); // Render the Away3D layer away3dView.render(); // Render the Starling stars layer starlingStars.nextFrame(); }
This approach is subtly different in that the Event.ENTER_FRAME
event listener is added to the Stage3DProxy
object, rather than the stage. As such, it internally manages when the clear()
and present()
calls are performed, leaving you to decide on how the rendering is done. In the above code, you can see that only the layer render calls are performed in the event listener method.
Conclusion
Through this tutorial, you have been introduced to the Stage3DManager
and more importantly the Stage3DProxy
and how they can be used in conjuntion with Away3D and Starling frameworks to benefit from GPU accerlerated 3D and 2D graphics together, which is increasingly becoming important to for rich interactive applications and games.
Greg Caldwell - The Away3D Team