Introduction to Effect Files

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Written by Robert Dunlop
Microsoft DirectX MVP

Target Version: DirectX 9.0
may require adaptation for other versions

Effects Part 1:
Introduction to Effect Files

This is to be the first of a series of articles about the use of Effect Files in DirectX.  In this article, we'll take a look at what effect files offer and a quick primer on how to use them.

What Effect Files are about

One factor that complicates 3D titles is the need for many different visual effects for various objects and environmental conditions, and the need to support them across a variety of hardware that often require different implementations.

DirectX 8 introduced the concept of Effect Files, a file format allowing for the description of rendering techniques outside of the compiled application, which can be loaded at run-time and applied to objects during rendering.  Simply separating the rendering code in this way has a number of significant benefits:

bullet

Rendering techniques are decoupled from the engine, meaning less hard coded rendering code and greater flexibility, as new visual effects can be added without modifying application code.

bullet

An effect file can contain several implementations of a given technique to allow for various hardware, and as we will see, we can readily validate techniques against hardware to find the correct implementation to use.

bullet

 By separating the programming of visual effects from the application code, effect files can potentially become part of the art pipeline, accompanying incoming geometry and associated media with the specifics of how they are to be rendered.

What's in an Effect File?

Effect files are made up of three basic components:

  1. Parameter declarations:
    These are named values that may be set by the application prior to rendering, to be used by the functions and techniques defined in the effect file.  These are how the application will communicate values necessary for rendering, such as transformation matrices and lighting parameters.
     

  2. Techniques:
    A technique is a definition of how to render an object.  It includes all rendering and texture state settings that need to be set prior to rendering, as well as vertex and pixel shader declarations.  A technique may consist of one or more rendering passes to support multi-pass rendering, each pass containing its own state and shader definitions.
     

  3. Functions:
    Functions are written in High Level Shader Language (HLSL), a C-style language which can be used as an alternative to writing shaders in assembly language.  Functions can be compiled into vertex or pixel shaders, may be called from other functions, or used in other ways we'll see later on (for example to calculate texel values for automatic generation of procedural textures!)

A Simple Effect File

Let's take a quick look at an example of an effect file.  We'll keep it moderately simple, no shaders here, but it will provide examples of parameters and techniques as described above.

Parameter Declarations

The first part of your file has several parameter declarations, values that our application will set prior to rendering

File: blend.fx

texture t0;         // first texture to blend
texture t1;         // second texture to blend
float 4x4 world;    // world matrix
float 4x4 camera;   // view matrix

Defining Techniques

Next we will define a technique that will set the world and view transformation states, and blend the two textures specified by the application in a single pass:

technique blend_singlepass
{
   pass p0
   {
      Texture[0]=<t0>;
      ColorOp[0]=SelectArg1;
      ColorArg1[0]=Texture;
      Texture[1]=<t1>;
      ColorOp[1]=Add;
      ColorArg1[1]=Texture;
      ColorArg2[1]=Current;
      ColorOp[2]=Disable;
      WorldTransform=<world>;
      ViewTransform=<camera>;
   }
}    

As you will note, all of the states and enumerated types used here are shortened versions of the constants you are used to seeing in D3D.  For example,

ColorOp[0]=SelectArg1;

is equivalent to this operation:

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

You can find a listing of the available states as referenced in the effect file format here or in the DX9 docs under "Effect States".

To continue our effect file, let's say we want to be able to support older hardware that would require multi-pass alpha blending to blend two textures.  To allow for this, we'll add a second technique to the file, right after the first:

technique blend_multipass
{
   pass p0
   {
      AlphaBlendEnable=False;
      Texture[0]=<t0>;
      ColorOp[0]=SelectArg1;
      ColorArg1[0]=Texture;
      ColorOp[1]=Disable;
      WorldTransform=<world>;
      ViewTransform=<camera>;
   }
   pass p1
   {
      AlphaBlendEnable=True;
      SrcBlend=One;
      DestBlend=One;
      Texture[0]=<t1>;
      ColorOp[0]=SelectArg1;
      ColorArg1[0]=Texture;
      ColorOp[1]=Disable;
      WorldTransform=<world>;
      ViewTransform=<camera>;
   }
}

