Shaders are SHD files loaded by the NWN:EE engine to perform graphical changes to textures. There are vertex (per-vertex) and fragment (per-pixel) shaders that are run by the game.

Using shaders you can alter the graphical presentation of textures and models.

Basic Shader Overview

Shaders are text files loaded by the engine and compiled onto the GPU for changing how textures and models look at run time.

There's really just a few real separate shaders. The vs and fs prefixed files just have key flags that get turned on and off to make the shaders do different things, like yes or no use normal, spec, roughness, etc. In general the VS ones get your vertex properties all set up. The FS ones basically paint your screen pixels using the information gleaned from the nearest verts. And then there's a post-processing layer that does your stuff like sharpen and screen depth type stuff. And finally there's your shadow and beaming projectors. 

For the most part the base game doesn't offer a set of special shaders to change too much at all except the water shader.

If you want to add something like an alpha override instead of just putting the alpha in the diffuse alpha layer like you should, then you can pick a texture # between 6 and 10 and add that texture in there. Then write a super simple shader mod to just get your alpha from that B&W map instead of the diffuse.

How Does NWN Load a Shader

There are several entry points for shaders:

  • To load a custom shader for custom content you need to add a customshaderVS and customshaderFS line in the MTR file for that specific texture. There used to be an old way to do this in TXI files as well (but don't use TXI files any more!)
    • You should be able to do this for most textures in the game. Exceptions may be things like GUI textures which are a bit odd (they replace a given texture reference internally) but it is worth experimenting. Skies for instance do not have a specific shader file but the MTR option works fine for them.
  • To use some shader things like Standard material inputs then only specifying a renderhint is required
  • The game has several default ones loaded - some are only loaded when certain Game Options are selected such as "Depth of Field" or "Sharpen". Others are loaded as general stock ones for different models.

See below for what stock shader files there are and where they are used.

Generally you don't want to override the games default shaders; need to edit in here when tested since it might only be possible to do at game launch.

Stock Shader Files

Shader files are generally in the base_shaders.bif file - extract this most easily with NWN Explorer. Up until relatively recently the shader files may have undergone significant changes and were included in the /ovr folder in the game installation directory. These were folded back into the bifs later.

From looking at them there are not many comments and no easy to follow examples for new modders. You probably want to learn about shaders - an old tutorial where some of this is from is here and hopefully a few tutorials will be added to this wiki in time.

The game ships with a set of stock shaders. Many of these emulate behaviour of the old 1.69 NWN engine. First step in modifying one of the stock shaders is figuring out which one is used for the model in question. The shaders mostly follow a naming scheme based on what they take as input:

  • lit means the shader uses dynamic lighting
  • _sm means the object is sphere-mapped,
  • _cm means the object is cube-mapped
  • t means the shader has texture coordinates
  • c means the shader is given a specific color by the engine
  • v means the (vertex) shader gets the vertex coordinates
  • _ls means the shader is using long-distance fog
  • _sk means the shader is using the m_bones skeleton
  • fb tends to be post-processing game options

A list of standard shaders included in the game files and what they're used on - when someone gets around to fully testing.

Shader FilenameTypeDescriptive NameIncludesUsage in GameNotes
fs.shdFragment Shader
inc_standard
Looks to the the standard fragment shader when no texture, lighting or PBR maps are applied.
fs_beamvolFragment Shader
None
Related to beaming (light ray effects on tiles).
fs_cg_btn_colFragment Shader
inc_standard
No comments. cg_btn_col is the button to select "colour" - PLT colours for the player - at chargen. So something to do with that potentially.
fs_invalidFragment ShaderInvalidNone
Provides the RGB colour 1.0, 0.0, 1.0 (alpha 1.0) to the output. This is bright pink.
fs_pltgenFragment ShaderPLT GenerationNone

No comments. PLT file related, from Merricksdad:

The shader code is only 4 lines long.

The first line gets the black and white color for the uv coordinate. 

The second line replaces the green channel with the correct row from the provided color scheme data. This converts the black and white data to new UV coords into the palette file

The third line just fetches the final color from the palette file.

 The last line just finishes setting the color for the fragment. 

fs_shadowplaneFragment ShaderShadow Planeinc_standard
No comments. Presumably shadow related.
fs_shadowvolFragment ShaderShadow VolumeNone
"This is just for debugging output." presumably for debugging shadow volumes
fscFragment ShaderColourinc_standard
Fragment colour matches the vertex colour, no PBR and no Textures - shadows possibly use this.
fsc_lsFragment ShaderColour, Long Distance FogNone

Fragment shader using color input with long-distance fog. The long-distance fog is useful for shadows: it needs to fade out unless it's on the top portion of the screen.

fsc_smFragment ShaderColour, Sphere MappedNone

fsfbdofFragment ShaderDepth of FieldNone
Depth of Field shader. Depth of field is generally used for screenshots only since it makes everything but the camera focus point blurry.
fsfbgamFragment ShaderGammaNone
Sets the gamma. The gamma option in-game defaults to 2.2
fsfblvlsFragment ShaderDynamic contrast shaderNone
Adds contrast to the current scene with a dynamic midpoint based on area lighting.
fsfbpostprFragment ShaderPost Processorinc_postpr
See include file "inc_postpr". Does this in fact do all the post-processing shader effects and the other files are legacy? or do the other files do it and this is legacy? or a mix?
fsfbshrpFragment ShaderSharpenNone
Sharpens the image based on differences in luma.
fsfbssaoFragment ShaderSSAONone

Screen Space Ambient Radiosity, Highlight and Occlusion for NWN:EE. Mimics the ambient light radiation and occlusion that would realistically affect the appearance of objects in a scene, adding to the sense of depth and detail.

fsfbvibFragment ShaderVibranceNone
Seems to find the min and max colour and saturates it by the difference. Unknown if used in game.
fsgluFragment Shader
inc_standard

fsgrassFragment ShaderGrassinc_standard
Presumably the shader for grass
fslitFragment ShaderLightinginc_standard
No textures, but has fog, lighting and keyholing, so general lighting shader?
fslit_nm




fslit_sm




fslit_sm_nm




fsparticle



Main entrypoint for emitter particle planes. Does not handle chunk emitters.
fst



Main entrypoint for many older GUI panels, as well as the skybox. Does not include debug GUI. NewUI not tested.
fstc




fstc_sm




fst_sm




fswater



Main entrypoint for fancy water applied via TXI. Can also be called directly to make custom fancy waters, but will not work exactly the same.
fs_beamvol




fs_cg_btn_col




fs_invalid




fs_pltgen




fs_shadowplane




fs_shadowvol




inc_common



Includes the most common functions and sets up variables used by all shader subtypes. Also preps communication with textures and negotiates flags that were no explicitly set. Calls inc_config.
inc_config



Sets up some specific shader flags, such as which shader quality to use, and some stuff about lighting. Called by inc_common.
inc_keyhole



Sets up keyholing functions. Called by inc_standard.
inc_lighting



Includes functions for rendering light from point sources and from certain types of self-illumination maps. Calls inc_material. Called by inc_standard.
inc_material



Includes functions for deriving numeric values from textures. Handles things like specular and roughness mapping, as well as texturespace occlusion based on height mapping. Calls inc_lighting. Called by
inc_postpr



Includes the main hub file for all of the post processing functions. If options are set to run those functions, it will include the other postpr includes and run their functions.
inc_postpr_dof



Includes functions to draw depth of field effects.
inc_postpr_dyn_c



Includes function for screenspace dynamic contrast correction.
inc_postpr_gam



Includes functions for screenspace gamma correction.
inc_postpr_shrp



Includes functions for screenspace sharpening.
inc_postpr_vibr



Includes functions for screenspace vibrance enhancement.
inc_standard



Includes functions to run the standard shader. Calls inc_lighting and inc_keyhole. Called by shader entrypoint files like fs_lit and vs_lit.
inc_water



Includes functions to produce fancy water. Includes code for handling things like wind sources and using the built-in noise texture. Called by fs_water.
vsfbdof




vsfbgam




vsfblvls




vsfbpostpr




vsfbshrp




vsfbssao




vsfbvib




vsglu




vsgrass




vslit




vslitc




vslitc_nm




vslitc_sm




vslitc_sm_nm




vslitnotex




vslitnt_sm




vslit_nm




vslit_sk




vslit_sk_nm




vslit_sm




vslit_sm_nm




vsparticle



Main entrypoint for emitter particle planes. Does not handle chunk emitters.
vsv




vsvc




vsvt



Main entrypoint for many older GUI panels, as well as the skybox. Does not include debug GUI. NewUI not tested.
vsvtc




vsvt_sm




vswater



Main entrypoint for fancy water applied via TXI.
vswaterc




vs_beamvol



Includes code for projecting beaming volumes.
vs_invalid




vs_pltgen




vs_shadowplane




vs_shadowvol



Includes code for projecting shadow volumes. Not to be confused with texturespace or screenspace shadow-ing from occlusion.

If you're not sure which stock shader is used for a given model, you can:

a) Load a GL debugger/tracer and check glUseProgram() calls

