Objective

The objective of this short tutorial is to show how to activate shaders for a particular player, allowing to change how they see the world without affecting the rest of players.

Requirements

The latest version of NWNX (https://github.com/nwnxee/unified). The following functions: NWNX_Player_UpdateWind() and NWNX_Area_GetWindPower() will be used.

Limitations

This tutorial supposes you are not using the `SetAreaWind` function of the base game:

// Sets the detailed wind data for oArea
// The predefined values in the toolset are:
//   NONE:  vDirection=(1.0, 1.0, 0.0), fMagnitude=0.0, fYaw=0.0,   fPitch=0.0
//   LIGHT: vDirection=(1.0, 1.0, 0.0), fMagnitude=1.0, fYaw=100.0, fPitch=3.0
//   HEAVY: vDirection=(1.0, 1.0, 0.0), fMagnitude=2.0, fYaw=150.0, fPitch=5.0
void SetAreaWind(object oArea, vector vDirection, float fMagnitude, float fYaw, float fPitch);

That is, the wind in your module areas is set by the toolset and not by any script. If this is not your case you'll have to change the code of this tutorial and adapt it to your needs.

The code presented here does not allow the application of different shader effects at the same time (but you can easily change it to do so).

How it works

The shaders have access to some uniforms (see shaders for more info on shaders and uniforms) that are set by the game: the area flags, the world time... and the one we will use in this tutorial areaGlobalWind which contains the global wind vector for the area.

¿Why this uniform? because this uniform can be changed in a per player basis, so we can change the value of areaGlobalWind for only one player. There are sureley other uniforms that can be used but, as we will see, this one allows an easy way for the shader to detect we want to apply a specific effect without affecting, at least noticeably, the wind effects in the area.

If you haven't changed the wind in any area of your module using the SetAreaWind() function, the wind will be configured by the toolset. From SetAreaWind() help, this means we'll have only three possible states for vDirection and fMagnitude (if you know maths you'll see the fMagnitude is not what we think it is, because the length of vDirection is not 1):

  • vDirection=(1.0, 1.0, 0.0), fMagnitude=0.0: the squared modulus of the areaGlobalWind vector will be 0
  • vDirection=(1.0, 1.0, 0.0), fMagnitude=1.0: the squared modulus of the areaGlobalWind vector will be 2
  • vDirection=(1.0, 1.0, 0.0), fMagnitude=2.0: the squared modulus of the areaGlobalWind vector will be 8

What we are going to do is to change slightly those numbers (slightly, in order to not have any noticeable/visible effect on the wind of the area). Since the modulus of areaGlobalWind is a float, an easy way to do this is to add a fractional part to the squared modulus: the shader will check the fractional part and apply an effect depending on its value. For example if the fractional part is 0.01 we can remove the colors, if it's 0.02 we can inverse the colors to get an ethereal effect, etc. Note that a change of, let say, 0.08 in the squared modulus of the wind vector will not be noticeable for the player.

Shader code

For the sake of brevity I'm showing the code for the `fslit.shd` shader, you'll have to also modify fslit_nm.shd, fslit_sm.shd, fslit_sm_nm.shd, fswater.shd and fsgrass.shd. As an alternative you can modify only fsfbpostpr.shd but be carefull because this will affect all the textures: traps will not be red if you change the colors.

fslit.shd
//=============================================================================
//
// fslit.shd
//
//=============================================================================

#define SHADER_TYPE 2

#define FOG 1
#define KEYHOLING 1
#define LIGHTING 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 0

#include "inc_standard"

// Per player shaders code begins here
//====================================
uniform vec3 areaGlobalWind; // This uniform is already defined in the water shader, don't declare it again!
#define SM_WINDMAG_ETHER 0.01
#define SM_WINDMAG_ULTRAVISION 0.02

//To avoid float round off errors
#define SM_WINDMAG_PRECISION 0.005

// Per player shaders code ends here
//====================================

void main ()
{
    FragmentColor = vec4(1.0);
    SetupStandardShaderInputs();
    ApplyStandardShader();
    
    // Per player shaders code begins here
    //====================================
    float fWindMagnitud = dot(areaGlobalWind,areaGlobalWind); // Squared modulus
    fWindMagnitud = fract(fWindMagnitud); // fractional part
    
    // Now apply the shader if needed
    if(fWindMagnitud>SM_WINDMAG_ETHER-SM_WINDMAG_PRECISION && fWindMagnitud<SM_WINDMAG_ETHER+SM_WINDMAG_PRECISION)
    {
        vec3 vColor = FragmentColor.xyz;
        vColor = 1.0 - vColor;
        FragmentColor.xyz = clamp(vColor,0.0,1.0); // Just in case, maybe not needed
    }
    else if(fWindMagnitud>SM_WINDMAG_ULTRAVISION-SM_WINDMAG_PRECISION && fWindMagnitud<SM_WINDMAG_ULTRAVISION+SM_WINDMAG_PRECISION)
    {
        vec3 vColor = FragmentColor.xyz;
        vColor = vec3((vColor.r*0.2126) + (vColor.g*0.587) + (vColor.b*0.114));
        vColor *= 2.0;
        FragmentColor.xyz = clamp(vColor,0.0,1.0); // Just in case, maybe not needed
    }
    // Per player shaders code ends here
    //====================================
    
    gl_FragColor = FragmentColor;
}


NWSCRIPT code

Add the following code to an include file:

inc_windshad.nss
#include "nwnx_area"

const float SM_WIND_SHADER_NONE        = 0.00;
const float SM_WIND_SHADER_ETHEREAL    = 0.01;
const float SM_WIND_SHADER_ULTRAVISION = 0.02;

void SetWindShader(object oPlayer, float fWindShader=SM_WIND_SHADER_NONE);

void SetWindShader(object oPlayer, float fWindShader=SM_WIND_SHADER_NONE)
{
    object oArea = GetArea(oPlayer);

    // I'm using the default game vector, a normalized vector (length 1) would be "easier", but I have not tested it
    vector vDirection = Vector(1.0, 1.0, 0.0);

    int nWindPower = NWNX_Area_GetWindPower(oArea);

    float fYaw, fPitch;
    switch(nWindPower)
    {
        case 0:
            fYaw = 0.0;
            fPitch = 0.0;
            break;
        case 1:
            fYaw = 100.0;
            fPitch = 3.0;
            break;
        case 2:
            fYaw = 150.0;
            fPitch = 5.0;
            break;
    }

    // We will work with the squared modulus to avoid the use of sqrt() operation in the shader
    float fSqModulus = IntToFloat(2*nWindPower*nWindPower);

    //We want the shader to see fSqModulus + fWindShader
    fSqModulus+=fWindShader;

    // From the desired result, calculate the magnitude we will use for the NWNX_Player_UpdateWind function
    float fMagnitude = fSqModulus/2.0; // 2.0 is the squared modulus of vDirection
    fMagnitude = sqrt(fMagnitude);

    // Apply it to the player     
    NWNX_Player_UpdateWind(oPlayer, vDirection, fMagnitude, fYaw, fPitch);
}

So, if for example you want to change the darkvision spell, add the include to the NW_S0_DarkVis.nss file and call SetWindShader(oTarget, SM_WIND_SHADER_ULTRAVISION) when applying the spell effects.

Note that:

  • You'll need a way to detect the end of the spell to restore the normal shader (call SetWindShader(oPlayer, SM_WIND_SHADER_NONE))
  • Since the shader depends on the wind of an area, you'll need a way to re-apply the shader when the player moves to another area

Although this can be done easily but it is out of the scope of this tutorial.

  • No labels