Native OSP Shaders Warband HLSL Shader Exchange

Users who are viewing this thread

?Flashlights and Spotlight Lamp Support?

giphy.gif


This is NOT a performance light pun unintended addition. In fact, it's quite heavy if you intend to use it on the pixel shader level for normal map support.


This is using matView[0] instead of matView[2], and the normals are clearly flipped with normal maps, but bugtesting to correct it has been weird.
Register this function, where you feed it the distance between the camera and the vertex or pixel, and the normal of the surface you're casting to.
C:
float3 flashlight(float distance, float3 viewDir, float3 worldNormal, float falloff = 1)
{
 
    float atten = saturate(falloff / distance); // Determine Light Attenuation (falloff by distance)
    //float3 camDir = matView[0];                    // This matrix holds the base camera direction inside the index 0.
    //camDir.x *= -1;                                // Flip the Y and swap x and y to rotate it 90 degrees i.e. camDir.yxz
 
    float3 camDir = matView[2];                    // This matrix holds the transformed camera direction inside the index 2, matching your actual facing.
 
    float lightCone = saturate(dot(normalize(viewDir), normalize(camDir.xyz)));        // Basically does a cone based on Camera's facing
    lightCone = pow(lightCone, 15);                                                    // This tightens the cone angle
    float3 flashlight = saturate(saturate(dot(camDir, worldNormal))) * atten * lightCone;    // You could tint the light by multiplying it by a color here
 
    return flashlight;
}
And an example of the implementation
C:
VS_OUTPUT vs_main(uniform const int PcfMode, uniform const bool UseSecondLight, float4 vPosition : POSITION, float3 vNormal : NORMAL, float2 tc : TEXCOORD0, float4 vColor : COLOR0, float4 vLightColor : COLOR1)
{
    INITIALIZE_OUTPUT(VS_OUTPUT, Out);

    Out.Pos = mul(matWorldViewProj, vPosition);

    float4 vWorldPos = (float4)mul(matWorld,vPosition);
    float3 vWorldN = normalize(mul((float3x3)matWorld, vNormal)); //normal in world space

    Out.Tex0 = tc;
    float NdotL = dot(-vSunDir, vWorldN);
    float4 diffuse_light = (vAmbientColor + vGroundAmbientColor /*+ ((vSkyLightColor) * smoothstep(0.5, 0.52, NdotL))*/);
 
    Out.ViewDir = normalize(vCameraPos.xyz - vWorldPos.xyz);
    Out.WorldNormal = vWorldN;
    Out.WorldPos = vWorldPos;
 
    //apply fog
    float3 P = mul(matWorldView, vPosition); //position in view space
    float d = length(P);
 
    if(bNightVision == 1)
        diffuse_light.rgb += flashlight(d, Out.ViewDir, Out.WorldNormal);
 
    Out.Color = (vMaterialColor * vColor * diffuse_light);

    //shadow mapping variables
    float wNdotSun = saturate(dot(vWorldN, -vSunDir));
    Out.SunLight = (wNdotSun) * vSunColor * vMaterialColor * vColor;
    if (PcfMode != PCF_NONE)
    {
        float4 ShadowPos = mul(matSunViewProj, vWorldPos);
        Out.ShadowTexCoord = ShadowPos;
        Out.ShadowTexCoord.z /= ShadowPos.w;
        Out.ShadowTexCoord.w = 1.0f;
        Out.ShadowTexelPos = Out.ShadowTexCoord * fShadowMapSize;
        //shadow mapping variables end
    }
 
    Out.Fog = get_fog_amount_new(d, vWorldPos.z);
    return Out;
}
with
Python:
(0, 0, 0, [(key_clicked, key_k),],
[
    (try_begin),
        (eq, "$NV_on", 1),
        (assign, "$NV_on", 0),
    (else_try),
        (assign, "$NV_on", 1),
    (try_end),
    (set_shader_param_int, "@bNightVision", "$NV_on"),
]),
turning it off and off.

and now, Spotlights

