HIDIHO!

giving something back to the Flash community

Ave AGAL! morituri te salutant

Tags: , , ,

3D with AGAL, model by LONEWOLF
time to ride the tiger.

after the workshop I gave in january -*-, I was extremely glad ( and proud somehow ) when one of the attendees told me he would start to blog and share his findings in AGAL. I wanted to tackle AGAL for a while now and this was a good starting point. So before reading further – and if you’re not famailiar with the concepts behind AGAL – I warmly encourage you to dive into his series of tutorials, well written, well illustrated and very informative.

A second thing is that for my last job, we had to use Stage3D and the guy I worked with (Pierre Lepers, freaking skilled) showed me that coding AGAL was not THAT horrible. it’s abstract, compact and difficult to debug yet far from impossible as long as you know what the operations do in your shaders.

A third thing that motivates me is the fact that the corpus of articles about AGAL is still pretty low. Sharing about what I learnt might help demystify AGAL and get some people started.

on my way, I found some helpful resources:

  • the guy at saltgames has a long in-depth article to explain the basics and another on how the registers work. I highly recommend them.
  • while searching for info, I came multiple times across Pierre Chamberlain‘s posts. he’s also done a great job at making things accessible & somewhat user friendly. he’s also tested some APIS( easyAGal, asgl, etc. )
  • obviously the most famous 2 links ( they were the only 2 links for a while ^^ ) part 1 & part 2 by Marco Scabia
  • one bothering thing is to manipulate the Matrix3D, for this I followed this explanation
  • Ben Whiting, despite the lack of snippets, his explanations are pretty informative and he did some interesting helpers.
  • pixelante offers a couple of tutorials too, same reproach as above – if any – the code is partial which means one has to know his way before trying to understand the article.

a couple of months ago, Alejandro Santander released a tool to edit AGAL on the fly. it’s available here : http://www.lidev.com.ar/category/agal/. a good idea to put what you’ve learnt into practice ^^

by the way there is also this wonderfl snippet: http://wonderfl.net/c/tAJf, a bit… dry… but it does the thing.

another endless source of amazement is Lars Gerckens’ dwell ; even if it’s more about 2D, he has skillfully implemented some tricky techniques and done beautiful things with it.

last but not least:

LEARNING CURVE MY ASS !

there is no learning curve ; AGAL has a learning cliff.
and the only way to deal with AGAL is… to deal with AGAL.

DO IT YOUR WAY

it is so dry and so compact that any wrapper or 3D engine will mislead you about what AGAL actually is: an assembly language. therefore if you intend to use Stage3D at some point, you should definitely spend some time studying AGAL. the concepts you’ll cover can be reused in other (serious) 3D languages ; for instance it is rather close to OpenGL ES and I guess DirectX is not too far either. while studying Android, I struggled against OpenGL, the things I learnt were of a great help when addressing AGAL. I guess it’s the same the other way around.

after a full week of consecutive failures, headaches and axe murdering dreams, I piled up a small API (the “core” has 5 classes), just enough to render a couple of meshes and start tweeaking around. it looks like this:

click to download
download
this should give you this awesome animation, click to rotate.

note that this is an extremely basic wrapper, not an engine. I did it only for learning purpose. I hope it will help some people to get started too but if you’ve read the previous chapter, you shouldn’t even use it.

LEAVE MY RESOURCES ALONE!

you can’t set variables directly to the GPU the way you would create a variable object in actionscript. instead you ask the Context3D to create the objects on the GPU and you keep a reference to them as a variable on the Flash side.
there are very few methods, with very few params and quite explicit params. this comes from the doc:

//Creates a CubeTexture object.
createCubeTexture(size:int, format:String, 
optimizeForRenderToTexture:Boolean):CubeTexture

//Creates an IndexBuffer3D object.	
createIndexBuffer(numIndices:int):IndexBuffer3D

//Creates a Program3D object. 	
createProgram():Program3D

//Creates a Texture object.	 	
createTexture(width:int, height:int, format:String, 
optimizeForRenderToTexture:Boolean):Texture

//Creates a VertexBuffer3D object. 	 	
createVertexBuffer(numVertices:int, 
data32PerVertex:int):VertexBuffer3D

a VertexBuffer3D holds the 3D vertices coordinates + other values such as rgba, uvs, wahtever. it’s important to note that these values are NEVER ALTERED BY THE SHADERS. they’re used as an input, transformed with the constants but its values never change.
an IndexBuffer3D holds the indices of the vertices that will alllow the GPU to render triangles.
a Texture is more or less a BitmapData and a CubeTexture is a series of 6 Textures to create environments.
the Program3D will hold together the Vertex Shader and the Fragment shader.
keeping references to what those methods return will enable you to set them before calling the holy DrawTriangles( indexBuffer3D ); method.

A MATRIX CONSTANT = 4 VECTORS YOU IDIOT !

that was a funnny one for – indeed – a Matrix is a series of 4 * 4 numbers, when setting a Matrix constant, it will take 4 slots, not one, like:

 
//setting a 4 numbers vectors at 0, will give me the constant VC0
context.setProgramConstantsFromVector( "vertex", 0, vector );