In this second technique, we have two pass blocks, while the previous only had one.  This is because the object will need to be rendered twice, using different settings each time.  We will see shortly how this is handled in the section "Rendering with a Technique".

Loading an Effect File

Loading the effect file can be performed using the D3DXCreateEffectFromFile() function.  In its simplest form, we can load an effect file like this:

LPD3DEFFECT pEffect;
if (FAILED(hr=D3DXCreateEffectFromFile(pDevice,"blend.fx",NULL,NULL,0,NULL,&pEffect,NULL)))
   return hr;

On success, pEffect points to an ID3DXEffect instance, from which we can access the parameters and techniques of the effect file.

Getting a Valid Technique

Once we have loaded our effect file, we can access any of its techniques by name if we wish.  However, we need to know what techniques the hardware our program is running on can support.  There are a couple of methods provided by the effect interface that can help us here: ValidateTechnique and FindNextValidTechnique.  We could use ValidateTechnique to explicitly test the available techniques, for example:

D3DXHANDLE hTech=pEffect->GetTechniqueByName("blend_singlepass");
if (FAILED(ValidateTechnique(hTech)))
   hTech=pEffect->GetTechniqueByName("blend_multipass");

However, this is a bit cumbersome as our application has to know the names of the techniques in the loaded file and check them explicitly.  A more flexible method would be to use FindNextValidTechnique(), as it does not require such explicit code and will allow for any number of techniques in the effect file:

D3DXHANDLE hTech;
if (FAILED(hr=pEffect->FindNextValidTechnique(NULL,&hTech)))
   return hr;

This method will find the first valid technique in the file.  If you arrange your techniques with your preferred technique first followed by fallback techniques, this will return the most desirable technique that will run on the current hardware.

Rendering with a Technique

Prior to rendering geometry with a given technique, you must first call the effect's SetTechnique() method with the technique handle.  Next, call the Begin() method to prepare for rendering with this technique and determine the number of passes that are required for rendering:

if (SUCCEEDED(pEffect->SetTechnique(hTech))) {
   UINT passes;
   if (SUCCEEDED(pEffect->Begin(&passes,0))) {

At this point, passes will contain the number of rendering passes required by the technique, for example 1 if blend_singlepass were used or 2 if blend_multipass were selected.

Setting Effect Parameters

Effect parameters are set using Set* methods of the effect class corresponding to different parameter types.  Each of these functions takes a D3DXHANDLE and a new value, however a string containing the name of the parameter can also be passed in place of the handle.  For example:

pEffect->SetTexture("t0",pTexture1);
pEffect->SetTexture("t1",pTexture2);
pEffect->SetMatrix("world",&worldMatrix);
pEffect->SetMatrix("camera",&viewMatrix);

This can be handy for setting effects that won't be changed regularly.  However, for parameters that are changed regularly it is preferable to get the parameter's handle when you first load the effect, then use the handle for setting the parameter:

// get handles once after loading effect
D3DXHANDLE hWorld,hCamera;
hWorld=pEffect->GetParameterByName(NULL,"world");
hCamera=pEffect->GetParameterByName(NULL,"camera");
...
// re-use handles each time parameters set
pEffect->SetMatrix(hWorld,&worldMatrix);
pEffect->SetMatrix(hCamera,&viewMatrix);

Rendering One or More Passes

To render geometry once Begin() has successfully been called, you will need to loop through the number of passes returned by Begin(), and for each pass call the Pass() function and render your geometry.  When completed, call the effect's End() method:

      for (int i=0;i<passes;i++) {
         pEffect->Pass(i);
         // render geometry here
         // e.g. DrawIndexedPrimitive(....
         //   or pMesh->DrawSubset(...
      }
   pEffect->End();
}

More ahead...

Stay tuned for more installments in this series of articles on Effects.  In the meantime, to get a real taste of effect files, run the EffectEdit sample in the DirectX 9 SDK.  There are several sample effect files provided that show a lot more features than we've covered here, and you can edit the loaded effect file while this sample is running and see the results of your changes immediately!

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.