Native OSP Shaders Warband HLSL Shader Exchange

Currently viewing this thread:

Warband HLSL Shader Exchange [OSP]
HLSL is a rough language to learn, especially if you aren't familiar with C-based languages, but with enough patience and willpower it's a really rewarding and fairly underutilized area of modding.

Getting Start with Shaders:
Read First: Swyter's quick-and-dirty Shader Tutorial
Read Second: Scion's Shader Tutorial via Duh

Important Threads for anyone working on Shaders:
Shader Stuff- HLSL instruction limits: essential reading for any modder working with shaders.

Specific replies of that thread that are of interest:
Swyter's collection of 1.011 Shader Uniforms
xenoargh's guide to optimization on specific shader usage

Collection of links to previously shared HLSL code:

Wanting to find help elsewhere online?
If you are trying to find shader information on the internet, it's best to include hlsl, Dx9 and Shader Model 2 (vs_2, ps_2) in your search terms. There's been a lot of evolution in shader tech since our poor engine was finalized and it's honestly difficult to seek out relevant things. Conveniently Source Engine is a very popular engine that also is limited to the same shader models that we are.
LoZ: Breath of The Wild Inspired Toon Shader

With Rim-lighting, Specular, Two-Tone lighting
HLSL translation based on this tutorial online

    PS_OUTPUT Output;
    // Register Texture Maps and Uniforms
    float4 color = tex2D(DiffuseTextureSamplerNoWrap, In.Tex0);
    float4 specular;

        specular = tex2D(SpecularTextureSampler, In.Tex0);
        specular = float4(1,1,1,1);

    float sun_amount = tex2Dproj(ShadowmapTextureSampler, In.ShadowTexCoord).r;
    float3 normal = normalize(In.WorldNormal);
    float3 viewDir = In.ViewDir;
    float fresnel = 1-(saturate(dot(viewDir, In.WorldNormal)));

    float NdotL = dot(-vSunDir, In.WorldNormal); // Normal dot Lighting Direction
    float3 halfVector = normalize(-vSunDir + viewDir);
    float NdotH = dot(normal, halfVector); // Normal dot Half Vector
    float4 ambientColor = vAmbientColor * 3;       
    float4 shadow = smoothstep(0.75,0.76, dot(vSunDir, In.WorldNormal)) * ambientColor; // Basically makes a antisun to make a shadow
    float4 isLit = NdotL * sun_amount;

    isLit = isLit.r > 0.5 ? float4(1,1,1,1): float4(0,0,0,1); // If under 50% shade, make it completely shaded

    specular = specular * pow(abs(NdotH) * smoothstep(0.75, 0.78, abs(NdotL)), fMaterialPower);

    float specularIntensitySmooth;

        specularIntensitySmooth = smoothstep(0.85, 0.86, specular);
        specularIntensitySmooth = smoothstep(0.985, 0.990, specular);
    specular *= specularIntensitySmooth;
    fresnel = smoothstep(0.85, 0.87, fresnel);
    float4 ambientSun;
    float4 ambientSky;

    if(vSunColor.r > 0.5)
        ambientSun = vSunColor * 0.33;
        ambientSky = vSkyLightColor * 0.33;
        ambientSun = vSunColor;
        ambientSky = vSkyLightColor;

    float sunIntensity = smoothstep(0.02, 0.05, NdotL);

    float4 light = saturate(ambientColor + ambientSun + ambientSky + (sunIntensity * isLit * vSunColor));
    light.rgb -= shadow * 0.25;

    Output.RGBColor = color * In.Color;
    Output.RGBColor *= light;
    Output.RGBColor += (fresnel * vSunColor * isLit) + (specular * vSunColor * isLit);   
    return Output;
Last edited:
Awesome but how do you implement it. I tried to use it but it always fails to compile cause Idk how to add completely new shaders to mb.fx
Hey DarthKiller, check out the Getting started with shaders: header in the OP to find tutorials on how to make basic shaders.

Getting started is by far the hardest part of learning, but once you've overcome the initial hump it is easier.