b) Modify each FS to show a different solid color, then check the color of the model, eg:

void main() 
{
	gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}

Define / Default Values List

This is a list of defines. You can use them in if statements:

#if NAME == VALUE

#endif

To block out sections of code based on these. The main one to note here that isn't used in the games default shaders may be BUILD_REVISION which is 34, 35, 36 etc. and useful for making shaders compatible with older game versions.

Since these are clientside fields you can retrieve some of them via. script commands but not necessarily all of them.

The Version Added field only is filled in if the value wasn't available to begin with when shaders were first added.

Define NameExampleVersion AddedNotes

#version 300 es

precision highp float;



If mobile this will be defined (open GL version etc.)

#version 330 core

#define mediump

#define lowp

#define highp



If not mobile this will be defined (open GL version etc.)

#define MAX_NUM_LIGHTS

3
Same as the client setting for Maximum Number of Lights

#define MAX_NUM_BONES

64
This can vary across some versions but now is standardised to 64.

#define GAMMA_CORRECTION

1 or 0
Gamma Correction game setting on/off

#define FRAGMENT_LIGHTING

1 or 0
Fragment Lighting game setting on/off

#define SHADER_QUALITY_MODE

0, 1 or 2
Shader quality mode game setting (low, medium, high)

#define KEYHOLING_ENABLED

