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:
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.
// 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;
}