Here's a quick and nasty tutorial specific to implementing these shared shaders into Warband, but don't expect it to be of any quality:
  2. Follow Swyter's tutorial on properly setting up the necessary system environment.
  3. Now that you can build a new mb.fx, at the end of the source file, set up your pixel shader. You'll need to know what Vertex Shader it'll be using at this point. I made a simple bespoke one because I didn't want lighting data passed through vertex color, but it's your preference.
  4. Third, define your Technique. This is the actual shader name. You'll need to place in both the vertex shader and pixel shader here. This also determines the name you'll want to include in your shader BRF. It will look something like
    1. C-like:
      technique fate_anime_shader
      pass P0
      VertexShader = compile vs_2_0 vs_fate_anime_shader_color();
      PixelShader = compile ps_2_a ps_fate_anime_shader_color();
  5. Compile!
  6. Now, register the shader in a BRF. I do not know what the texture access fields mean so I copy a Native shader with the appropriate maps for this. Use the i.e. if I need Diffuse, Spec, Normal, and Skinning I find the appropriate standart_ shader, copy it, use the standart_ as my fallback and change the shader name to that in my defined Technique.
    1. ofp0OMo.png
    2. In this example the shader only uses a diffuse map, so we only need the singular map.

But, in the nature of sharing, here's a goofy little experiment I conducted:
Screentone / Dot shading (wip)
via Screenspace and Diffuse2 RGB channels


Idea: recreate Manga style dot shading techniques using screenspace in the pixel shader with as little effort on my part as possible. This of course will look better the more simple the target model's texture is. I cannot recommend a texture as busy as the example

Problems and Errors: while dividing the z from ScreenSpace does make an effective Screenspace approximation, there are issues a few issues. Since this is determining the screen space of vertices, the fewer vertices a model has, the less accurate it will be close up. Also, on faces that are approaching parallel to the camera's viewpoint the screenspace approximation nearly totally falls apart.

Necessary Setup:
Screentone Texture: I used PixaFlux to assign three black and white screentone images to the R, G and B channels of a texture resulting in this.
Vertex Shader Setup: You need to pass the Screenspace to the PS via an unused texcoord
struct VS_OUTPUT_***
    // Add this to the header
    float4 ScreenSpace        : TEXCOORD3; // Any unused TexCoord will work

// Add this to the Vertex Shader proper.
Out.ScreenSpace.xy = (float2(Out.Pos.x, -Out.Pos.y) + Out.Pos.w)/2;
    Out.ScreenSpace.xy += (vDepthRT_HalfPixel_ViewportSizeInv.xy * Out.Pos.w); =;

Pixel Shader Snippet
PS_OUTPUT ps_***(VS_OUTPUT_*** /*the modified one from earlier*/ In)
    PS_OUTPUT Output;

    // Register Texture Maps and Uniforms
    float4 shading = tex2D(Diffuse2Sampler, In.ScreenSpace.xy);
    // This will apply texture coordinates based on the vertex location in screenspace!
        The rest of your pixel shader.
        At the very bottom after applying the lighting model (which is probably the most wasteful way I could have done this) My reasoning for applying lighting than removing it is I want the texture's perceived luminosity to be what affects the application of the shading, not just the lighting information.
    float luminosity = (Output.RGBColor.r * 0.299f + Output.RGBColor.g * 0.587f + Output.RGBColor.b * 0.114f);
    // According to W3C this is how humans perceive brightness. It is better than averaging the colors as it doesn't perceive greys as darker.
    Output.RGBColor = color; // I reset the color after finding the luminosity of the pixel
    // These, of course, are personal preference and highly dependent on the texture.
    // For mine green was 20% shaded, red was meant to be 50% and blue was supposedly 80%
    // But it didn't look ~right~ to me so I fiddled around
    if (luminosity < 0.15)
        Output.RGBColor.rgb *= (shading.b * shading.g);
    if (luminosity < 0.2 && luminosity > 0.15)
        Output.RGBColor.rgb *= shading.r;
    if (luminosity < 0.5 && luminosity > 0.2)
        Output.RGBColor.rgb *= shading.g;
    // You can now reapply your specular and other effects to get a more natural look.
    return Output;

Alternate uses:
Don't feel like UVing models? Use screenspace as texture coords like we did for the shading texture.
Particularly like the cartoon Chowder? Boom, Chowder styled textures.
Want a Starwars styled hologram? Use screenspace and some maths and you got yourself a scrolling hologram texture.
EDIT: Updated Screenspace calculation to match TW's native maths. It was better.
Last edited:


Master Knight
So I finally convinced myself to try the shaders and managed to copy VC windy flora shaders to base Native file without the stuff I didn't need (seasons and stuff) and it worked. It definitely isn't something I should be proud of but I'm regardless (kinda) :mrgreen:

Now that I have a base for my mod and some ideas, I'm definitely going to fiddle around with it, thanks for convincing me via this thread!
A PostFX Modification:
Boost Saturation
(without making everything technicolor)

