Pixel Shader Extensions

Now that we’ve introduced a framework for setting up shaders in the previous chapter, we are going to extend this functionality through a few examples, easing in to more of the capabilities offered to us via HLSL.

Dissolve Away

Lets look at how we remove pixels from the image, by making them transparent. For this example we are going to use a panel from Y3030 – http://y3030.com – as our test image:

y3030panel

To set this up, declare a new variable in the framework (from the last chapter) for a float amount:

float dissolveAmount;

Declaring this in the main section of the shader, will allow us to edit the value via the SunBurn editor.

The dissolve effect we will be looking at uses a simple decision on whether or not each pixel is less than a comparison tolerance, if they are then the pixel is set to transparent.

if (all(diffuse.rgb < dissolveAmount)) {
    diffuse.rgba = 0;
}

The function above that is really driving this effect is the test with all. All is checking that all values within the float4 result are greater than 0. If we break it down for a second, we can rewrite the function and explain what’s going on.

Really, the first part is how the < & > signs are used and what the resulting outcome is. Normally in C# if we were testing a condition and used a < or > sign we would expect a result to be returned as true or false. However, because we are actually performing this test on the rgb elements of the diffuse variable, we are actually getting a true or false result for each element, R, G & B. As such, if we run the shader without the all check, we are actually trying to evaluate a float4 as being true or false (which it can’t be); the shader will throw a compile error.

To test this out and see for yourself, just try modifying the sample from the previous chapter and add the following in:

diffuse.rgb = diffuse.rgb < dissolveAmount;

Your shader will now test every pixels RGB amount as being less than the dissolve amount & if it is, will then assign to each element a 1 or a 0. This will result in some funky hard colours, every colour drawn in this process will be quite strong because it’s only using a colour pallet of 8 possible colours (0,0,0 – 0,0,1 – 0,1,1 – 1,1,1 – 1,0,0 – 1,1,0 – 1,0,1 – 0,1,0, ). If we use this on a simple panel from Y3030 we get a funky result like this:

PannelHardColours

If we add all to the test, we will passing out a value of true or false; because of the world of shaders, we can assign the true value or the false value to the rgb elements of a float 4, like so.

diffuse.rgb = all(diffuse.rgb < dissolveAmount);

This has the result of reducing the colour pallet to 2 (0,0,0 – 1,1,1). So the panel would look like this:

PannelBlackandWhiteColours

Using this function, the dissolve shader leaves the colours in tact which aren’t below the tolerance and those which are above the tolerance, sets the pixel to transparent. Using a low tolerance it looks like this:

lowtollerancepanel 

If we use a higher tolerance, it looks like this:

hightolerance

That’s great and all for a static amount but it’s also a little useless. We need to be able to control the dissolve amount from outside of the shader, so that we can, on queue animate between the two tolerance levels.

Animate this ..

To be able to modify the dissolveAmount from our game we need to create a reference to the shader. Because I have a personal affinity for components, this example will use a component. Before we review the whole component, the following lines are the core, that updates the value in the shader.

theObject = (SceneObject)ParentObject;

(theObject.RenderableMeshes[0].Effect as SasLightingEffect).TransparencyMode = TransparencyMode.Additive;

theObject.RenderableMeshes[0].Effect.Parameters[“dissolveAmount“].SetValue(dissolveAmount);

First a reference to the SceneObject is setup. The transparency mode for the mesh (assuming it only has a single mesh) is set to additive. Finally we set the dissolveAmount value in the shader to the dissolveAmount from our update loop.

Here’s the full component:

public class ShaderComponent : BaseComponentAutoSerialization<ISceneEntity>
{
    [SerializeMember]
    public float dissolveAmount { get; set; }

    SceneObject theObject;

    public ShaderComponent()
    {
        dissolveAmount = 0.0f;
    }

    public override void OnUpdate(GameTime gametime)
    {
        // Call the base implementation.
        base.OnUpdate(gametime);

        dissolveAmount = dissolveAmount + 0.001f;
        if (dissolveAmount >= 1)
        {
            dissolveAmount = 0;
        }

        theObject = (SceneObject)ParentObject;
        (theObject.RenderableMeshes[0].Effect as SasLightingEffect).TransparencyMode = TransparencyMode.Additive;
        theObject.RenderableMeshes[0].Effect.Parameters[“dissolveAmount“].SetValue(dissolveAmount);
    }
}

Each update, increments the amount that’s being dissolved (this example also resets to 0 after it’s completely transparent).

The really awesome part of this is, that no change to the shader is required to support the animation.