This requires compilation with ps_3_0 as it use (n*6) + 1, n is lamp count const registers and ps_2 only allows 31 while ps_3 allows 256. This should be fine so long as your target user's PC isn't old enough to gamble in the United States.
Scene Prop Entry
Python:
("spotlight_position", sokf_invisible, "scene_edit_spotlight", "0", [
    (ti_on_init_scene_prop,
    [
        (set_fixed_point_multiplier, 1000),
   
        (store_trigger_param_1, ":instance_no"),
   
        (prop_instance_get_variation_id, ":variance_id", ":instance_no"),
        (prop_instance_get_variation_id_2, ":variance_id2", ":instance_no"),
        (prop_instance_get_position, pos2, ":instance_no"),
        (prop_instance_get_scale, pos3, ":instance_no"),
   
        (position_get_x, reg15, pos2),
        (position_get_y, reg16, pos2),
        (position_get_z, reg17, pos2),
   
        (position_get_rotation_around_x, reg18, pos2),
        (position_get_rotation_around_y, reg19, pos2),
        (position_get_rotation_around_z, reg20, pos2),
   
        (convert_to_fixed_point, reg18),
        (convert_to_fixed_point, reg19),
        (convert_to_fixed_point, reg20),
   
   
        (store_add, ":string", "str_spotlight_pos0", ":variance_id"),
        (str_store_string, s10, ":string"),
        (store_add, ":string", "str_spotlight_dir0", ":variance_id"),
        (str_store_string, s11, ":string"),
        (store_add, ":string", "str_spotlight_col0", ":variance_id"),
        (str_store_string, s12, ":string"),
        (store_add, ":string", "str_spotlight_fao0", ":variance_id"),
        (str_store_string, s13, ":string"),
        (store_add, ":string", "str_spotlight_con0", ":variance_id"),
        (str_store_string, s14, ":string"),
   
        (set_shader_param_float4, s10, reg15, reg16, reg17, 0),
        (set_shader_param_float4, s11, reg18, reg19, reg20, 0),
   
        (try_begin),
            (eq, ":variance_id2", 0),
            (set_shader_param_float4, s12, 1000, 1000, 1000, 1000),   # Using fpm 1000, 1000 is just 1.0, colors are out of percentages inside hlsl
            (set_shader_param_float, s13, 1000), # Using fpm 1000, 1000 is just 1.0, falloff of 1 is default
            (set_shader_param_float, s14, 15000), # Using fpm 1000, 15000 is just 15.0, 15 is a pretty narrow cone, something like 30degrees
        (else_try),
            (eq, ":variance_id2", 1),
            (set_shader_param_float4, s12, 0, 0, 1000, 1000),   # Blue lamp
            (set_shader_param_float, s13, 1000),
            (set_shader_param_float, s14, 15000),
        (else_try),
            (eq, ":variance_id2", 2),
            (set_shader_param_float4, s12, 0, 1000, 0, 1000), # Green lamp
            (set_shader_param_float, s13, 1000),
            (set_shader_param_float, s14, 15000),
        (else_try),
            (eq, ":variance_id2", 3),
            (set_shader_param_float4, s12, 1000, 0, 0, 1000),   # Red lamp
            (set_shader_param_float, s13, 1000),
            (set_shader_param_float, s14, 15000),
        (try_end),
    ]),
]),
Strings Entry
Python:
# After the close of the strings = []
strings.extend(("spotlight_pos%d" %i, "vSpotLightPos[%d]" % i)         for i in xrange(0, 11))
strings.extend(("spotlight_dir%d" %i, "vSpotLightDir[%d]" % i)         for i in xrange(0, 11))
strings.extend(("spotlight_col%d" %i, "vSpotLightColor[%d]" % i)     for i in xrange(0, 11))
strings.extend(("spotlight_fao%d" %i, "vSpotLightFalloff[%d]" % i)     for i in xrange(0, 11))
strings.extend(("spotlight_con%d" %i, "vSpotLightCone[%d]" % i)     for i in xrange(0, 11))
# Changing the last number of the range will allow you to support as many lamps as you would like +1