//so the next availbale slot is 1 and the constant name will be VC 1
context.setProgramConstantsFromVector( "vertex", 1, vector );

//next availbale slot is 2, let's set a Matrix3D, it will be VC2
context.setProgramConstantsFromMatrix( "vertex", 2, matrix, true);

//SO what's the next available slot ?
// - hmmm 3?
// - NOOOOOOOOOOOOOOOO !
// - the next availbale slot is 6 ! not 3 YOU IDIOT !
// matrix is 4 vectors long so the next is 6 -> VC6
context.setProgramConstantsFromVector( "vertex", 6, vector ); 

just remember you’ll have to be extra careful with this and plan how many slots you’ll need before setting them on the GPU. the number of Constants per shader ( vertex or Fragment ) is limited to 128.

NEVER TRUST A MATRIX THEY’D STAB YOU IN THE THROAT!

well I still can’t understand what’s really happening inside this bloody object… so the solution I used to render my scene is not completely satisfying. I guess the fact that DirectX and OpenGL do not have the same row column order doesn’t help much. proceed with caution.

CLEAN UP BEHIND YOU !

yes, THIS is a source of endless sorrow: after calling drawTriangles(), the swf would crash saying that some constants are set and not used.
when you prepare the context to draw something ; typically you’d say something like:

context.setVertexBufferAt( 0, vertexBuffer, 0, format );

the problem is that this vertex buffer will remain in memory after the drawTriangles() call and when you’ll draw the next object, the GPU will expect the shader to use it and crash if it doesn’t. so before rendering anything, it is wise to implement a 3 steps render logic.

/**
 * @param	context the Context3D to prepare
 */
public function prepareContext( context:Context3D ):void
{
	
	//prepares the resources and set them on the GPU
	context.setVertexBufferAt( 0, vertexBuffer, 0, format );// ->va0
	context.setVertexBufferAt( 1, uvBuffer, 0, format );// ->va1
	context.setTextureAt( 0, texture );// -> fs0

}
/**
 * render pass
 * @param	context
 */
public function render( context:Context3D ):void
{

	//call prepare
	prepareContext( context );
	
	//render
	context.setProgram( program );
	context.drawTriangles( indexBuffer ); 
	
	//clean up Mother Fetcher !
	clearContext( context );
	
}

/**
 * @param	context the Context3D to clear
 */
public function clearContext( context:Context3D ):void
{
	
	//unassigns resources from the GPU
	context.setVertexBufferAt( 0, null );// ->va0 = null
	context.setVertexBufferAt( 1, null );// ->va1 = null
	context.setTextureAt( 0, null );// -> fs0 = null
	
}

not that hard and it might save you some time :)
NB this does not apply to constants !
so you can well have a fc0, fc5 & vc3 or vc92 and leave them in memory. if you don’t overwrite them (reassign their slot) they’ll keep the last value they were assigned. better clean them up also :)

UPDLOADING TO GPU IS SLOW, CONSTANTS ARE FAST

uploading a BitmapData to the GPU is an expensive action, like creating uploading data into a VertexBuffer. this is THE bottleneck when using AGAL. try to avoid it as much as can be. for example, creating a spritesheet is ok, it is uploaded once then you can shift the UVS by setting a constant and perform a small computation before sampling the texture ( or so have I understood ). same goes for meshes, upload them once then tweak them with constants.
amen.

RENDERING TO TEXTURE SUCKS

but I probably did it wrong…

by default drawTriangles() will draw on the backbuffer which is the final composite (an image with an opaque background) but it is also possible to target a texture to render the scene onto. it allows post processing and 2D tweaking ( à la pixel bender ). a typical instruction if you have a texture called texture and a Context3D called context looks like:

//tells the Context3D to render to the texture
context.setRenderToTexture( texture, true, 4 );
//true if you want to use depth, 4 is the level of antialias

//vars to clear the textures some colors (a dark grey)
var r:Number = 0.05;
var g:Number = 0.05;
var b:Number = 0.05;
var a:Number = 0.0; 

//just like the backBuffer, you HAVE TO CLEAR 
//the texture BEFORE using it
context.clear( r, g, b, a );
	
	//then you render something
	render( context );

//and now that the scene or object was rendered into the texture
//you can start drawing to the backbuffer again like this
context.setRenderToBackBuffer();

note that the texture will not be drawn to the screen. it is simply drawn in memory. if you want to print it to screen, you’ll have to create a specific vertex, fragment, program etc. and render it on the backbuffer.

NEED MESH, NEED IT QUICK!

as I needed some meshes, I tweaked the handy AS3GEOMEXPORTER by @SERAF so that it outputs vertices, indices, uvs and normals as Vector.<number>. in general, I don’t need a fully featured exporter / parser and this is a quick and convenient solution. you’ll find the modified version in the “lib” folder of the zip.

next posts will talk more specifically about the heart of AGAL: Shaders.

Tags: , , ,

© 2009 HIDIHO!. All Rights Reserved.

This blog is powered by Wordpress and Magatheme by Bryan Helmig.