Effect Files (.fx)

One big problem programming games for the PC Platform is that the specification of the platform varies widely. Users of your game may have really old graphic cards or the very latest card with all the bells and whistles. Traditionally when a Direct3D game starts up it tests the machine to see what is available, e.g. does this machines' graphic card support pixel shaders 2.0? The rendering of primitives is then done with the best method for that card. This can be really clumsy and create a lot of spaghetti code and data. Effect files allow the programmer or artists to specify different techniques dependant on the capabilities of the platform.

Another issue that is growing in games development is the huge amount of art assets required. The creation of these assets and the process of converting them into the game environment can cause real bottlenecks in development (production pipeline). In the past what the artists saw in their 3D package was not necessarily what they would see when the art was in the game. The game would choose at runtime hard coded methods of how to render a particular piece of art. Nowadays this process is improving with the addition of higher level shading languages like HLSL and Cg that define rendering methods and effects files that define the rendering context (based on platform etc.). We are getting toward the point where the artist has a WYSIWYG view in their package and can tweak shaders in the package and assign effects to a scene entity. Rendering techniques can now be separated from the graphics engine and become part of the art pipeline.

Effect files can contain different rendering techniques that can be chosen based on the hardware available as well as HLSL code to define rendering. Direct3D provides a set of interfaces to allow the easy manipulation of effects.

Effect File Structure

An effect file is a text file with a .fx extension. It is split into three main sections:

  1. Variable declarations - these are values that can be set before rendering and then used in this effect file. Examples include: textures, world matrix, lighting parameters
  2. Techniques & Passes- defines how something is to be rendered. It includes state information along with vertex and shader declarations.
  3. Functions - the shader code written in HLSL

Effect File Parameters

Parameters are defined at the start of the effect file. You must specify a type and an id (a unique name). You can then provide specific values depending on the type specifying the usage, user defined data and initial values.

Examples

float4 Diffuse : DIFFUSE = { 0, 0, 0, 1 };

float4 is the type (this must be one of the HLSL shader types), Diffuse is the name, the unique id. After the colon is a tag specifying the usage, in this case it is to be used as a diffuse colour. Finally the value is initialised to 0,0,0,1 (red, green, blue, alpha).

float4x4 World : WORLD;
float4x4 View : VIEW;
float4x4 Projection : PROJECTION;

The above declares three parameters with ids: World, View and Projection that will be used to set the world, view and projection matrix to be used during this effect file.

Parameters can be read from and written to at run time by the application. More about how to do this later.

Effect File Techniques & Passes

For this one effect you can provide many techniques. The one most suitable to the graphic card on the users platform is chosen at run time. Within each technique you can define a number of passes. For example you could provide a technique to blend two textures in one pass and another technique for older hardware to blend the textures in two passes.

You can set states to values or expressions. They are set in a very similar way to normal Direct3D code (as shown in the states notes). E.g. in your code you might write:

gDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);

but in the effect file the same thing is achieved by writing:

ColorOp[0]=SelectArg1;

Other examples:

Lighting = true; // turn on lighting
LightEnable[0] = true; // turn on light 0
ZEnable = true; // turn on depth tests

You can look in the DirectX help for all the states and options or see the MSDN page here: Effect States

A technique is defined as the keyword technique followed by an optional unique id (name) e.g.

technique TextureEnabled

Within the technique you can define the passes which contain the actual state assignments. So an example of two techniques, the second having two passes, could be:

technique FirstTechnique
{
    pass P0
    {
          // Set states here for pass 0
    }
}

technique SecondTechnique
{
    pass P0
    {
          // Set states here for pass 0
    }

    pass P1
    {
          // Set states here for pass 1
    }

}

A fuller example below is shown. This is a simple texturing technique not using shaders:

texture diffuseTexture : DiffuseMap; // Variable declaration