mission_templates (for live positional updates, you can exempt this for static lamps)
Python:
# Changing the trigger time will change how quickly movement will update in shader
(0, 0, 0, [],
[
    (assign, ":fpm", 1),
    (convert_to_fixed_point, ":fpm"),
    (scene_prop_get_num_instances, ":spots_count", "spr_spotlight_position"),
    (set_shader_param_int, "@iSpotLightCount", ":spots_count"),

    (try_for_prop_instances, ":inst", "spr_spotlight_position"),
        (set_fixed_point_multiplier, 1000),

        (prop_instance_get_variation_id, ":variance_id", ":inst"),
        (prop_instance_get_position, pos2, ":inst"),
        (prop_instance_get_scale, pos3, ":inst"),

        (store_add, ":string", "str_spotlight_pos0", ":variance_id"),
        (str_store_string, s10, ":string"),
        (store_add, ":string", "str_spotlight_dir0", ":variance_id"),
        (str_store_string, s11, ":string"),

        (position_get_x, reg15, pos2),
        (position_get_y, reg16, pos2),
        (position_get_z, reg17, pos2),

        (position_get_rotation_around_x, reg18, pos2),
        (position_get_rotation_around_y, reg19, pos2),
        (position_get_rotation_around_z, reg20, pos2),

        (convert_to_fixed_point, reg18),
        (convert_to_fixed_point, reg19),
        (convert_to_fixed_point, reg20),

        (set_shader_param_float4, s10, reg15, reg16, reg17, 0),
        (set_shader_param_float4, s11, reg18, reg19, reg20, 0),

    (try_end),
    (set_fixed_point_multiplier, ":fpm"),
]),

If you've followed along so far, you'll recognize that colors are set when the prop is initialized, so, as it, scenes will need to be reset to show color, falloff and angle changes. It would be an easy change to add, however.

Finally, let's get to those got dang functions!
C-like:
// Spot Light Support
#define NUM_SPOT_LIGHTS                    11    // Update this to your max lights
int iSpotLightCount    =    NUM_SPOT_LIGHTS;    // Total Spot Light Count
float3 vSpotLightPos[NUM_SPOT_LIGHTS];
float3 vSpotLightDir[NUM_SPOT_LIGHTS];
float4 vSpotLightColor[NUM_SPOT_LIGHTS];
float vSpotLightFalloff[NUM_SPOT_LIGHTS];
float vSpotLightCone[NUM_SPOT_LIGHTS];


float3 angleToVector(float3 angle)
{
    // Convert angle to radians
    angle.x = (angle.x) * 3.14159265 / 180;
    angle.z = (angle.z - 90) * 3.14159265 / 180;

    float sinYaw = sin(angle.z);
    float cosYaw = cos(angle.z);

    float sinPitch = sin(angle.x);
    float cosPitch = cos(angle.x);

    float3 direction;
    direction.x = cosPitch * cosYaw;
    direction.y = cosPitch * sinYaw;
    direction.z = -sinPitch;

    return direction;
}

float4 calculate_spot_lights_diffuse(const float3 vWorldPos, const float3 vWorldN)
{
 
    float4 total = 0;
    for(int j = 0; j < iSpotLightCount; j++)
    {
   
            float3 spotLightPos = vSpotLightPos[j];
       
            if(distance(spotLightPos, vCameraPos) < 30) // This is an attempt at making it more performant, I may update as I go on
            {
            float3 spotLightDir = angleToVector(vSpotLightDir[j]);
            float4 spotLightColor = vSpotLightColor[j];
            float falloff = vSpotLightFalloff[j];
            float angle = vSpotLightCone[j];
       
            float3 L = normalize(spotLightPos - vWorldPos);                            // Point Light!
       
            falloff /= distance(spotLightPos, vWorldPos) / 5;                        // Falloff!
       
            float atten = pow(saturate(dot(L, spotLightDir)), angle);                // Cone!
       
            total += saturate(dot(normalize(vWorldN), L)) * atten * falloff * spotLightColor;        // Light * Cone * Color!
            }
    }
    return total;
}

Finally! That should be all you need to get it working. Place the lamp, numerate the first variation id to the current lamp count -1, and set the second to whatever color/intensity variation you would like to have and you should have a spotlights!
 
Last edited:
Shaders (both GLSL and HLSL) are an obscure realm of modding which is underdeveloped but opens up new amazing possibilities. Since they profoundly expand the horizons of scripting, it is a pity they got a dedicated topic in the final era of Warband modding. Regardless of that fact, this thread is great; thank you all for your contribution.

