Showing posts with label Cel-Shading. Show all posts
Showing posts with label Cel-Shading. Show all posts

Thursday, August 11, 2011

More XNA Cel Shading - Cel Shaded Animated Models using a SkinnedModelProcessor

This short post builds upon my previous article A Cel-Shading Example in XNA 4.0, applying what we did there to a skinned model with animation.

Before you begin
First you will need to complete and understand the Skinned Model tutorial on the MSDN AppHub site http://create.msdn.com/en-US/education/catalog/sample/skinned_model. After that this example is pretty straight forward as long as you understand what digitalerr0r did in his post XNA Shader Programming Tutorial 7, Toon shading and what we in my previous post A Cel-Shading Example in XNA 4.0.

Update CelShader.fx
First we will update out CelShader.fx and call it SkinnedCelShader.fx.  Using the Microsoft stock effects as an example add the following message to get the skinning data from the model.
/* Get the skinning data from each of the bones
 */
void Skin(inout VertexShaderInput vin, uniform int boneCount)
{
    float4x3 skinning = 0;

    [unroll]
    for (int i = 0; i < boneCount; i++)
    {
        skinning += Bones[vin.Indices[i]] * vin.Weights[i];
    }

    vin.Position.xyz = mul(vin.Position, skinning);
    vin.N = mul(vin.N, (float3x3)skinning);
}
And then call Skin from your vertex shader before making any of the other position calculations
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    Skin(input, 4);

    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);

    output.Tex = input.Tex;
    output.L = normalize(LightDirection);
    output.N = normalize(mul(InverseWorld, input.N));

    return output;
}
The game class
The modifications to the game class are pretty simple.  Start with the game class you implemented in the SkinnedEffect tutorial.

Add some class members to hold our new effect data and some parameters we will need to set their properties:
/* CelShader effects and data
 */
Effect celShader; // Toon shader effect
Texture2D celMap; // Texture map for cell shading
Vector4 lightDirection; // Light source for toon shader

Effect outlineShader; // Outline shader effect
float defaultThickness = 1.0f; // default outline thickness
float defaultThreshold = 0.9f; // default edge detection threshold
float outlineThickness = 1.0f; // current outline thickness
float outlineThreshold = 0.9f; // current edge detection threshold
float tStep = 0.01f; // Ammount to step the line thickness by
float hStep = 0.001f; // Ammount to step the threshold by

// Render target for post render outlining
RenderTarget2D celTarget;

Set our light direction in the initialize method:
/* Set our light direction for the cel-shader
 */
lightDirection = new Vector4(2f, 45f, -110f, 1.0f);
And in LoadContent load our custom effects:
// load and initialize our cel shader effect
celShader = Content.Load<Effect>("SkinnedCelShader");
celMap = Content.Load<Texture2D>("celMap");

celShader.Parameters["Projection"].SetValue(proj);
celShader.Parameters["View"].SetValue(view);
celShader.Parameters["LightDirection"].SetValue(lightDirection);
celShader.Parameters["CelMap"].SetValue(celMap);

/* Load and initialize the outline shader effect
 */
outlineShader = Content.Load<Effect>("OutlineShader");
outlineShader.Parameters["Thickness"].SetValue(outlineThickness);
outlineShader.Parameters["Threshold"].SetValue(outlineThreshold);
outlineShader.Parameters["ScreenSize"].SetValue(
new Vector2(GraphicsDevice.Viewport.Bounds.Width, GraphicsDevice.Viewport.Bounds.Height));

/* Set up a render target to draw our cel shaded model to
 * for post render outlining
 */
celTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height,
    false, SurfaceFormat.Color, DepthFormat.Depth24);

