Skip to content

Putting HLSL in the shader file

Zack edited this page May 31, 2025 · 15 revisions

Replace PixelShader = <pSdr> with PixelShader = compile ps_1_1 ShaderName() to use the game's compiler

The game supports shader models 1_1 to 3_0, you should use 3.0 for the most features, but I'll write it for 1_1 here since it's a bit more limited

Texture Tex0;
Texture Tex1;
Texture Tex2;
Texture Tex3;

const string inputStreamFormat = "PosNormColorTex1";

// There is one flaw, getting the compiler to respect reserved constants is quite difficult.
// localToWorld's last row is considered unused, it gets treated like a 4x3 which doesn't sound right, but I guess that means it's not needed?
// That might be why it doesn't work with the validator
float4x4 localToScreen : register(c0);
float4x4 localToWorld : register(c4);

#define LocalToScreen(x) mul(x, localToScreen)
#define LocalToWorld(x) mul(x, localToWorld)
// I did try changing this line thinking it confused the compiler or something, but no.
#define RotateToWorld(x) mul(x, (float3x3)localToWorld)

float4 CAMERA : register(c8);
float4 CAMDIR : register(c9);
float4 TIME : register(c14);
float4 PLANEX : register(c17);
float4 PLANEY : register(c18);
float4 PLANEZ : register(c19);

struct VSInput
{
    float3 pos : POSITION;
    float3 nrm : NORMAL;
    float4 diff : COLOR;
    float2 uv : TEXCOORD0;
};

struct PSInput
{
    float4 pos : POSITION;

    // In shader model 1, the coordinate inputs need to match the textures
    float2 uv1 : TEXCOORD0;
    float3 reflectionUV : TEXCOORD1;
    float2 uv2 : TEXCOORD2;
    float3 normalUV : TEXCOORD3;

    float4 AMBIENT : COLOR0;
    float4 EXTRA : COLOR1;
};

#define FRESNEL AMBIENT.a
#define BLEND EXTRA.a

PSInput MyVertexShader(VSInput i)
{
    PSInput o;

    o.pos = LocalToScreen(float4(i.pos, 1));
    o.uv1 = i.uv;
    o.uv2 = i.uv;

    float4 worldPos = LocalToWorld(float4(i.pos, 1));

    float4 worldNormal;
    worldNormal.xyz = RotateToWorld(i.nrm);

    worldNormal.a = 1.0f;

    o.normalUV = worldNormal.xyz;

    float3 incident = normalize(worldPos.xyz - CAMERA.xyz);
    o.reflectionUV = reflect(incident, worldNormal.xyz);


    o.AMBIENT.x = sqrt(dot(worldNormal, PLANEX));
    o.AMBIENT.y = sqrt(dot(worldNormal, PLANEY));
    o.AMBIENT.z = sqrt(dot(worldNormal, PLANEZ));

    float2 f;
    f.x = abs(dot(worldNormal.xyz, incident));
    f.x = 1.0f - f.x;
    f.y = f.x * f.x * f.x;
    o.FRESNEL = f.y * 0.5 + 0.5;

    o.BLEND = i.diff.a;

    o.EXTRA.rgb = float3(1.0f, 1.0f, 1.0f);

    return o;
}

float4 SHADOW : register(c2);

sampler2D colour : register(s0);
samplerCUBE specular : register(s1);
sampler2D dirt : register(s2);
samplerCUBE lighting : register(s3);

float4 MyPixelShader(PSInput i) : COLOR
{
    float4 c = texCUBE(specular, i.reflectionUV) * i.FRESNEL;
    c = saturate(c + lerp(tex2D(colour, i.uv1), tex2D(dirt, i.uv2), i.BLEND));

    float4 l = texCUBE(lighting, i.normalUV).a * SHADOW;
    l = float4(saturate(i.AMBIENT.rgb * 0.6f + l), 1);

    return c * l;
}

// The technique and pass name is the same across everything
Technique T0
{
    Pass P0
    {
        // In FlatOut 2 the pass can require either a bit of setup or a lot depending on the shader
        Texture[0] = <Tex0>;
	Texture[1] = <Tex1>;
	Texture[2] = <Tex2>;
	Texture[3] = <Tex3>;

        VertexShader = compile vs_1_1 MyVertexShader();
        PixelShader = compile ps_1_1 MyPixelShader();
    }
}

Clone this wiki locally