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:
Top Bottom