Finally update Draw to draw our shaded model (you will notice this is pretty much identical to the way we drew the cel shaded model in my previous post).  The only trick here is that we need to get the model's texture.  We do not have that directly because its reference is baked into the fbx.  Because our model is loaded with its default effect as SkinnedEffect though we can get it from there with the following line of code:
Texture2D texture = ((SkinnedEffect)meshPart.Effect).Texture;
The full draw method:
protected override void Draw(GameTime gameTime)
{
    /* Set our render target
     */
    GraphicsDevice.SetRenderTarget(celTarget);

    /* Make sure we have a depth stencil for proper
     * depth culling
     */
    GraphicsDevice.DepthStencilState = DepthStencilState.Default;

    /* clear the graphics device with a color with a clear
     * alpha channel.
     */
    Color alpha = Color.White;
    alpha.A = 0;
    GraphicsDevice.Clear(alpha);

    /* Get our model bones from the animation player
     * so that we can pass this to the cel shader effect
     */
    Matrix[] bones = animationPlayer.GetSkinTransforms();

    // for each model in the mesh
    foreach (ModelMesh mesh in model.Meshes)
    {
        // for each mesh part
        foreach (ModelMeshPart meshPart in mesh.MeshParts)
        {
            // Set the vertex buffer and indices in the graphics device
            GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer,
            meshPart.VertexOffset);
            GraphicsDevice.Indices = meshPart.IndexBuffer;

            /* The texture of a skinned model is available in SkinnedEffect.Texture.
             * We are loading our model with the SkinnedModelProcessor and using
             * SkinnedEffect as the model's default effect. Use that to get the
             * texture of the current mesh part.
             */
            Texture2D texture = ((SkinnedEffect)meshPart.Effect).Texture;

            /* Set up a simple world, no translation, scaling or rotation
             * for this example
             */
            Matrix world = Matrix.Identity;

            /* Set the color map, world, inverse world and bones
             * properties of the cel shader effect
             */
            celShader.Parameters["ColorMap"].SetValue(texture);
            celShader.Parameters["World"].SetValue(world);
            celShader.Parameters["InverseWorld"].SetValue(Matrix.Invert(world));
            celShader.Parameters["Bones"].SetValue(bones);

            // for each effect pass in the cell shader
            foreach (EffectPass pass in celShader.CurrentTechnique.Passes)
            {
                // apply the effect
                pass.Apply();

                // and draw the current mesh part
                GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                    meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
            }
        }
    }

    /* We are done with the render target so set it back to null.
     * This will get us back to rendering to the default render target
     */
    GraphicsDevice.SetRenderTarget(null);

    /* Clear the device to get ready for more drawing
     */
    GraphicsDevice.Clear(Color.Wheat);

    /* Draw the game model again without cell shading so we can do a side
     * by side comparison
     */
    foreach (ModelMesh mesh in model.Meshes)
    {
        foreach (ModelMeshPart meshPart in mesh.MeshParts)
        {
            foreach (SkinnedEffect effect in mesh.Effects)
            {
                effect.SetBoneTransforms(bones);
                effect.View = view;
                effect.Projection = proj;
                effect.EnableDefaultLighting();
                effect.SpecularColor = Vector3.Zero;
            }
            mesh.Draw();
        }
    }

    /* Draw the cel shaded model with outlining
     */
    spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied,
        null, null, null, outlineShader);
    spriteBatch.Draw(celTarget, new Vector2(-175, 0), Color.White);
    spriteBatch.End();

    /* Draw our debug message
     */
    spriteBatch.Begin();
    spriteBatch.DrawString(debugFont, debugMsg, debugLoc, consoleFgColor);
    spriteBatch.End();

    base.Draw(gameTime);
}

Things to watch out for:
After adding your animated model to the content project make sure you change its content processor property to SkinnedModelProcessor and then under set the default effect to SkinnedEffect.  If you miss either of these steps you will get errors.

Here is what the result will look like (cel shaded model on the left compared to a naturally lit model on the right).

You do not have the latest version of Flash installed. Please visit this link to download it: http://www.adobe.com/products/flashplayer/




The full code for this project can be downloaded here.

Good luck!

Tuesday, August 9, 2011

A Cel-Shading Example in XNA 4.0

Recently I came across an excellent cel-shading tutorial written by digitalerr0r titled XNA Shader Programming – Tutorial 7, Toon shading for the XNA 3.0 framework.  With a few minor updates we can get this working in XNA 4.0 as well.

This post will cover the changes necessary to apply the toon and edge detection shaders presented by digitalerr0r in XNA 4.0.  For a more in depth explanation of the cel-shading techniques used here please see digitalerr0r's original article.

Complete source code can be found at the end of this post.

HLSL Shaders
While it wasn't strictly necessary to update the shaders presented by digitalerr0r I chose to modify them to follow the HLSL code style preferred (inferred) by the Visual Studio effects templates.

The Toon Shader
Most of these updates are trivial variable name changes and the use of a struct for the input to the vertex shader function.  The most significant change here was splitting the composite word, view, projection matrix into three separate components.  While this might have a small performance hit (the matrix multiplication happens for each vertex) the style and use of the effect is more consistent with default effects provided by the XNA framework.  This should make the effect more recognizable to those of you that are just starting out with XNA and you more experienced programmers out there will know how to switch it back.

