Files
t5de-renderer/renderer/shaders/fixed_function.hlsl
T
dhkatzandClaude Opus 5.5 a04dba18c1 Draw particles, match the client's fog, and measure frame delay
Particles (point sprites) are drawn as instanced camera-facing squares sized like Direct3D 9's. Fog is by depth buffer value, as Direct3D 9 does with IMVU's projections. At most one frame is queued for display, frames carry the adapter's timestamp so the renderer process logs the delay to presenting, and the log's lines from several processes no longer mix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 06:11:51 -07:00

146 lines
5.3 KiB
HLSL

// IMVU's Direct3D 9 fixed-function look, as its renderer sets it up (docs/interface.md):
// - per-vertex (Gouraud) lighting, with the vertex color as diffuse and ambient material (white without one): the
// environment's ambient light, plus each light's color times max(0, N.L); no specular or emissive light;
// - textures: none (the lit color), one (times the lit color), or two (the second blended over the first by its
// alpha, then times the lit color), both on the vertex's texture coordinates with their own transforms;
// - an alpha test passing pixels whose alpha is greater than the reference, then linear fog. Direct3D 9 fogs by
// distance from the eye only for projections with +1 as their (3, 4) element; IMVU's (right-handed) have -1, so
// it fogs by the depth buffer's value (0 to 1) against the fog's start and end (in scene units, 1 to 50 in
// rooms): in effect, no fog. Reproduced as is.
//
// Particles (vs_sprite) are Direct3D 9 point sprites there: camera-facing squares, viewport height / distance pixels
// across (1 to 64), textured over the whole square; here each is drawn as an instance of a 4-vertex strip.
//
// Compiled for each combination of the vertex streams present (HAS_NORMALS, HAS_TEX_COORDS, HAS_COLORS) and the
// number of textures (TEXTURES).
cbuffer Constants
{
row_major float4x4 g_World;
row_major float4x4 g_View;
row_major float4x4 g_Projection;
float4 g_TexTransform0[2]; // u' = dot((u, v, 1), [0].xyz), v' = dot((u, v, 1), [1].xyz)
float4 g_TexTransform1[2];
float4 g_Ambient; // rgb
float4 g_LightColor[8]; // rgb, a: enabled
float4 g_LightVector[8]; // xyz: position, or direction toward the light; w: 1 if directional
float4 g_FogColor; // rgb
float4 g_Fog; // x: start, y: end, z: on
float4 g_Flags; // x: lighting on, y: alpha test on, z: alpha reference (0-255)
float4 g_Viewport; // xy: size in pixels
};
struct VSInput
{
float3 Position : ATTRIB0;
#if HAS_NORMALS
float3 Normal : ATTRIB1;
#endif
#if HAS_TEX_COORDS
float2 TexCoord : ATTRIB2;
#endif
#if HAS_COLORS
float4 Color : ATTRIB3; // bytes B, G, R, A
#endif
};
struct PSInput
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
float2 TexCoord0 : TEXCOORD0;
float2 TexCoord1 : TEXCOORD1;
};
void vs_main(in VSInput input, out PSInput output)
{
float4 world = mul(float4(input.Position, 1.0), g_World);
float4 view = mul(world, g_View);
output.Position = mul(view, g_Projection);
float4 material = float4(1.0, 1.0, 1.0, 1.0);
#if HAS_COLORS
material = input.Color.zyxw;
#endif
float4 color = material;
if (g_Flags.x > 0.5)
{
float3 normal = float3(0.0, 0.0, 0.0);
#if HAS_NORMALS
normal = normalize(mul(input.Normal, (float3x3)g_World));
#endif
float3 lit = g_Ambient.rgb * material.rgb;
for (int i = 0; i < 8; ++i)
{
if (g_LightColor[i].a > 0.5)
{
float3 toLight = g_LightVector[i].w > 0.5 ? g_LightVector[i].xyz : g_LightVector[i].xyz - world.xyz;
lit += g_LightColor[i].rgb * material.rgb * max(0.0, dot(normal, normalize(toLight)));
}
}
color = float4(saturate(lit), material.a);
}
output.Color = color;
float3 uv = float3(0.0, 0.0, 1.0);
#if HAS_TEX_COORDS
uv.xy = input.TexCoord;
#endif
output.TexCoord0 = float2(dot(uv, g_TexTransform0[0].xyz), dot(uv, g_TexTransform0[1].xyz));
output.TexCoord1 = float2(dot(uv, g_TexTransform1[0].xyz), dot(uv, g_TexTransform1[1].xyz));
}
struct SpriteInput
{
float3 Position : ATTRIB0;
float4 Color : ATTRIB3; // bytes B, G, R, A
uint Corner : SV_VertexID;
};
void vs_sprite(in SpriteInput input, out PSInput output)
{
float4 view = mul(mul(float4(input.Position, 1.0), g_World), g_View);
float4 clip = mul(view, g_Projection);
float size = clamp(g_Viewport.y / max(length(view.xyz), 1e-4), 1.0, 64.0);
float2 corner = float2((input.Corner & 1) != 0 ? 1.0 : -1.0, (input.Corner & 2) != 0 ? -1.0 : 1.0);
clip.xy += corner * size / g_Viewport.xy * clip.w; // half the size each way, in clip space
output.Position = clip;
// No normals: lit by the ambient light alone.
float4 material = input.Color.zyxw;
output.Color = g_Flags.x > 0.5 ? float4(saturate(g_Ambient.rgb * material.rgb), material.a) : material;
output.TexCoord0 = float2(corner.x * 0.5 + 0.5, 0.5 - corner.y * 0.5);
output.TexCoord1 = output.TexCoord0;
}
#if TEXTURES >= 1
Texture2D g_Texture0;
SamplerState g_Texture0_sampler;
#endif
#if TEXTURES >= 2
Texture2D g_Texture1;
SamplerState g_Texture1_sampler;
#endif
float4 ps_main(in PSInput input) : SV_Target
{
float4 color = input.Color;
#if TEXTURES == 1
color *= g_Texture0.Sample(g_Texture0_sampler, input.TexCoord0);
#elif TEXTURES == 2
float4 first = g_Texture0.Sample(g_Texture0_sampler, input.TexCoord0);
float4 second = g_Texture1.Sample(g_Texture1_sampler, input.TexCoord1);
color *= lerp(first, second, second.a);
#endif
if (g_Flags.y > 0.5 && !(color.a * 255.0 > g_Flags.z))
{
discard;
}
if (g_Fog.z > 0.5)
{
float visible = saturate((g_Fog.y - input.Position.z) / max(g_Fog.y - g_Fog.x, 1e-6));
color.rgb = lerp(g_FogColor.rgb, color.rgb, visible);
}
return color;
}