Here is a nice website with OpenGL shaders to dive into: https://www.shadertoy.com/. And here is another with old HLSL, DirectX9-compatible shaders from the 2000s which are free for non-commercial use; therefore they are perfect for Warband mods: https://developer.download.nvidia.com/shaderlibrary/webpages/hlsl_shaders.html.
 
I forgot about this one! This one is a little more half-baked, but it's probably more useful than lamps.

reticle-swap-via-shader.gif
directional-attack-marker-swap-via-shader.gif

Swappable Aiming Reticle / Directional Markers
The idea behind it is, make a duplicate core_materials, core_shaders and core_meshes to override the ui gadget's base material, shader and uv maps so all three aiming reticles share a texture space, and use a global shader const to tell the shader which one is selected.

MLNOf.png

4 * 4 for the Reticles
1 * 8 for Directional Arrows

It takes some footwork, but the shader itself is pretty easy.
C-like:
// In your other Application Constants
    int bReticleOffset = 0;
    int bDirectionalOffset = 0;

// Amongst the other Font/UI Shaders
PS_OUTPUT ps_main_no_shadow_custom_ui(VS_OUTPUT_FONT_X In)
{
    PS_OUTPUT Output;
  
    float2 tile_offset = (In.Tex0.y > 0.5) ? float2((bDirectionalOffset % 8) * 0.125, 0) : float2((bReticleOffset % 4) * 0.25, floor((bReticleOffset % 16) / 4) * 0.125);
    // The above code is pretty simple, but it's sort of written weird.
    // Basically, if the vertex UV.y is below halfway, use the first formula, since it's a directional arrow, leaving UV.y intact and modifying the UV.x based on the bDirectionalOffset
    // Else, it's a reticle, so use the modulo of bReticleOffset to figure out which it's supposed to be.
  
    float4 tex_col = tex2D(MeshTextureSampler, In.Tex0 + tile_offset);
    INPUT_TEX_GAMMA(tex_col.rgb);
  
    // This half is nearly exclusively to address shortcomings in my own texture sheet, and is totally optional.

    float3 hsv = RGBtoHSV(tex_col.rgb);
  
    tex_col = (hsv.y > 0.5 && hsv.z > 0.25 && In.Tex0.y < 0.5)? In.Color : tex_col;
    tex_col.a = (tex_col.r > 0.05 || tex_col.g > 0.05 || tex_col.b > 0.01) ? tex_col.a : 0;
    // Basically I used a specific shade of blurple to mark the center of the reticle, and if that's there, change it to the reticle mesh's Vertex Color. I did this to get the R, G, and B dots inside the Native Gadgets.
  
    Output.RGBColor = (In.Tex0.y > 0.5) ? In.Color * tex_col : tex_col; // But, again, only if it's a reticle.
    OUTPUT_GAMMA(Output.RGBColor.rgb);
    return Output;
}
technique simple_shading_custom_ui //Uses gamma
{
    pass P0
    {
        VertexShader = vs_font_compiled_2_0;
        PixelShader = compile ps_2_a ps_main_no_shadow_custom_ui();
    }
}