1 or 0
Keyholing game setting on/off

#define SHADER_DEBUG_MODE

1 or 0
Shader debug mode on/off

#define BUILD_VERSION

8193

This is very unlikely to change for MP servers - since the server version must match the client version - but can perhaps matter for SP modules if they want to support older versions.

You can still safely use this with the BUILD_REVISION to get a more accurate picture.

#define BUILD_REVISION

35
This is the incremental patch number at the end of the patch version. Omits the last part, eg: 1.87.8193.35.5 won't show the 5, just be "35"

#define NO_DISCARD

1 or 0
Discard enabled or not

#define POSTPROCESSING_TYPES_ENABLED

Bitmask value

See inc_postpr.shd for the bitmask values

#define POSTPROCESSING_SHARPEN                  0x0001
#define POSTPROCESSING_DOF                      0x0002
#define POSTPROCESSING_DYNAMIC_CONTRAST         0x0004
#define POSTPROCESSING_VIBRANCE                 0x0008
#define POSTPROCESSING_GAMMA                    0x0010
#define POSTPROCESSING_TOON                     0x0020

#define varying

out or in
out = is the for types GL_VERTEX_SHADER and in = not vertex shader

#define attribute

in

#define texture2D

texture

#define textureCube

texture

#define gl_FragColor compat_glFragColor

out vec4 compat_glFragColor;



Only added if a fragment shader

SHD Format

Shaders are relatively standard text files, written in c-like code.

Generally each shader you'll want to use for model texture changes will include "inc_standard". They also will use #define to setup what the shader file will do. For instance fslit_nm has the normal, specular, roughness height and self-illumination maps set (the "nm" means "normal maps"):