Notice how the whites don't become vomit and more saturate colors saturate at slower rates

So, there are plenty of ways to handle this, such as this tweak that performs a fantastic desaturation effect, but I wanted a slightly different way to handle it, so:
First, add these declarations to the top of the PostFX.fx file
// I placed it just under float g_DOF_Range = ***;
// Courtesy of Ian Taylor @
// Y'all know I cannot math like this.

  float3 HUEtoRGB(in float H)
    float R = abs(H * 6 - 3) - 1;
    float G = 2 - abs(H * 6 - 2);
    float B = 2 - abs(H * 6 - 4);
    return saturate(float3(R,G,B));
  float Epsilon = 1e-10;

  float3 RGBtoHCV(in float3 RGB)
    // Based on work by Sam Hocevar and Emil Persson
    float4 P = (RGB.g < RGB.b) ? float4(, -1.0, 2.0/3.0) : float4(, 0.0, -1.0/3.0);
    float4 Q = (RGB.r < P.x) ? float4(P.xyw, RGB.r) : float4(RGB.r, P.yzx);
    float C = Q.x - min(Q.w, Q.y);
    float H = abs((Q.w - Q.y) / (6 * C + Epsilon) + Q.z);
    return float3(H, C, Q.x);

float3 RGBtoHSV(in float3 RGB)
    float3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return float3(HCV.x, S, HCV.z);

  float3 HSVtoRGB(in float3 HSV)
    float3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1) * HSV.y + 1) * HSV.z;

Second, in the final scene pass, conveniently called FinalScenePassPS add this after the gamma adjustments

    float boost = 1.5f;  
    float3 hsv = RGBtoHSV(color.rgb);
    float3 saturated = hsv;
    saturated.y *= boost;
    color.rgb = lerp(HSVtoRGB(saturated), HSVtoRGB(hsv), hsv.y);

So, from my admittedly very basic understanding, this will take a look at the color, determine the Hue, Saturation and Value and boost the saturation of colors that are more washed out, while leaving the already more saturated colors alone. You could change this to boosting darker color's saturation by swapping the lerp value from hsv.y to hsv.z.

boostof 2 looks natural enough, 5 looks nasty, and anything over 10 will be strong enough to make that lerp useless.

This will also add many new arithmetics to the shaders, so you'll need to change all the compile PS_2_0 to PS_2_X to give yourself more instruction slots

Alternate Uses: Simpler saturations by just doing

float3 hsv = RGBtoHSV(color.rgb);
hsv.y *= 0.6;
color.rgb = HSVtoRGB(hsv);

// Make the entire screen look like Predator Vision by making the saturation value something like 4
// Or technicolor vomit by boosting it to something wild like 255
Last edited:


Sergeant Knight at Arms
Very interesting. I may have some use for it.

Favorite thread anyways but more is always welcome!
Enhanced Fog

(with more fog collecting at lower altitudes)​

Pretty straight forward technique, I'm surprised this isn't the default behavior, to be honest.

float get_fog_amount_new(float d, float wz)
    wz = max(wz, 0.05f);    // This limits the minimum altitude, because the strength will increase exponentially as it approaches 0
    float valleys = pow(wz, -1);
    return get_fog_amount(4 * d * fFogDensity + (valleys * d * d * fFogDensity));
    // This is more up to artistic intent. I like dark, thick fog, hence the power.
There are a few tweaks you could make.
I like the idea of removing the just distance based fog entirely, allowing it to collect solely on scene altitude, with the power determined on distance from camera.
Easy-Peasy Lemon Squeezy
Playstationify Vertex Shader
(faux affine texture mapping, vertex snapping)

Warning: Not for those who get motion sickness easily
Register this with the other functions:
float4 playstationify(float4 vPos)
    int vRes = 120;    // Vertical Resolution. Lower = chunkier snapping
    int hRes = 160;    // Horizontal Resolution. Lower = chunkier snapping
    float4 projection = mul(matWorldViewProj, vPos); // Get vertex position in world view
    float4 vertex = projection;                        // Make a copy = / projection.w;        // flatten it along the depth
    vertex.x = floor(hRes * vertex.x) / hRes;        // This will lose precision of the float, causing the snapping
    vertex.y = floor(vRes * vertex.y) / vRes; *= projection.w;                        // This will then readd the depth
    if(vertex.w > 0)            // If in front of the camera . . .
        vertex /= vertex.w;        // Purposefully remove the depth information to break the texture projection in order to cause affine texture mapping
    return vertex;

