October 6, 2006

[XNA] Wrap Your Shaders

One nice thing that the XNA team did was their BasicEffect class. Essentially, they're wrapping a shader in a class to prevent you from having to worry about setting effect parameters manually, and to keep you from having to pick the right technique.

You can do it yourself as well, but unfortunately, you can't inherit from Effect because it's a sealed class.

This sample class wraps the sample shader shown in "How to: Apply an Effect". Let me know if you have any problems with it.

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Components;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace Shaders
{
public class SimpleEffect
{
private Effect myEffect;
private Game myGame;

private const string effectSource = @"
uniform extern float4x4 WorldViewProj : WORLDVIEWPROJECTION;

struct VS_OUTPUT
{
float4 position : POSITION;
float4 color : COLOR0;
};

VS_OUTPUT Transform(
float4 Pos : POSITION,
float4 Color : COLOR0 )
{
VS_OUTPUT Out = (VS_OUTPUT)0;

Out.position = mul(Pos, WorldViewProj);
Out.color = Color;

return Out;
}

technique TransformTechnique
{
pass P0
{
vertexShader = compile vs_2_0 Transform();
}
}
"
;

private Matrix wvp;
private EffectParameter wvp_p;

public Matrix WorldViewProjection
{
get { return wvp; }
set {
// This check takes less time than it takes to set (and possibly recompile) the shader constant
if (value == wvp)
return;

wvp = value;
wvp_p.SetValue(value);
}
}

public SimpleEffect(Game g)
{
CompiledEffect cfx = Effect.CompileEffectFromSource(effectSource, null, null,
CompilerOptions.None, TargetPlatform.Windows);
myGame = g;
IGraphicsDeviceService gfxSvc = myGame.GameServices.GetService<IGraphicsDeviceService>();
GraphicsDevice dvc = gfxSvc.GraphicsDevice;

if (dvc.GraphicsDeviceCapabilities.VertexShaderVersion.Major < 2)
throw new BadObjectException("This shader requires vertex shader 2 or above.");

myEffect = new Effect(dvc, cfx.GetShaderCode(), CompilerOptions.None, null);
wvp_p = myEffect.Parameters["WorldViewProj"];
}

public EffectPassCollection Passes
{
get { return myEffect.CurrentTechnique.Passes; }
}

public void Begin(EffectStateOptions flags)
{
// If you have multiple techniques in your shader, you'd pick the right one here.
myEffect.CurrentTechnique = myEffect.Techniques["TransformTechnique"];
myEffect.Begin(flags);
}

public void CommitChanges()
{
myEffect.CommitChanges();
}

public void End()
{
myEffect.End();
}
}
}

No comments: