In this tutorial we will be making a doggo green. He's a good doggo, just needs to be green. The files for this tutorial are zipped here: Jasperre Test Shader Colouring.zip

Concepts

We are not covering Hakpack files in this tutorial.

What do we need to do?

To change a textures base colours that the user sees we want to:

  • Have a new shader file that does the work
  • Have a MTR file that points to the new shader file
  • Have a script that alters the colours for the creature

Shader File Basics

Shaders are available in the games bif files and /ovr folder. In this case we need to extract the bif files, you can use the nim tools or NWN Explorer for it.

We have 2 types of entry points for shaders; Fragment and Vertex.

  • Vertex shaders deal with geometry edges (ie polygons)
  • Fragment shaders deal with the pixels in those polygons

We want the latter to be able to alter the colours of a texture so we grab the nearest base game shader as a starting point, in this case c_dog has no normal maps or other fancy stuff so we go with fslit_sm.shd

Make a copy of this file called fslit_sm_colour.shd

Creating Our Fragment Shader

We need to define some new modifiers to the all the fragment inputs before they get output. The current file looks like this:

//=============================================================================
//
// fslit_sm.shd
//
//=============================================================================

#define SHADER_TYPE 2


#define LIGHTING 1
#define FOG 1
#define KEYHOLING 1

#define NORMAL_MAP 0
#define SPECULAR_MAP 0
#define ROUGHNESS_MAP 0
#define HEIGHT_MAP 0
#define SELF_ILLUMINATION_MAP 0
#define ENVIRONMENT_MAP 1

#include "inc_standard"

void main ()
{
    FragmentColor = vec4(1.0);
  	SetupStandardShaderInputs();
    ApplyStandardShader();
    gl_FragColor = FragmentColor;
}

At the bottom you can see it sets the FragmentColor to vec(1.0) this equates to RGBA and thus is just "full normal colour".

To do this lets add a new parameter that can be set in the MTR file and in the game with scripting; call it vRGBA

Add this above the void main () call:

#include "inc_standard"

uniform vec4 vRGBA;

