What I Learned Porting to Shader Model 3.0

The Project Reality Team (realitymod.com) upgraded Project Reality to support Shader Model 3, unlocking greater graphical potential. This document outlines key considerations when porting shaders from Shader Model 2 to 3.

Fogging

In Shader Model 3, fogging is no longer a fixed-function operation. You must implement your desired fogging method within the pixel shader.

Output Register Count

  • Shader Model 2 vertex shaders have a limited number of output registers for each data type:

    oPos:

    1 position

    oFog:

    1 fog

    oPts:

    1 point-size

    oD#:

    2 vertex color

    oT#:

    8 texture coordinate

  • Shader Model 3 provides 12 output registers, usable for any data type.

Input Register Format

  • In Shader Model 2, output registers have varying precision levels.

    For example, vertex color registers oD# are 8-bit unsigned data, mapped to the [0, 1] range in the pixel shader.

  • When porting vertex colors oD# from Shader Model 2 to 3, apply saturate() to the vertex color output to ensure correct clamping.

Register Assignments and Declarations

If you encounter the following assembly code, where constants are not explicitly declared:

Example ASM
VertexShader = asm
{
   vs.1.1

   dcl_position0 v0

   add r0.xyz, v0.xzw, -c[0].xyz
   mul r0.xyz, r0.xyz, c[1].xyw // z = 0, w = 1
   add oPos.x, r0.x, -c[1].w
   add oPos.y, r0.y, -c[1].w
   mov oPos.z, r0.z
   mov oPos.w, c[1].w // z = 0, w = 1
   add r1, v0.y, -c[2].x
   mul oD0, r1, c[2].y
   mov oD0.a, c[1].z // z = 0
};

PixelShader = asm
{
   ps.1.1
   mov r0, v0
};

The solution is to use the : register() syntax to bind shader variables to specific registers.

Solution
// Assign variables to registers because DICE didn't do so in their ASM.
float4 Constant0 : register(c0); // c[0]
float4 Constant1 : register(c1); // c[1]
float4 Constant2 : register(c2); // c[2]

struct APP2PS_ProjectRoad
{
   float4 Pos : POSITION0;
};

struct VS2PS_ProjectRoad
{
   float4 HPos : POSITION;
   float4 Color : TEXCOORD0;
};

// VertexShader
VS2PS_ProjectRoad VS_ProjectRoad(APP2PS_ProjectRoad Input)
{
   VS2PS_ProjectRoad Output = (VS2PS_ProjectRoad)0.0;

   // add r0.xyz, v0.xzw, -c[0].xyz
   // mul r0.xyz, r0.xyz, c[1].xyw // z = 0, w = 1
   float3 ProjPos = Input.Pos.xzw - Constant0.xyz;
   ProjPos *= Constant1.xyw; // z = 0, w = 1

   // add oPos.x, r0.x, -c[1].w
   // add oPos.y, r0.y, -c[1].w
   // mov oPos.z, r0.z
   // mov oPos.w, c[1].w // z = 0, w = 1
   Output.HPos.x = ProjPos.x - Constant1.w;
   Output.HPos.y = ProjPos.y - Constant1.w;
   Output.HPos.z = ProjPos.z;
   Output.HPos.w = Constant1.w; // z = 0, w = 1

   // add r1, v0.y, -c[2].x
   // mul oD0, r1, c[2].y
   // mov oD0.a, c[1].z // z = 0
   float4 Color = Input.Pos.y - Constant2.x;
   Output.Color = Color * Constant2.y;
   Output.Color.a = Constant1.z; // z = 0
   Output.Color = saturate(Output.Color);

   return Output;
}

// PixelShader
float4 PS_ProjectRoad(VS2PS_ProjectRoad Input) : COLOR0
{
   // mov r0, v0
   return Input.Color;
}