//=============================================================================
//
// fslit_nm.shd
//
//=============================================================================

#define SHADER_TYPE 2

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

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

#include "inc_standard"

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


Editing Parameters Dynamically via. nwscript

There is actually a way to edit some shaders - when applied to a specific texture on a specific object - via. nwscript. The object in question must be able to be referenced by, say, tag, or OBJECT_SELF - you can't apply this to specific tiles in a area's tileset for instance, or to set a general change on every general shader.

This can be used to quickly test some parameters though. For example this could be your MTR file, in this case called c_ettercap.mtr - overriding the c_ettercap.mdl main texture file.

customshadervs vslit_nm
customshaderfs fslit_nm
renderhint NormalTangents
// Textures
texture0 c_ettercap_d
texture1 c_ettercap_n
parameter float Specularity 0.02
parameter float Roughness 0.1

In this case we specify the diffuse, normal maps and force on the specular and roughness as uniform values - we don't need to supply a texture file.

This is limited of course to applying the same value across the entire model but is great for testing. In game you can run this code in the debug window when selecting the Ettercap to run it on:

float fSpecularity = 0.02;
float fRoughness = 0.05;
SetMaterialShaderUniformVec4(OBJECT_SELF, "c_ettercap", "Specularity", fSpecularity);
SetMaterialShaderUniformVec4(OBJECT_SELF, "c_ettercap", "Roughness", fRoughness);

You can also do this for the "metallicness" value but this tends to be auto-generated. This provides some instant results, eg:

Some examples:

SpecularRoughnessPictureNotes
0.30.05

Generally a bit "wet" but not quite good enough
1.00.00001

