Multi pass shaders

Aug 31, 2009 at 10:23 PM

Hey all,

Maybe somebody has figured this out and maybe im doing it the way it suppose to be used. But im just checking if maybe a faster way is possible.

Lets say i have a model that has its own class that derives from IDraw:

public class TestClass: IDraw, IContentOwner, IUpdate
{
        private ModelInstance model;
        ......
}

Now lets say i have a Shader that requires to run 4 techniques on that model. I  made then a ModelInstanceShaderProvider for that model that would support those 4 techniques.

enum TestRenderMode
{
    One,
    Two,
    Three,
    Four
}

class PlanetShaderOverride : ModelInstanceShaderProvider
{
    public override bool ProviderModifiesWorldMatrixInBeginDraw
    {
        get { return false; }
    }
    public override bool BeginGeometryShaderOverride(DrawState state, GeometryData geometry, MaterialLightCollection lights)
    {
        switch (state.GetDrawFlag<TestRenderMode>())
        {
            case TestRenderMode.One:
                Shader.RenderOne shaderOne = state.GetShader<Shader.RenderOne>();

                // Set shader params
                // ......

                shaderOne.Bind(state);
                break;
            case TestRenderMode.Two:
                Shader.RenderTwo shaderTwo = state.GetShader<Shader.RenderTwo>();

                // Set shader params
                // ......

                RenderTwo.Bind(state);
                break;
            case TestRenderMode.Three:
                Shader.RenderThree shaderThree = state.GetShader<Shader.RenderThree>();

                // Set shader params
                // ......

                RenderThree.Bind(state);
                break;
            case TestRenderMode.Four:
                Shader.RenderFour shaderFour = state.GetShader<Shader.RenderFour>();

                // Set shader params
                // ......

                RenderFour.Bind(state);
                break;
        }
    }
}

Now with this class i can set the render flags on the model class (the class above), i did it like this:

public void Draw(DrawState state)
{
    DrawFirstPass(state);
    DrawSecondPass(state);
    DrawThirthPass(state);
    DrawFourthPass(state);
}

private void DrawFirstPass(DrawState state)
{
    state.PushDrawFlag<TestRenderMode>(TestRenderMode.One);

    state.PushWorldMatrixMultiply(ref worldMatrix);
    state.PushWorldMatrixMultiply(ref viewMatrix);

    //ModelData stores accurate bounding box information
    //the ModelInstance uses this to cull the model
    if (model.CullTest(state))
    {
        model.Draw(state);
    }

    state.PopWorldMatrix();
    state.PopWorldMatrix();

    state.PopDrawFlag<TestRenderMode>();
}

private void DrawSecondPass(DrawState state)
{
    state.PushDrawFlag<TestRenderMode>(TestRenderMode.RenderTwo);

    Matrix scale = Matrix.CreateScale(1.01f);
    state.PushWorldMatrixMultiply(ref worldMatrix);
    state.PushWorldMatrixMultiply(ref scale);
    //state.PushWorldMatrixMultiply(ref viewMatrix);

    state.PushRenderState();
    state.RenderState.AlphaBlend.SetToAlphaAdditiveBlending();
    state.RenderState.DepthColourCull.DepthWriteEnabled = false;

    if (model.CullTest(state))
    {
        model.Draw(state);
    }

    state.PopRenderState();

    state.PopWorldMatrix();
    state.PopWorldMatrix();

    state.PopDrawFlag<TestRenderMode>(); 
}

private void DrawThirthPass(DrawState state)
{
    state.PushDrawFlag<TestRenderMode>(TestRenderMode.RenderThree);

    // Same story here.... Draw model etc.

    state.PopDrawFlag<TestRenderMode>(); 
}

private void DrawFourthPass(DrawState state)
{
    state.PushDrawFlag<TestRenderMode>(TestRenderMode.RenderFour);

    // Same story here.... Draw model etc.

    state.PopDrawFlag<TestRenderMode>(); 
}

 

Should this be used like this? Because now i have to render the model four times to get the desired effect. I just would like to know if i handle correctly or not :).

Coordinator
Sep 1, 2009 at 7:51 PM
Edited Sep 1, 2009 at 7:52 PM

Hi vinnepin. As usual apologizes for a slow response. I should have proper internet and an xbox in the next few days.

That is pretty much the correct way to go about it. There are some variations, but that should work fine. There will be some overhead if your model has multiple geometry and mesh instances within it (ie, loads of draw calls that require some extra setup...) however it won't be enormously significant. Frankly, the overheads in the effect system were probably just as bad, if not worse :-)

If you do find you have problems, it wouldn't be *that* hard to retrofit ShaderProvider to have a virtual 'PassCount' property, which could be looped on... But I honestly don't think you will need it.