void main ()
{

We also need to use this input, add a new line in void main () to multiply our FragmentColor value by it. This may have been altered by the SetupStandardShaderInputs and ApplyStandardShader and we can change it just before it goes to screen.

void main ()
{
    FragmentColor = vec4(1.0);
  	
    SetupStandardShaderInputs();
    ApplyStandardShader();

    // Use inputs from scripts
    FragmentColor = FragmentColor * vRGBA;

    gl_FragColor = FragmentColor;
}

The final file should look like this (with an updated comment at the top).

Put this file in your override folder in your documents.

fslit_sm_colour.shd
//=============================================================================
//
// fslit_sm_colour.shd
//
//=============================================================================

#define SHADER_TYPE 2


#define LIGHTING 1
#define FOG 1
#define KEYHOLING 1

#define NORMAL_MAP 0
#define SPECULAR_MAP 0
#define ROUGHNESS_MAP 0
#define HEIGHT_MAP 0
#define SELF_ILLUMINATION_MAP 0
#define ENVIRONMENT_MAP 1

#include "inc_standard"

uniform vec4 vRGBA;

void main ()
{
    FragmentColor = vec4(1.0);
  	
    SetupStandardShaderInputs();
    ApplyStandardShader();

    // Use inputs from scripts
    FragmentColor = FragmentColor * vRGBA;

    gl_FragColor = FragmentColor;
}

Loading the Shader

Since this is a new shader file, the shader won't actually run until prompted so we need to generate a MTR file.

Finding What Texture to Edit

Since you need to tie a shader for a creature to it's texture file we need to know which one it is!

We're going to apply this to a dog so  we check what c_dog.mdl has as it's main bitmap file. You could use NWN Explorer for this. In this case it's also c_dog:

...
node trimesh Wolf_ribcage
  parent Wolf_rootdummy
  #part-number 2
  ambient 1 1 1
  diffuse 1 1 1
  specular 0.0500000007 0.0500000007 0.0500000007
  shininess 26
  bitmap c_dog
  verts 17
...

Material File

Generating a MTR is easy - it's a text file and we don't need a lot in it for this. This file will be called c_dog.mtr

All custom shaders for whatever reason does need both a fragment shader and vertex shader defined - even if the latter is a base game, and unedited. In this case the customshadervs can match the base one we chose (which doesn't load normal maps etc.), called vslit_sm

The fragment shader can point to our new file fslit_sm_colour

We also need to setup the vector parameter for the colouring, called vRGBA, and default the values. The special note here is that by specifying the keyword "parameter" we are setting the values (if none were set they default to 0.0, which can be checked for in the shader if you want to check for them being set - but for our test here we are always going to set them).

Create the file c_dog.mtr with the content below, and add it to your override folder for testing.

c_dog.mtr
customshadervs vslit_sm
customshaderfs fslit_sm_colour

parameter float vRGBA 1.0 1.0 1.0 1.0

Testing

The first test is to use a module, example forest-with-doggo: Jasperre Test Shader Colouring.zip (this also includes the MTR and SHD files) - but any doggo in a module will do.

Load the game - first test is does the game render pink suddenly. If so the shader hasn't compiled properly and the client log will contain the error. Maybe you wrote a comment without // in front of it, or reused a parameter name, etc.

If the game loads, load the module, this should just...show the normal doggo!

We can test if MTR file is loading without any scripting by altering it slightly, changing the default parameters we set, to make it say, green:

parameter float vRGBA 0.0 1.0 0.0 1.0

Finally we can test our new script inputs which override the MTR values. In the script window use this script and "Pick" the doggo to run it on themselves. The function is SetMaterialShaderUniformVec4 and uses OBJECT_SELF (the dog) the name of the texture (MTR file) to affect, the parameter to change and the values to use.

This sets it back to normal:

float fRed = 1.0;
float fGreen = 1.0;
float fBlue = 1.0;
float fAlpha = 1.0;
SetMaterialShaderUniformVec4(OBJECT_SELF, "c_dog", "vRGBA", fRed, fGreen, fBlue, fAlpha);

While this sets it to blue:

float fRed = 0.0;
float fGreen = 0.0;
float fBlue = 1.0;
float fAlpha = 1.0;
SetMaterialShaderUniformVec4(OBJECT_SELF, "c_dog", "vRGBA", fRed, fGreen, fBlue, fAlpha);

And this sets it to hot pink (the values are a multiplier afterall!):

float fRed = 5.0;
float fGreen = 1.0;
float fBlue = 5.0;
float fAlpha = 1.0;
SetMaterialShaderUniformVec4(OBJECT_SELF, "c_dog", "vRGBA", fRed, fGreen, fBlue, fAlpha);

Note that the "fAlpha" won't do much on this particular creature since the texture doesn't have any - either for see through bits, or if the envmap was enabled in appearances.2da it'd be for shiny armor.

Conclusion

The zip containing the example files and the module is here: Jasperre Test Shader Colouring.zip

This is a simple introduction to fragment shaders. With this knowledge you can:

  • Set a texture to any particular colour - even block colours (just set the FragmentColor instead of multiplying it) - which can be good for debugging sometimes.
  • Set a models parameters - you can edit ones the default shaders use, eg for secularity, roughness etc. as well (see Shaders)
  • Move onto move advanced changes to a creatures look and feel - see the games files like inc_postpr_toon.shd for the toon shader for examples of more effects.

Remember to include your new files in a Hakpack and keep them up to date with relevant patches. A future tutorial will cover combining all include files into one massive file for patch-specific shaders in case you need to provide them for multiple versions of the games.

Also we did mention this was for textures without normal maps and the like, there are 3 options to base your shaders on:

  1. fslit_sm - base texture, no environment map or normal/spec/roughness/height/etc
  2. fslit_nm - normal/spec/roughness/height/etc but no environment maps
  3. fslit_sm_nm - both normal/spec/roughness/height/etc and environment maps

Generally it's fine to base it off fslit_sm_nm since usually the things, if not present, are not used, but be wary since a rogue set of alpha or lack-thereof may make things wonky!

You can also technically replace the base game shaders, and with some careful checks make sure the values are not 0.0 for our recolouring, this means however you are going to have to keep those shaders up to date and any breakages will cause major issues across your entire module.

  • No labels