A metallic mess (note setting Roughness to 0.0 seems to recalculate it as if it wasn't set)

Using One Shader in Multiple Materials But With Variance

By adding the following line to your shader file ...

uniform float fMyNewParameter;

...and by adding the following line to your material file ...

parameter float fMyNewParameter 5.0

You can pass the named parameter to the shader. If you then write into your shader for something to happen based on that parameter, you can use the same shader with multiple material files, and have the material file modify how the shader functions.

As an example, let's say you have a texture slide shader, but you want to modify the slide speed. You can supply parameter float fSlideSpeedMod 5.0 in your material and then apply the value in uniform fSlideSpeedMod to the slide speed calculation.

This method does not require nwscript, so would work on materials applied to things you cannot target with nwscript, like tile meshes.

Warning: if you supply a parameter name matching an already used uniform name, you will likely crash the shader to hot pink.

How to Use Vector Parameters

Using vector parameters is very simple to do, but has some inconsistencies you need to watch.

If you want to set a default value to your vector parameter, you need code like this. First add your vector parameter to the shader file like this...

uniform vec4 vMyNewParameter;

... and then add code like this to your material file.

parameter float vMyNewParameter 0.0 1.0 0.0 0.0

Notice the parameter is defined as float, not vec4. You're passing an array of floats into the engine which will be converted to a vec4 object for use in the shader. The values correspond to XYZW or RGBA coordinates depending on if the vector will be used as a direction or a color.

Using Per-Player Global Shader Uniforms

As of preview v.35 [869dfc51] there are 48 new shader uniforms which can be set via NwScript in a per-player manner.

There are sixteen instances of three types of uniform.

Uniform NameType
scriptableInt<n>Signed 32-bit Integer
scriptableFloat<n>Signed 32-bit Single Precision Float
scriptableVec<n>Four-part Signed 32-bit Float Vector

In each variable name <n> is equal to a number 1 to 16 and corresponds to NwScript constants SHADER_UNIFORM_*

To set any of the uniforms in NwScript, use the corresponding function:

  • void SetShaderUniformFloat(object oPlayer, int nShader, float fValue)
  • void SetShaderUniformInt(object oPlayer, int nShader, int nValue)
  • void SetShaderUniformVec(object oPlayer, int nShader, float fX, float fY, float fZ, float fW)

In each function oPlayer is the player who will set their global uniform value.

The value nShader accepts the constants SHADER_UNIFORM_* with indices 1 to 16, which correspond to the values 0 to 15;

The value of fValue in each function must be the correct type, integer or float respectively.

To pass a vector value, fill out ALL parts, even if not needed. For example a vec2 value will only use the fX ad fY, but you will supply 0.0 for fZ and fW as they were not given default values of 0.0.

To capture the data in your custom shader, you will either need to specifically define the names you need, for example "uniform float scriptableFloat1" if you are using just that one global uniform.

Or, you can easily capture all available scriptable uniforms by including the new include file, inc_scriptable. Add this line to the top of your shader under other includes:

#include "inc_scriptable"

Note that players reconnecting after already having these uniforms set will need to have them reapplied.

What to do if you run out of global floats

Remember that each vec4 has 4 more floats, so you can pass additional information beyond your float quota via those vectors.

Simply pass each additional float as part of the vec4 structure, and then unpack them in the shader script.

Getting more work out of global ints

An int data structure can hold 32 bits of information. Each bit is the equivalent of a power of 2.

For example bit 0 is 2^0, accepting 1 or 0.

You can therefore store a bitflag system in any int. Just keep in mind that these are signed integers, bit range will be 0 to 30, and the last flag will hold your negative sign.

To pack bitflags from NwScript, set your global integer to a value equal to:

int nValue = bitFlag0;
nValue += bitFlag1 * 2;
nValue += bitFlag2 * 2^2;
nValue += bitFlag3 * 2^3;
nValue += bitFlag4 * 2^4;
...
nValue += bitFlag30 * 2^30;

To unpack bitflags in the shader, simply enumerate flags as powers of 2:

#define MY_BITFLAG0 0x00000001
#define MY_BITFLAG1 0x00000002
#define MY_BITFLAG2 0x00000004
...
#define MY_BITFLAG30 0x40000000

Each number slot will go 1, 2, 4, 8, 0. At zero, the next slot in front will become 1. Repeat as needed until you get to 40000000 for bit 31.

Because boolean operations are not implemented yet in GLSL, we must now add some special functions to get back our info. You could add these as an include file called "inc_boolean" and then add it to your custom shader using #include "inc_boolean".

//get nA mod nB
int modi(int nA, int nB) {
    return nA - nB * (nA / nB);
}

//check nA or nB
int or(int nA, int nB) {
    int result = 0;
    int n = 1;

    for(int i = 0; i < 32; i++) {
        if ((modi(nA, 2) == 1) || (modi(nB, 2) == 1)) {
            result += n;
        }
        nA = nA / 2;
        nB = nB / 2;
        n = n * 2;
        if(!(nA > 0 || nB > 0)) {
            break;
        }
    }
    return result;
}

//check nA and nB
int and(int nA, int nB) {
    int result = 0;
    int n = 1;

    for(int i = 0; i < 32; i++) {
        if ((modi(nA, 2) == 1) && (modi(nB, 2) == 1)) {
            result += n;
        }

        nA = nA / 2;
        nB = nB / 2;
        n = n * 2;

        if(!(nA > 0 && nB > 0)) {
            break;
        }
    }
    return result;
}

//check not nA
int not(int nA) {
    int result = 0;
    int n = 1;
    
    for(int i = 0; i < 32; i++) {
        if (modi(nA, 2) == 0) {
            result += n;    
        }
        nA = nA / 2;
        n = n * 2;
    }
    return result;
}

//check if just one flag is set
bool GetIsFlagSet(int nA, int nB){
	return and(nA,nB)==nB;

}

And then you can ask your single global int questions using:

//check if flag 2 is set
if ( GetIsFlagSet(scriptableInt1, MY_BITFLAG2) ){
	//do something here
}

//check if both flag 1 and 2 are set
if ( GetIsFlagSet(scriptableInt1, (MY_BITFLAG1 + MY_BITFLAG2)) ){
	//do something here
}

//check if either flag 1 or flag 2 is set
if ( GetIsFlagSet(scriptableInt1, MY_BITFLAG1) || GetIsFlagSet(scriptableInt1, MY_BITFLAG2)){
	//do something here
}

Voilà

  • No labels