technique textureTechnique
{
     pass p0
     {
         Texture[0] = <diffuseTexture>;

         MinFilter[0] = Linear;
         MagFilter[0] = Linear;
         MipFilter[0] = Linear;

         ColorOp[0] = Modulate;
         ColorArg1[0] = Texture;
         ColorArg2[0] = Diffuse;
         AlphaOp[0] = SelectArg2;
         AlphaArg1[0] = Texture;
         AlphaArg2[0] = Diffuse;
     }
}

The texture is assigned to stage 0 then the filtering is set to bilinear. The texture is then modulated with the diffuse colour by setting the colour operation.

Effect File Functions

The above examples did not include any shader code. We could if we wanted place the assembly shader code directly into the pass e.g.:

VertexShader =
asm
{
      vs_1_1
      m4x4 oPos, v0, c4 // pos in screen space.
};

Most people now prefer using a high level shader language like HLSL to write their shaders in 'C' like code. Within the effect file we can write this code in a function just like we would write any shader code. We then specify the use of this shader in a technique pass e.g.

VertexShader = compile vs_1_1 OurShaderName();

For detailed notes on writing shaders please see this section of the site: Shaders

Loading Effect Files

So you now know the basics of writing effects files. The final thing to know is how to load and use them in your game code.

As with all of DirectX we must first create an instance of a DirectX object that provides the required interfaces. To allow us to work with a particular effect file the function call is D3DXCreateEffectFromFile e.g.

LPD3DXEFFECT anEffect;
D3DXCreateEffectFromFile(gDevice,"fxfname.fx",NULL,NULL,0,NULL,&anEffect,NULL)

The main parameters are the effect file filename and the address of an effect interface pointer that (on success) will be assigned to a ID3DXEffect object. The full function definition can be found on MSDN here: D3DXCreateEffectFromFile.

Once the effect file has been loaded we can choose a technique from it to use based on the users hardware. We need to know which effect technique will work on this hardware so we get Direct3D to validate each technique. We can either validate each one by name using ValidateTechnique but I find it is easier to use FindNextValidTechnique to simply find the first technique in the effect file that will work:

D3DXHANDLE hTech;
anEffect->FindNextValidTechnique(NULL,&hTech);

After this call hTech will be a handle to a technique that works for this hardware or NULL if no suitable technique was found.

Simply arrange your effect file so the best techniques are first and the worst (but backward compatible techniques) are later. This way the best technique for the hardware will be found.

Rendering With Effect Files

To render using a technique in an effect file is quite easy. The only tricky part is that you need to determine the number of passes used by the chosen technique. E.g.

if (SUCCEEDED(anEffect->SetTechnique(hTech)))
{
   UINT numPasses;
   nEffect->Begin(&passes,0);
   ...

SetTechnique specifies which technique you want to render with (use the handle you obtained earlier). You then call Begin where by the number of passes required by the technique is returned. You then loop:

for (UINT i=0;i<numPasses;i++)
{
    anEffect->BeginPass(i); // Set the pass
    // render geometry e.g. DrawIndexedPrimitive

        anEffect->EndPass();
}

Once finished:

anEffect->End();

Setting Parameters

The parameters we defined at the top of our effect file can be set from our application via the variables unique name id. e.g.

anEffect->SetTexture("t0",texture1);
anEffect->SetMatrix("world",&worldMatrix);
anEffect->SetMatrix("camera",&viewMatrix);
anEffect->SetVector("var1", v);

Advanced note: you can specify that a parameter is to be shared within the effect pool. This can be very useful as you can set the parameter once and it will be set for all effects.

Summary

Effect files are very useful in separating the graphics engine from the art effects. This means that effects can be edited by programmers or artists outside of the hard coded graphics engine. Many techniques can be defined per effect to support hardware with different capabilities and the best technique chosen at run time.

Further Reading

As yet there is not an awful lot of material on effect files available apart from that supplied by Microsoft. So your first place to look is the DirectX help file. Also worth trying is the EffectEdit utility program (via the Sample Browser) it lets you edit effect files and see the changes in real time.

Effects can also be included in .x files and so I hope in future to add notes on this to the .x files pages. Shaders are discussed in the Shaders section. It includes notes on getting started writing vertex and pixel shaders.



© 2004-2016 Keith Ditchburn