Spritesheets With Minko
Update: the full source code for this tutorial is available in minko-examples on GitHub.
Spritesheets are a nice way to create nice effects with an extensive artistic control. You just have to create a texture with a series of cool-looking sprites to have a nice animation. They are widely used for particles, fog and explosions for example. We used spritesheets to render the explosions and the clouds in our latest game: The Mirage.
When you have the spritesheet itself, you need two things to get it working in your application:
- A "frame id" value that will be updated to tell which frame of the spritesheet should be sampled.
- A shader that will sample the spritesheet accordingly.
And here is what you get:
Render To Texture With Minko 2
Update: If you want to give it a try, you can start working with Minko 2 beta today!
During last week "Starting With Shaders" workshop, nicoptere asked me about Render To Texture (RTT). RTT is a very common technique used for multi-pass rendering. The principle is quite simple: the pre-passes render the scene in one or more textures. This texture can then be sampled to provide per-pixel data during the rendering pass.
A very common use for RTT is shadow mapping. Shadow mapping is a technique to create projected shadows. The shadow mapping rendering pass will require access to the depth of each pixel as seen from the light source which is "emitting" the shadow. Therefore, each mesh will have more than just a rendering pass. Indeed, we will have to add a pre-pass for each light in the scene to generate it's "depth map".
RTT can also be used to create portals! So it's a very cool feature.
Procedural Animated Flag With Minko ShaderLab
I used Alexandre's work about waves simulation on the GPU with the ShaderLab to create a French flag with a simple blue-white-red procedural texture. Here is the result:
As always with the ShaderLab, not a single line of ActionScript and everything is hardware accelerated! If you want to subscribe to the Minko ShaderLab beta, you can apply on the "Minko ShaderLab: Beta testers wanted!" thread on Aerys Answers.
Minko ShaderLab: Waves Simulation On The GPU with Flash
Alexandre Cyprien - one of Aerys' engineers - trained himself on the Minko ShaderLab with quite a challenge: waves simulation on the GPU! Alexandre is doing a very extensive work on 3D compression and Artificial Intelligence algorithms. He is not a GPU programmer so I'm very happy he was able to create such a cool and complex effect in no time with the ShaderLab! He took his inspiration from the GPU Gem Article: Effective Water Simulation from Physical Models. Here are the results:
There is not a single line of ActionScript here: everything is done on the GPU! Even the little embed above was created using the "share" feature of the ShaderLab I already detailed in a previous post.
You can actually embed it on your own page/blog with the following HTML code:
<iframe src="http://shaderlab.aerys.in/view?url=http://shaderlab.aerys.in/samples/Circular%20And%20Directional%20Waves%20v2.mks"
width="640"
height="360">
</iframe>ShaderLab: Embedding Shaders In A Web Page
As you can read in the online Backsun FAQ, we are currently working on a tool called the "ShaderLab". This tools provides a visual programming environment to create, debug and test shaders in a user-friendly way:

The ShaderLab is a Flex web application. It's really easy to use and anyone can start playing. The only requirement is to know shader basics and 3D maths. The tool is actually so powerful that you can build an entire hardware accelerated particles engine without a single line of AS3 (you can look at the first screenshot if you don't believe me). But I won't speak about that today.
Today I want to introduce a very cool feature based on the fact that we are working on 3D for the web. To me, the web component is something that must be used to provide new features. And being able to program the GPU and create shaders with a web Flex application is one of those features. And another one is the possibility to share and embed those very shaders on a web page.
Indeed, the ShaderLab will provide a "Share" button "à la Youtube". It will provide an HTML code - an iframe really - to embed your creations on your blog/website. Here the embedding code in action:
More samples and details after the jump...
Single Pass Cel Shading
Cel shading - aka "toon shading" - is an effect used to make 3D rendering look like cartoon. It was used in many flavours in a few games such as XIII or Zelda Wind Wakers.
TL;DR
Click on the picture above to launch the demonstration.
The final rendering owns a lot to the orginal diffuse textures used for rendering. But this cartoon style is also achieved using two distinct features on the GPU:
- The light is discretized using a level function.
- A black outline on the edges.
This is usually done using two passes (or even more). One pass renders the scene in the backbuffer with the lighting modified by the level function. Another pass renders the normals only and then pass is done as a post-process to perform an edge detection algorithm (a Sobel filter) to add the black outline.
Another technique uses two passes: one to render the object with the lighting, and a second one with front-face culling and a scale offset to render the outline with a solid black color.
But I thought it might be done more efficiently in one pass using a few tricks. The most difficult part here is how to get the black outline. Indeed, we are working on a per-vertex (vertex shader) or per-pixel (fragment shader) basis. Thus, it's pretty hard to work on edges. It's pretty much the same problem we encountered when working on wireframe, except here we want to outline the object and not the triangles. But this little difference actually makes it a lot easier for us.
Why? Because detecting the edges of the 3D shape is much easier.
The Outline
The trick is to get the angle between the eye-to-vertex vector and the vertex normal. If that very angle is close to PI/2 (=90°), then the vertex is on an edge. If the vertex is on an edge, then we will displace it a bit toward its normal. The vertices deplaced this way will form an outline around around shape:
public class CelShadingShader { private var _isEdge : SValue = null; override protected function getOutputPosition() : SValue { var eyeToVertex : SValue = normalize(subtract(vertexPosition, cameraLocalPosition)); _isEdge = lessThan( -0.05, dotProduct3(vertexNormal, eyeToVertex) ); var thickness : SValue = getNamedParameter("thickness", .04); var delta : SValue = multiply(_isEdge, vertexNormal.xyz, thickness); return multiply4x4( add(vertexPosition, float4(delta, 0)), localToScreenMatrix ); } override protected function getOutputColor() : SValue { var outline : SValue = lessThan(interpolate(_isEdge), 0.1); return float4(float3(outline), 1); } }
Our fragment shader is pretty simple here: _isEdge is supposed to contain 1 if the vertex is on an edge, 0 otherwise. Therefore, as we want our outline to be black, we simply use the "lessThan" operation. If the vertex is on an edge, the outline value will be 0 and 1 otherwise. We just have to multiply "outline" with whatever color we want to use.
You'll get the following result:

The Light Level Function
There are many ways to transform a continuous per-pixel lighting equation into another one that will use levels. The best way to make it possible for the artists to customize it is to use a light texture. The Lambert factor is then used as the UVs to sample that texture.
But here I wanted this effect to rely on no textures (except a diffuse one eventually). So I implemented a very simple level function using an euclidian division:
private static const NUM_LEVELS : uint = 6; private static const DEFAULT_THICKNESS : Number = 0.04; private static const DEFAULT_AMBIENT : Number = .2; private static const DEFAULT_LIGHT_DIRECTION : ConstVector4 = new ConstVector4(1., -1., 0.); private static const DEFAULT_DIFFUSE_COLOR : ConstVector4 = new ConstVector4(1., 1., 1., 1.); override protected function getOutputColor() : SValue { var vertexNormal : SValue = normalize(interpolate(vertexNormal)); var lightDirection : SValue = getNamedParameter("light direction", DEFAULT_LIGHT_DIRECTION); lightDirection.normalize(); // diffuse lighting var lambertFactor : SValue = saturate( negate(dotProduct3(lightDirection, vertexNormal)) ); // cel shading lambertFactor = multiply(lambertFactor, NUM_LEVELS); lambertFactor = subtract(lambertFactor, fractional(lambertFactor)); lambertFactor = divide(lambertFactor, NUM_LEVELS); // ambient lighting var ambient : SValue = getNamedParameter("ambient", DEFAULT_AMBIENT); lambertFactor.incrementBy(ambient); var diffuseColor : SValue = getNamedParameter("diffuse color", DEFAULT_DIFFUSE_COLOR); return float4( multiply(diffuseColor.rgb, lambertFactor), diffuseColor.a ); }
Here is what you get:

The Final Shader
You can combine both effects by simply multiply the Lambert factor by the outline value we used to output.

And voilà!
public class CelShadingShader { private static const NUM_LEVELS : uint = 6; private static const DEFAULT_THICKNESS : Number = 0.04; private static const DEFAULT_AMBIENT : Number = .2; private static const DEFAULT_LIGHT_DIRECTION : ConstVector4 = new ConstVector4(1., -1., 0.); private static const DEFAULT_DIFFUSE_COLOR : ConstVector4 = new ConstVector4(1., 1., 1., 1.); private var _isEdge : SValue = null; override protected function getOutputPosition() : SValue { var eyeToVertex : SValue = normalize(subtract(vertexPosition, cameraLocalPosition)); _isEdge = lessThan( -0.05, dotProduct3(vertexNormal, eyeToVertex) ); var thickness : SValue = getNamedParameter("thickness", .04); var delta : SValue = multiply(_isEdge, vertexNormal.xyz, thickness); return multiply4x4( add(vertexPosition, float4(delta, 0)), localToScreenMatrix ); } override protected function getOutputColor() : SValue { var vertexNormal : SValue = normalize(interpolate(vertexNormal)); var lightDirection : SValue = getNamedParameter( "light direction", DEFAULT_LIGHT_DIRECTION ); lightDirection.normalize(); // diffuse lighting var lambertFactor : SValue = saturate( negate(dotProduct3(lightDirection, vertexNormal)) ); // cel shading lambertFactor = multiply(lambertFactor, NUM_LEVELS); lambertFactor = subtract(lambertFactor, fractional(lambertFactor)); lambertFactor = divide(lambertFactor, NUM_LEVELS); // ambient lighting var ambient : SValue = getNamedParameter("ambient", DEFAULT_AMBIENT); lambertFactor.incrementBy(ambient); var diffuseColor : SValue = getNamedParameter( "diffuse color", DEFAULT_DIFFUSE_COLOR ); // outline var outline : SValue = lessThan(interpolate(_isEdge), 0.1); lambertFactor.scaleBy(outline); return float4( multiply(diffuseColor.rgb, lambertFactor), diffuseColor.a ); } }
Minko 2.0 New Feature: Wireframe Rendering
One of the new things coming up with the next version of Minko (Minko 1.0 is available here) is a new extension called "minko-effects". This extension will feature new rendering effects, shaders and shader parts. One of them is the wireframe effect. Wireframe is very useful to see what the geometry looks like and it is widely used to debug 3D applications.
Rendering real-time 3D using wireframe is usually not that much of a big deal: OpenGL and DirectX make it easy to switch from normal to wireframe rendering. But it works only when using the fixed function pipeline. Not with shaders. As every shader program defines how rendering should be perform, it is actually quite logic that wireframe should be handled there. But doing wireframe in the shader is really not easy, mainly because it is a per-edge algorithm and we can only work per-vertex or per-fragment.
In this article, I will try to expose the main challenges regarding the implementation of a single pass wireframe method in a shader and how Minko makes it possible to address those issues.
Implementation
We based our work on the Shader-Based Wireframe Drawing article and the minimole implementation. Of course, just like any shader in Minko, it is written with ActionScript instead of AGAL/PB3D. The single pass rendering method is very well explained in the article:
The intensity, Ip, of a fragment centered at p is computed based on the window space distances d1, d2, and d3 to the three edges of the triangle
In the single pass method, the polygon edges are drawn as an integral part of drawing the filled polygons. For each fragment of a rasterized polygon, we need to compute the line intensity, I(d), which is a function of the window space distance, d, to the boundary of the polygon which we assume is convex. The fragment color is computed as the linear combination I(d) Cl + (1−I(d)) Cf where Cl and Cf are the line and face colors, respectively.
Source: Shader-Based Wireframe Drawing
Implementation details, code and demo application right after the jump...
New Minko Tutorial: Vertex Attributes In The Fragment Shader
Passing values from the vertex to the fragment shader is a very common thing. The simplest use case is when you want to sample a texture: this operation can only be done in the fragment shader, but the texture coordinates to sample are accessible in a vertex attributes in the vertex shader. In the end, any non-trivial shader will require to pass values from the vertex shader to the fragment shader.

The picture above is a very simple use case: we define a per-vertex RGB color and use it to draw a triangle. Everything is explained in the "Work with vertex attributes in the fragment shader" tutorial on the Hub.
The actual shader is pretty simple, but I recommend you read the entire tutorial to understand it properly:
public class RGBShader extends ActionScriptShader { private var _color : SValue = null; override protected function getOutputPosition() : SValue { var xy : SValue = float2(getVertexAttribute(VertexComponent.XY)); _color = getVertexAttribute(VertexComponent.RGB); return float4(xy, 0., 1.); } override protected function getOutputColor() : SValue { return interpolate(_color); } }
Another interesting thing is optimization. As values are linearily interpolated between the vertex shader and the fragment shader, it makes it possible to perform some per-vertex computations and reuse them in the fragment shader. The only requirement is that those values have to be linearily interpolable. In other words: every linearily interpolable values computed in the fragment shader could be computed in the vertex shader instead. It gives a very important performance boost, as the vertex shader is much faster and runs on a much smaller data set: we always have a lot less vertices than we have pixels. I will soon write an article about this very optimization.
Last but not least, this tutorial uses the latest additions in Minko. Among them, an easier way to declare vertices and fill a vertex stream using a VertexIterator. Indeed, you can now use an (inlined) Object to initialize an entire vertex at once. It was done by overloading the flash_proxy::setProperty() method in the VertexIterator dynamic class. You can now chose between 3 different approaches to fill a vertex stream:
var vstream : VertexStream = new VertexStream(null, format); var vertices : VertexIterator = new VertexIterator(vstream); // the inline version (new!) vertices[0] = {x: 0., y: .5, r: 1., g: 0., b: 0.}; vertices[1] = {x: -.5, y: -.5, r: 0., g: 1., b: 0.}; vertices[2] = {x: .5, y: -.5, r: 0., g: 0., b: 1.}; // the dynamic version vertices[0].x = 0.; vertices[0].y = .5; vertices[0].r = 1.; vertices[0].g = 0.; vertices[0].b = 0.; vertices[1].x = -.5.; vertices[1].y = -.5; vertices[1].r = 0.; vertices[1].g = 1.; vertices[1].b = 0.; vertices[2].x = .5; vertices[2].y = -.5; vertices[2].r = 0.; vertices[2].g = 0.; vertices[2].b = 1.; // the fast version vstream.push( 0., .5, 1., 0., 0., -.5, -.5, 0., 1., 0., .5, -.5, 0., 0., 1. );
All those 3 versions do the very same thing, but in a different fashion. You can now use any of those depending on what you are doing.
As always, if you have questions or suggestions, you can post them on Aerys Answers.



Aerys