Overlaying a material onto model entities

From Valve Developer Community
Revision as of 07:45, 10 September 2008 by Winston (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


This article describes a method of overlaying a transparent material onto a model entity, by drawing it on top of the model's existing material(s). It can be applied to any model entitity, and no editing of the model file or its materials is required. This makes this technique ideal for "shield belt" or "damage amplifier" effects, to borrow some examples from the Unreal Tournament series.

The first section describes a framework for controlling the effect; the reader is advised to significantly modify this section to suit their own requirements, only novice coders will require to follow it precisely.

Note: This technique currently causes minor problems with character faces on some graphics cards, due to the shaders used on them. Its not catestrophic, but it does look slightly odd.

Control framework

Deciding which class to modify

The class you choose to implement this effect in is dependent on your mod and its requirements. A deathmatch mod may happily edit the HL2MP player classes, while a single player mod may edit the Base AI classes. A mod that requires this effect to occur on both NPCs and players may wish to edit the Base Combat Character classes, as this is shared by both these groups. Regardless of the class selected, the controlling code should occur on the server class, and the actual effect code on the client class. These will be linked by networking a variable (in this case, a boolean).

For the purposes of this section of this tutorial, I will assume an HL2DM-based deathmatch mod, that wishes to modify the CHL2MP_Player and C_HL2MP_Player classes. Applying it to a different class should be trivial.

Control functions

Open hl2mp_player.h, and add the following simple functions to a public section of the player class declaration. They can be renamed as you like, they won't be referenced again in this tutorial.

void SetMaterialGlowEnabled(bool enabled) { m_bGlowEnabled = enabled; }
bool IsMaterialGlowEnabled() { return m_bGlowEnabled; }

Into a private section of the same class, add

CNetworkVar( bool, m_bGlowEnabled );

Now get the header for the client version of your class, in this case c_hl2mp_player.h, and into a private section in the player class there, add

bool m_bGlowEnabled;

It will be up to you to decide when to enable & disable the glow effect. Disabling in the player's Event_Killed function is recommended, to prevent it from remaining on across a respawn.

Networking

These two booleans represent the same value, and we need to get them to synchronise, with the server's version being authoritive. For a proper discussion of how to network variables between entities, see Networking Entities.

In hl2mp_player.cpp, find the network send table, the easiest way of doing this is to search for SendProp. Into this table, add the following (at the top, so its easiest to spot):

SendPropBool( SENDINFO( m_bGlowEnabled ),

Now in c_hl2mp_player.cpp, find the network receive table, for this, the easiest way is to search for RecvProp. Into this table (at the top again) add:

RecvPropBool( RECVINFO( m_bGlowEnabled ),

The boolean value set on the server will now always be accessible on the client, allowing us to correctly decide when to draw the effect.

Implementing the effect

Overriding DrawModel

In order to draw a new material precisely over the model, we will use ForcedMaterialOverride, which replaces every material drawn with the given parameter, until it is turned off. We will override the DrawModel function, call the base function to draw the model as normal, then turn on ForcedMaterialOverride, draw the model again using our transparent material, then turn ForcedMaterialOverride back off.

The class you are editing probably won't have a DrawModel function present (but do check!), so you'll have to create one. Note that this should be done entirely client-side. In the header, add a virtual function declaration, to override the DrawModel function of your base class. In this case, this would be in c_hl2mp_player.h, in a public section.

virtual int DrawModel(int flags);

Now in the client-side cpp file, we'll create the actual function. In c_hl2mp_player.cpp:

int C_HL2MP_Player::DrawModel(int flags)
{
   int retVal = BaseClass::DrawModel(flags);
   
   if ( m_bGlowEnabled )
   {
      modelrender->ForcedMaterialOverride( materials->FindMaterial( "glowmaterial", TEXTURE_GROUP_CLIENT_EFFECTS ) );
      BaseClass::DrawModel(STUDIO_RENDER|STUDIO_TRANSPARENCY);
      modelrender->ForcedMaterialOverride(0);
   }

   return retVal;
}

Thats all the code complete. If it doesn't compile at this point, something's gone wrong, so double check all your changes. Note that "glowmaterial" should be replaced with the name of the material you create in the next section. For a simple (non-transparent) test, try "vgui/white"

Creating the glow material

For a proper material guide, see Creating a Material. You should give your material an alpha channel, and set the RGB channels to show pure white. The alpha should be where any detail in your material is created, but the channel should be quite dull in order to allow the model's existing texture to be partially visible underneath. Perhaps surprisingly, an alpha value of 64 should be significantly intense. A suggested vmt file format is shown here. Note that the material must be set to be additive in order for the transparency to function.

"UnlitGeneric"
{
   "$basetexture" "glow_strong"
   "$vertexcolor"  "1"
   "$vertexalpha"  "1"
   "$translucent"  "1"
   "$model"        "1"
   "$additive"     "1"
}

The whole purpose of using UnlitGeneric is so that the material is rendered "glowing" - i.e. independent of the shadowing currently on the model.

Additional features

Multiple effect materials

If you wish to have multiple glow effect options, you could consider networking an integer instead of a boolean, and then in DrawModel, using a switch statement to select which material to draw, such as:

int C_HL2MP_Player::DrawModel(int flags)
{
   int retVal = BaseClass::DrawModel(flags);
   
   if ( m_iGlowEnabled > 0 )
   {
      char matName[32];
      switch ( m_iGlowEnabled )
      {
      case 1:
         matName = "glow_weak";
         break;
      case 2:
         matName = "glow_weak";
         break;
      case 3:
         matName = "glow_strong";
         break;
      }
      modelrender->ForcedMaterialOverride( materials->FindMaterial( matName, TEXTURE_GROUP_CLIENT_EFFECTS ) );
      BaseClass::DrawModel(STUDIO_RENDER|STUDIO_TRANSPARENCY);
      modelrender->ForcedMaterialOverride(0);
   }

   return retVal;
}

Coloring the effect in code

By changing render colours, a white glow material can be made to appear in any color. Ideally, the render color of the player should be left untouched, while the render color of the glow would be changed. This proved somewhat difficult, however, as the values were immediately getting stomped back to the server value for the player. It was then discovered that the simplest way was simply to change the player's render color directly, as the color of the model underneath the glow effect won't be particularly visible.

In the spawn function of your class (eg in Spawn of hl2mp_player.cpp), add

SetRenderMode(kRenderTransColor);
SetRenderColorA(255); // just to be safe

You could now change your SetGlowEnabled function to optionally accept r, g & b values (let them default to 255), and to call SetRenderColorR, SetRenderColorG & SetRenderColorB as automatically. It would then seem prudent to make it set all of these to 255 when disabling the glow effect.