Python:
("reticle_chooser", prsntf_manual_end_only, 0,
    [
    (ti_on_presentation_load, [
        (set_fixed_point_multiplier, 1000),
        (presentation_set_duration, 99999),
              
        (create_text_overlay, "$g_presentation_load_in_text", "@Modify Your Reticle", tf_title_text),
        (overlay_set_color, "$g_presentation_load_in_text", 0xFFFFFF),
        (init_position, pos19),
        (init_position, pos20),
        (position_set_x, pos19, 500),
        (position_set_y, pos19, 650),
        (position_set_x, pos20, 1000),
        (position_set_y, pos20, 1000),
        (overlay_set_position, "$g_presentation_load_in_text", pos19),
        (overlay_set_size, "$g_presentation_load_in_text", pos20),
      
        (position_set_x, pos19, 200),
        (position_set_y, pos19, 550),
      
        (create_text_overlay, "$g_top_reticle_text", "@Reticle Select", tf_with_outline),
        (overlay_set_color, "$g_top_reticle_text", 0xFFFFFF),
        (overlay_set_position, "$g_top_reticle_text", pos19),
      
        (position_set_x, pos19, 220),
        (position_set_y, pos19, 500),
      
        (create_number_box_overlay, "$g_reticle_choice", 0, 16),
        (overlay_set_position, "$g_reticle_choice", pos19),
      
        (position_set_x, pos19, 200),
        (position_set_y, pos19, 450),
        (create_text_overlay, "$g_attack_dir_text", "@Direction Attack Select", tf_with_outline),
        (overlay_set_color, "$g_attack_dir_text", 0xFFFFFF),
        (overlay_set_position, "$g_attack_dir_text", pos19),
      
        (position_set_x, pos19, 220),
        (position_set_y, pos19, 400),
        (create_number_box_overlay, "$g_attack_dir_choice", 0, 8),
        (overlay_set_position, "$g_attack_dir_choice", pos19),
      
        (position_set_x, pos19, 500),
        (position_set_y, pos19, 550),
      
        (create_text_overlay, "$g_left_reticle_text", "@Preview", tf_center_justify),
        (overlay_set_position, "$g_left_reticle_text", pos19),
        (position_set_y, pos19, 500),
      
        (create_mesh_overlay, "$g_reticle_preview_t", "mesh_crosshair_combined"),
        (overlay_set_position, "$g_reticle_preview_t", pos19),
  
        (create_mesh_overlay, "$g_attack_dir_preview_t", "mesh_arrow_up"),
        (position_set_y, pos19, 400),
        (overlay_set_position, "$g_attack_dir_preview_t", pos19),
      
      
      
    ]),
    (ti_on_presentation_event_state_change,
    [
    (set_fixed_point_multiplier, 1000),
    (store_trigger_param_1, ":object"),
    (store_trigger_param_2, ":value"),
  
    (try_begin),
        (eq, ":object", "$g_reticle_choice"),
        (set_shader_param_int, "@bReticleOffset", ":value"),
    (else_try),
        (eq, ":object", "$g_attack_dir_choice"),
        (set_shader_param_int, "@bDirectionalOffset", ":value"),
    (try_end),
    ]
    ),
    (ti_on_presentation_run,
     [ 
        (presentation_set_duration, 99999),
      
        (this_or_next|key_clicked, key_escape),
        (this_or_next|key_clicked, key_tilde),
        (this_or_next|key_clicked, key_k),
        (key_clicked, key_tab),

        (presentation_set_duration, 0),
      ]),
  ]),

This is all I can think of off the top of my head, when I get home I'll upload a BRF with all the modified meshes.
 
Last edited:
This is an old one, but I think it's pretty neat.

Automagic Swapping
Indoor / Outdoor Envmap
xLlJU.gif


gQNIi.png

This was a rushed blur job to clear out some detail since the example generates the coords inside the VS. Scaling it down would probably have been a better solution.

Concept: Without using any mission_template scripting or alternate materials, intelligently swap envmap reflections to better match indoor and outdoor scenes.

Execution: vSunColor is by default float4(0.0, 0.0, 0.0, 0.0) for indoor scenes, knowing that we can check if we are inside and if so shift the reflection's UV.x coordinates to match the appropriate scene.

C-like:
float3 get_envmap_coords( float3 eye, float3 normal, float sun)
{
    float3 coords = reflect(eye, normal) * 0.5 + 0.5; // I forget why I added the adjustments? It seems necessary though
    coords.x = sun != 0.0 ? coords.x * 0.5 : coords.x * 0.5 + 0.5; // read as: if sun is not 0 use left panel, else use right panel
    // Since reflect gives values between 0.0 and 1.0 we need to shrink the UV.x by half to take into account for the rectangular image
    return coords;
}

Notes: You could expand this to do an entire atlas of envmaps and intelligently swap to match various lighting conditions, but at that point you should just use a shader constant that defines which map to use. Thanks to @iJustWant2bPure for his encouragement and help with this one.
 
Last edited:
Nice little one! Envmaps are mostly for plate armours or are there other items at which they can be useful?
They're best thought of as a way to add depth and variation in the specular reflections, to keep them from being one flat tone.
This gif I made for the DaC team in January illustrates it pretty well. (Despite the reflection math being wrong here)
ezgif.com-gif-maker_3.gif

It goes from the old base shader, to just (Specular + Fresnel) * Envmap, to the combined shader now with envmap. Credits to DaC Team for such a gorgeous model to illustrate it on.
 
Back
Top Bottom