Then instead of using Out.Pos = mul(matWorldViewProj, vPosition);
use Out.Pos = playstationify(vPosition);
And you've converted that Vertex Shader into a PSX style monstrosity.

For skinning support first do the deform and then run the deformed vObjectPos through the playstationify magic box.

And for anyone who just wants to see what it's like if you drop this shader into any (noninstanced) native shader
Last edited:


Hi, I am new on HLSL, is it possible to perform model frames through HLSL?
I mean in OPENbrf some models with frames like xbox, bow, however their frames can only be performed as scene props or items when on attack/reload. Is it possible to do so as items display its own frames all the time through HLSL?
I'm fairly confident in saying no, unfortunately. There's no way for us to instruct a shader to load the next frame of a vertex animation.

The strength of the shader is its speed at runtime, but it sacrifices access to things like memory and outside information to make that possible.


I'm fairly confident in saying no, unfortunately. There's no way for us to instruct a shader to load the next frame of a vertex animation.

The strength of the shader is its speed at runtime, but it sacrifices access to things like memory and outside information to make that possible.
Thx for your reply.
Enhanced Bloodied Effects
(using Native's Vertex Color Blood)
For best results, you can make a blood map per texture, but a generalized one could also work, albeit with potentially weird results
Time to Implement, 10 minutes. Time to fix the odd lightening issue, dunno, I'll update this post if I accomplish a fix.

This takes advantage of RGBtoHSV listed in the Boost Saturation post above.
I popped mine into the ps_main_standart shader, full code of which will be included in the bottom most spoiler, but the gist of it is very simple, set up a normal native material, but in Diffuse2 add in a bloodified version of the diffuse texture. We will interpolate between that and the base texture by taking the diffence between the Vertex Coloring and its base color of white, as it will become more saturated, the more bloodied we find ourselves.

        float4 tex2_col = tex2D(Diffuse2Sampler, In.Tex0);        //Register the Blood Texture
        float bloodAmount = distance(In.VertexColor.rgb, float3(1,1,1)); // Further from pure white = larger number is distance
        Output.RGBColor.rgb *= lerp(tex_col.rgb, tex2_col.rgb, bloodAmount * tex2_col.a);    // lerp between the two, based how far from the base white the vertex coloring is multiplied by the blood texture's alpha (to support transparent textures)
        INPUT_TEX_GAMMA(Output.RGBColor.rgb); // this line is all it took to correct the texture lightening issue

Pretty straightforward, huh?

Now, implementing it in Native took some doing, so here's everything I had to tweak, literally copy-pasted from my file:
The pixel shader
PS_OUTPUT ps_main_standart ( VS_OUTPUT_STANDART In, uniform const int PcfMode,
                                    uniform const bool use_bumpmap, uniform const bool use_specularfactor,
                                    uniform const bool use_specularmap, uniform const bool ps2x,
                                    uniform const bool use_aniso, uniform const bool terrain_color_ambient = true,
                                    uniform const bool use_bloodmap = false)
    PS_OUTPUT Output;

    float3 normal;
    if(use_bumpmap) {
        normal = (2.0f * tex2D(NormalTextureSampler, In.Tex0) - 1.0f);
        normal = In.SunLightDir;

    float sun_amount = 1;
    if (PcfMode != PCF_NONE)
        if((PcfMode == PCF_NVIDIA) || ps2x)        //we have more ins count for shadow, add some ambient factor to sun amount
            sun_amount = 0.05f + GetSunAmount(PcfMode, In.ShadowTexCoord, In.ShadowTexelPos);
            sun_amount = GetSunAmount(PcfMode, In.ShadowTexCoord, In.ShadowTexelPos);

    //define ambient term:
    const int ambientTermType = ( terrain_color_ambient && (ps2x || !use_specularfactor) ) ? 1 : 0;
    const float3 DirToSky = use_bumpmap ? In.SkyLightDir : float3(0.0f, 0.0f, 1.0f);
    float4 total_light = get_ambientTerm(ambientTermType, normal, DirToSky, sun_amount);

    float3 aniso_specular = 0;
    if(use_aniso) {
        float3 direction = float3(0,1,0);
        aniso_specular  = calculate_hair_specular(normal, direction, ((use_bumpmap) ?  In.SunLightDir : -vSunDir), In.ViewDir, In.Tex0);

    if( use_bumpmap)
        total_light.rgb += (saturate(dot(, + aniso_specular) * sun_amount * vSunColor;

        if(ps2x || !use_specularfactor) {
            total_light += saturate(dot(, * vSkyLightColor;
        if(ps2x || !use_specularfactor || (PcfMode == PCF_NONE))
            total_light.rgb += In.VertexLighting;

        #ifndef USE_LIGHTING_PASS
            float light_atten = In.PointLightDir.a;
            const int effective_light_index = iLightIndices[0];
            total_light += saturate(dot(, * vLightDiffuse[effective_light_index]  * light_atten);
    else {
        total_light.rgb += (saturate(dot(-vSunDir, + aniso_specular) * sun_amount * vSunColor;

        if(ambientTermType != 1 && !ps2x) {
            total_light += saturate(dot(, * vSkyLightColor;
        total_light.rgb += In.VertexLighting;

    if (PcfMode != PCF_NONE)
        Output.RGBColor.rgb = total_light.rgb;
        Output.RGBColor.rgb = min(total_light.rgb, 2.0f);

    // Output.RGBColor.rgb = total_light.rgb;    //saturate?
    Output.RGBColor.rgb *= vMaterialColor.rgb;

    float4 tex_col = tex2D(MeshTextureSampler, In.Tex0);

        float4 tex2_col = tex2D(Diffuse2Sampler, In.Tex0);
        float bloodAmount = distance(In.VertexColor.rgb, float3(1,1,1));
        bloodAmount *= 0.25f;
        Output.RGBColor.rgb *= lerp(tex_col.rgb, tex2_col.rgb, bloodAmount);
    else {
        Output.RGBColor.rgb *= tex_col.rgb;
        Output.RGBColor.rgb *= In.VertexColor.rgb;

    //add specular terms
    if(use_specularfactor) {
        float4 fSpecular = 0;

        float4 specColor = 0.1 * spec_coef * vSpecularColor;
        if(use_specularmap) {

            //float spec_tex_factor = dot(tex2D(SpecularTextureSampler, In.Tex0).rgb,0.33);
            //get more precision from specularmap
            float spec_tex_factor = tex2D(SpecularTextureSampler, In.Tex0).rgb;
            specColor *= spec_tex_factor;
        else //if(use_specular_alpha)    //is that always true?
            specColor *= tex_col.a;

        float4 sun_specColor = specColor * vSunColor * sun_amount;

        //sun specular
        float3 vHalf = normalize( In.ViewDir + ((use_bumpmap) ?  In.SunLightDir : -vSunDir) );
        fSpecular = sun_specColor * pow( saturate(dot(vHalf, normal)), fMaterialPower);
        if(PcfMode != PCF_DEFAULT)    //we have 64 ins limit
            fSpecular *= In.VertexColor;

            if(PcfMode == PCF_NONE)    //add point lights' specular color for indoors
                fSpecular.rgb += specColor * In.ShadowTexCoord.rgb;    //ShadowTexCoord => point lights specular! (calculate_point_lights_specular)
            //add more effects for ps2a version:
            if(ps2x || (PcfMode == PCF_NONE)) {
                #ifndef USE_LIGHTING_PASS
                //effective point light specular
                float light_atten = In.PointLightDir.a;
                const int effective_light_index = iLightIndices[0];
                float4 light_specColor = specColor * vLightDiffuse[effective_light_index] * (light_atten * 0.5);     //dec. spec term to remove "effective light change" artifacts
                vHalf = normalize( In.ViewDir + In.PointLightDir );
                fSpecular += light_specColor * pow( saturate(dot(vHalf, normal)), fMaterialPower);
            //fSpecular.rgb += specColor * In.SkyLightDir * 0.1;    //SkyLightDir-> holds lights specular color (calculate_point_lights_specular)
            fSpecular.rgb += (specColor + In.SkyLightDir) * 0.1;    //SkyLightDir-> holds lights specular color (calculate_point_lights_specular)
        Output.RGBColor += fSpecular;
    else if(use_specularmap) {


    //if we dont use alpha channel for specular-> use it for alpha
    Output.RGBColor.a = In.VertexColor.a;    //we dont control bUseMotionBlur to fit in 64 instruction

    if( (!use_specularfactor) || use_specularmap) {
        Output.RGBColor.a *= tex_col.a;

    return Output;
The technique macros (Twice! I dunno why but the macros are each registered again immediately after)
#define DEFINE_STANDART_TECHNIQUE(tech_name, use_bumpmap, use_skinning, use_specularfactor, use_specularmap, use_aniso, terraincolor, use_bloodmap)    \
                technique tech_name    \
                { pass P0 { VertexShader = standart_vs_noshadow[(2*use_bumpmap) + use_skinning]; \
                            PixelShader = compile ps_2_0 ps_main_standart(PCF_NONE, use_bumpmap, use_specularfactor, use_specularmap, false, use_aniso, terraincolor, use_bloodmap);} } \
                technique tech_name##_SHDW    \
                { pass P0 { VertexShader = standart_vs_default[(2*use_bumpmap) + use_skinning]; \
                            PixelShader = compile ps_2_0 ps_main_standart(PCF_DEFAULT, use_bumpmap, use_specularfactor, use_specularmap, false, use_aniso, terraincolor, use_bloodmap);} } \
                technique tech_name##_SHDWNVIDIA    \
                { pass P0 { VertexShader = standart_vs_nvidia[(2*use_bumpmap) + use_skinning]; \
                            PixelShader = compile ps_2_a ps_main_standart(PCF_NVIDIA, use_bumpmap, use_specularfactor, use_specularmap, true, use_aniso, terraincolor, use_bloodmap);} }  \
                DEFINE_LIGHTING_TECHNIQUE(tech_name, 0, use_bumpmap, use_skinning, use_specularfactor, use_specularmap)

#define DEFINE_STANDART_TECHNIQUE_HIGH(tech_name, use_bumpmap, use_skinning, use_specularfactor, use_specularmap, use_aniso, terraincolor, use_bloodmap)    \
                technique tech_name    \
                { pass P0 { VertexShader = compile vs_2_0 vs_main_standart(PCF_NONE, use_bumpmap, use_skinning); \
                            PixelShader = compile PS_2_X ps_main_standart(PCF_NONE, use_bumpmap, use_specularfactor, use_specularmap, true, use_aniso, terraincolor, use_bloodmap);} } \
                technique tech_name##_SHDW    \
                { pass P0 { VertexShader = compile vs_2_0 vs_main_standart(PCF_DEFAULT, use_bumpmap, use_skinning); \
                            PixelShader = compile PS_2_X ps_main_standart(PCF_DEFAULT, use_bumpmap, use_specularfactor, use_specularmap, true, use_aniso, terraincolor, use_bloodmap);} } \
                technique tech_name##_SHDWNVIDIA    \
                { pass P0 { VertexShader = compile vs_2_0 vs_main_standart(PCF_NVIDIA, use_bumpmap, use_skinning); \
                            PixelShader = compile ps_2_a ps_main_standart(PCF_NVIDIA, use_bumpmap, use_specularfactor, use_specularmap, true, use_aniso, terraincolor, use_bloodmap);} } \
                DEFINE_LIGHTING_TECHNIQUE(tech_name, 0, use_bumpmap, use_skinning, use_specularfactor, use_specularmap)

and add a false to every Native shader registered using those macros
DEFINE_STANDART_TECHNIQUE( standart_noskin_bump_nospecmap,                 true, false, true, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_noskin_bump_specmap,                 true, false, true, true,  false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_skin_bump_nospecmap,                 true, true,  true, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_skin_bump_specmap,                     true, true,  true, true,  false, true, false)
//high versions:
DEFINE_STANDART_TECHNIQUE_HIGH( standart_skin_bump_nospecmap_high,         true, true,  true, false, false, true, false)
DEFINE_STANDART_TECHNIQUE_HIGH( standart_skin_bump_specmap_high,         true, true,  true, true , false, true, false)
DEFINE_STANDART_TECHNIQUE_HIGH( standart_noskin_bump_nospecmap_high,     true, false,  true, false, false, true, false)
DEFINE_STANDART_TECHNIQUE_HIGH( standart_noskin_bump_specmap_high,         true, false,  true, true , false, true, false)
//nobump versions:
DEFINE_STANDART_TECHNIQUE( standart_noskin_nobump_nospecmap,             false, false, true, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_noskin_nobump_specmap,                 false, false, true, true , false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_skin_nobump_nospecmap,                 false,  true, true, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_skin_nobump_specmap,                 false,  true, true, true , false, true, false)
//nospec versions:
DEFINE_STANDART_TECHNIQUE( standart_noskin_nobump_nospec,                 false, false, false, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_noskin_bump_nospec,                 true,  false, false, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_noskin_bump_nospec_noterraincolor,     true,  false, false, false, false, false, false)
DEFINE_STANDART_TECHNIQUE( standart_skin_nobump_nospec,                 false,  true, false, false, false, true, false)
DEFINE_STANDART_TECHNIQUE( standart_skin_bump_nospec,                     true,   true, false, false, false, true, false)
DEFINE_STANDART_TECHNIQUE_HIGH( standart_noskin_bump_nospec_high,                 true, false, false, false, false, true, false)
DEFINE_STANDART_TECHNIQUE_HIGH( standart_noskin_bump_nospec_high_noterraincolor, true, false, false, false, false, false, false)
DEFINE_STANDART_TECHNIQUE_HIGH( standart_skin_bump_nospec_high,                 true,  true, false, false, false, true, false)

All that, just to add:
DEFINE_STANDART_TECHNIQUE_HIGH( standart_skin_bump_specmap_high_blood, true, true, true, true , false, true, true)

Possible Tweaks:
Change the blood texture to blood splatter over an alpha instead of over the base texture and apply that to the texture based on bloodiedness levels to allow it to be reused in many places. (this will also allow you to use the alpha as a specular to make the blood shinier than surrounding areas)
Instead of using the saturation value of the vColor, use float blood_amount = distance(vColor.rgb, float3(1,1,1); as it should become a larger value as you become more bloodied. Needs testing to see if it would interpolate enough into the bloodied texture.

Oh, and here's my test texture, if you are doing a bespoke blood texture per model, I'd recommend adding splatter the the arms, neck, and anywhere else you'll commonly hit/be hit.

There were some conversations about some potential uses inside the #wb-3d-art channel of the discord, which are copied below
Well, since the engine applies blood via vertex coloring there's not a whole lot you can do without a lot of bodging. Using RBGtoHSV on the In.VertexColor and you can take the .x (hue) channel of the HSV'd color to determine if the hue is red, and then check the .y (saturation) channel to determine if it's over a certain level of saturation, and then if all that passes, use the .y (saturation) inside the lerp function.
That just really ramps up the instructions count. And won't work at all if the mesh is already using red vertex colors.

oh yea that's a good idea. I wonder how many meshes actually have vertex colours, might be a non problem. The addition of blood maps for 2 many meshes seems like it could mount up in resource costs. But I guess special ones would work. You could just multiply Output.RGBColor.rgb *= The blood colour you want; Instead of in.vertexcolor to tint blood to different colours right?

That could work if you used the bloodAmount to mask it so you wouldn't accidentally tint the entire texture. You could also do something like a generic blood texture with transparency for all armors, and do a different one per race, or do it in greyscale and tint it in the shader per race. But in that case you would have to probably multiply the bloodAmount inside the lerp by the alpha channel of the blood map so it wouldn't apply where it shouldn't.
Last edited:
I thought I made a post of this particular postFX filter, but apparently I only shared the glory on discord. Here's:
Palette Swap
(low-fi graphics to chill/kill looters to)
Keep in mind I made 5 months ago and so I am not 100% sure what that Dustin was thinking, but I know him better than most and think I can translate my own nonsense to another useless post.

Concept: Using the final PostFx pass, take the colors in a scene and convert it to a more limited palette to emulate older hardware palettes. (I use to get the hexadecimals of the palettes, and then convert that to a percentage RGB later)

I also use noise lifted from in order to break up large blocks of single colors since I was using 4 color CGA palettes. I am not clever enough to make it an actual dither, but the generative noise works well enough as is.

//inside the definitions at the top
#define Palette             (postfxParams3.w)

// just before the close of FinalScenePassPS, before return color;
if (Palette != 1.0f) {
    float pixelDepth = tex2D(postFX_sampler4, texCoord).r; //Conditions of their variables definitions aren't always true and so it needs to be redefined here.
    float3 hsv = RGBtoHSV(color.rgb);

    float N = pow(2.0, 6.0 - 5.0 * hsv.z - pixelDepth);
    float seed = random(hsv.y); // hsv.y is Saturation

    float3 noise = floor(N * color.rgb + 2.0 * seed) / N;

    color.rgb = lerp(color.rgb, noise, 0.5); // Liner Interpolation to mix the scene's colors with the generative noise

    color.rgb = floor(color.rgb * 16) / 16; // This line is interesting, essentially we are removing accuracy from the colors.
// My assumption here is that by removing the amount of renderable colors to 4096, it will look more ~retro~, you can drop it even further down
// by dropping that 16 to a smaller number. I think #^3 should be a count for the colors remaining in the palette.
// you can use this multiply than floor technique to make low resolution shaders, as seen in the playstationify shader above.

    hsv = RGBtoHSV(color.rgb);

    int paletteChoice = Palette;
    // I am using the postfxParams3.w as a way to declare palette inside of since it doesn't seem there is another way to pass info to the post shaders on the fly
    // I can't seem to see where any of the floats in postfxParams3 get used anywhere, so you might see me using these things as inputs in later postFX

    float3 palette[4]; // I am declaring the number of floats inside the list it might expand automatically when you put more than expected, but since all my examples only have 4, I used 4

    if(paletteChoice == 0){
    palette[0] = float3(0.33,1,0.33);
    palette[1] = float3(1,0.33,0.33);
    palette[2] = float3(1,1,0.33);
    palette[3] = float3(0,0,0);}

    else if(paletteChoice == 1){
    palette[0] = float3(0.33,1,1);
    palette[1] = float3(1,0.33,1);
    palette[2] = float3(1,1,1);
    palette[3] = float3(0,0,0);}

    else if(paletteChoice == 2){
    palette[0] = float3(0.33,1,1);
    palette[1] = float3(1,0.33,0.33);
    palette[2] = float3(1,1,1);
    palette[3] = float3(0,0,0);}

    else {
    palette[0] = float3(0.33,1,1);
    palette[1] = float3(1,0.33,1);
    palette[2] = float3(1,1,1);
    palette[3] = float3(0,0,0);}
    float3 replacementcolor = float3(0, 0, 0);
    float dist = 1000000.0;

    for (int i = 0; i < 2; i++){
        //I never check the palette[3] because it's already the default replacement color
        // I honestly never thought about this until writing this post. I could rewrite this
        // buuuutttt. . . I'm not gonna
        float3 c = palette[i];
        float d = distance(color.rgb, c);
        if (d < dist) {
            dist = d;
            replacementcolor = c;

        I'm sure you can read the above, but basically
        it looks at the new noisy pixel color and compares
        it to all the colors in the palette and whichever color
        in the palette is nearest, it gets assigned

    color.rgb = replacementcolor;

    if(hsv.z > 0.75)
        color.rgb = palette[2];
    else if (hsv.z < 0.15)
        color.rgb = palette[3];

    I then use the above to replace the brightest and darkest values with their respective
    colors in my palettes, this just adds a much better look, it's fine to skip with a
    larger palette, though

return color;

I'll go through and update this after transcribing a 16 color palette and maybe a 32 color palette just to see, I don't have time at this exact moment to do that atm.

  • if (Palette != 1.0f) is used for in scene palette changes, you don't need it. You can alter this pretty easily to avoid using that.
  • This version requires RGBtoHSV, listed in a post above.
Last edited:


Sergeant Knight at Arms
Earendil had the nice idea to switch between loading screens.
dstn linked me to that one:

perfect for the first loading screen or if you want something like a tv screen.

    PS_OUTPUT Output;

    float offsetX = 1.25;
    float offsetY = 4.5;
    float slideTime = time_var * 0.3;

    float mix1 = clamp(exp(2.0 * sin(slideTime)) - offsetY, 0.0 , 1.0);

    float mix2 = clamp(exp(2.0 * sin(slideTime - offsetX)) - offsetY, 0.0 , 1.0);

    float mix3 = clamp(exp(2.0 * sin(slideTime - offsetX * 2.0)) - offsetY, 0.0 , 1.0);

    float mix4 = clamp(exp(2.0 * sin(slideTime - offsetX* 3.0)) - offsetY, 0.0 , 1.0);

    float mix5 = clamp(exp(2.0 * sin(slideTime - offsetX* 4.0)) - offsetY, 0.0 , 1.0);

    float4 tex_col = tex2D(MeshTextureSampler, In.Tex0)* mix1;
    float4 tex_col2 = tex2D(Diffuse2Sampler, In.Tex0)* mix2;
    float4 tex_col3 = tex2D(NormalTextureSampler, In.Tex0)* mix3;
    float4 tex_col4 = tex2D(EnvTextureSampler, In.Tex0)* mix4;
    float4 tex_col5 = tex2D(SpecularTextureSampler, In.Tex0)* mix5;

    tex_col = tex_col + tex_col2 + tex_col3 + tex_col4 + tex_col5;
    Output.RGBColor =  In.Color * tex_col;
    return Output;

technique tc_screen //Uses gamma
    pass P0
        VertexShader = vs_font_compiled_2_0;
        PixelShader = compile ps_2_0 ps_tc_screen();
the shader will switch between the different texture samplers

edit: if you want the shader for the starting screen add it to core_shaders
best copy simple_shader and rename the shader and technique.

Last edited:
Top Bottom