The Edge Shader
Again just a few minor changes here (and a  few small tweaks for correct alpha blending and more generalized scene handling).  First even though we do not have a vertex shader I am preserving the inferred effect style of the XNA templates.  Namely the input to the pixel shader is the output of the vertex shader.  Thus we still have our VertexShaderOutput struct that is the input for the pixel shader function.

Next I have replaced the float2 QuadScreenSize in the pixel shader function with an effect parameter float2 ScreenSize.  This variable is the same thing but it is set as a parameter letting the game code scale the effect to the texture that is being processed.

Finally the last change to this shader is a very small, rather subtle update to fix a problem with alpha blending in the original shader.  The return value has been changed to
Color*float4(result.xxx,1);
instead of 
Color*result.xxxx;
When we have an edge pixel result is 0 multiplying Color by result.xxxx causes the alpha channel of the color to also be multiplied by zero.  Remember that a value of 0 is a completely transparent pixel when alpha blending is on.  If we don't do this our nice black outlines could disappear on us!

Game Code
Finally ;) This is where the bulk of the changes are to use digitalerr0r's excellent shaders in XNA 4.0.  The main differences are in the way XNA 4 implements RenderTargets and applies effects.

XNA 4.0 completely reworked render targets, they are still there but the way they are set on a GraphicsDevice is different, supposedly easier and more efficient.  The new RenderTarget2D constructor that I will use takes 5 parameters, the GraphicsDevice, a width, height, color format and depth format.
celTarget = new RenderTarget2D(GraphicsDevice,
    GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height,
    false, SurfaceFormat.Color, DepthFormat.Depth24);
The important part here is that we set the depth format to Depth24.  This gives us a 24 bit depth buffer, without it we will not get proper (or any for that matter) depth culling.  Because our model for this example is rather simple we could get away with Depth16 for a 16 bit depth buffer but I went ahead and used 24.  The other options Depth24Stencil8 gives us a 24 bit depth buffer and an 8 bit stencil buffer (this is what you get when rendering to the default screen render target) and None which is no depth buffer and the default for new render targets.

The next XNA 4.0 change is setting the render targets.  We do not need to worry about render target indexes any more, just set it.
GraphicsDevice.SetRenderTarget(celTarget);
With the rework of the render targets Microsoft also changed up the device render states to make them easier to use.  Creating device states is still expensive so you will want to do that in code that is not time critical or use some form of state cache.  Setting states on a device is extremely inexpensive though and can be done as often as needed.  The important thing for us to know here is that when we set the graphics device to our render target our DepthStencilState will be None and we will not get depth culling.  Fix this by adding
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
after setting the render target.

The next change is with the effect.  In XNA 4.0 it is no longer necessary to begin and end an effect, just apply it.  Otherwise our drawing code is still the same:
foreach (ModelMesh mesh in model.Meshes)
{
    Matrix world = bones[mesh.ParentBone.Index];
    celShader.Parameters["World"].SetValue(world * rotation);
    celShader.Parameters["InverseWorld"].SetValue(Matrix.Invert(world * rotation));

    foreach (ModelMeshPart meshPart in mesh.MeshParts)
    {
        GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);

        GraphicsDevice.Indices = meshPart.IndexBuffer;
        celShader.CurrentTechnique = celShader.Techniques["ToonShader"];

        foreach (EffectPass effectPass in celShader.CurrentTechnique.Passes)
        {
            effectPass.Apply();

            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
        }
    }
}
Our cel shaded model is now drawn into our render target and ready for post processing edge detection.  Again the begin and end for the effect are gone, now we just pass the effect into the sprite batch when we call its begin.  (In this example the parameters for the edge effect are set in the content loading and update methods.)
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied,
    null, null, null, outlineShader);
spriteBatch.Draw(celTarget, Vector2.Zero, Color.White);
spriteBatch.End();
Here we are using an overload of SpriteBatch.Begin that takes a sort mode, blend state, sampler state, depth stencil state, rasterizer state and effect.  Because we do not need special sampling, depth treatment or rasterizing here those are all null.  We do use BlendState.NonPremultiplied to enable non-premultiplied alpha blending though and of course our edge outlining effect is passed in for the effect parameter.

That's it... with those changes digitalerr0r's great cel-shader code will be running in XNA 4.0.

Full source code for my example can be downloaded here XNA 4.0 Cel-Shader