!!ARBvp1.0
OPTION ARB_position_invariant;
MOV result.texcoord, vertex.attrib[8];
MOV result.color, vertex.attrib[3];
END
!!ARBfp1.0
# == Fragment Program ==
#
# Input textures
# texture[0] particle diffusemap
# texture[1] _currentDepth
#
# Constants set by the engine:
# program.env[4] is reciprocal of _currentDepth size. Lets us convert a screen position to a texcoord in _currentDepth
# program.env[5] is the particle radius, given as { radius, 1/(fadeRange), 1/radius }
# fadeRange is the particle diameter for alpha blends (like smoke), but the particle radius for additive
# blends (light glares), because additive effects work differently. Fog is half as apparent when a wall
# is in the middle of it. Light glares lose no visibility when they have something to reflect off.
# program.env[6] is the color channel mask. Particles with additive blend need their RGB channels modifying to blend them out.
# Particles with an alpha blend need their alpha channel modifying.
#
# Hard-coded constants
# depth_consts allows us to recover the original depth in Doom units of anything in the depth
# buffer. TDM's projection matrix differs slightly from the classic projection matrix as it
# implements a "nearly-infinite" zFar. The matrix is hard-coded in the engine, so we use hard-coded
# constants here for efficiency. depth_consts is derived from the numbers in that matrix.
#
PARAM depth_consts = { 0.33333333, -0.33316667, 0.0, 0.0 };
PARAM particle_radius = program.env[5];
TEMP tmp, scene_depth, particle_depth, near_fade, fade;
# Map the fragment to a texcoord on our depth image, and sample to find scene_depth
MUL tmp.xy, fragment.position, program.env[4];
TEX scene_depth, tmp, texture[1], 2D;
MIN scene_depth, scene_depth, 0.9994; # Required by TDM projection matrix. Equates to max recoverable
# depth of 30k units, which is enough. 0.9995 is infinite depth. This
# is needed only if there is caulk sky on show (which writes no depth,
# so leaves 1 in the depth texture).
# Recover original depth in doom units
MAD tmp, scene_depth, depth_consts.x, depth_consts.y;
RCP scene_depth, tmp.x;
# Convert particle depth to doom units too
MAD tmp, fragment.position.z, depth_consts.x, depth_consts.y;
RCP particle_depth, tmp.x;
# Scale the depth difference by the particle diameter to calc an alpha
# value based on how much of the 3d volume represented by the particle
# is in front of the solid scene
ADD tmp, -scene_depth, particle_depth; # NB depth is negative. 0 at the eye, -100 at 100 units into the screen.
ADD tmp, tmp, particle_radius.x; # Add the radius so a depth difference of particle radius now equals 0
MUL_SAT fade, tmp, particle_radius.y; # divide by the particle radius or diameter and clamp
# Also fade if the particle is too close to our eye position, so it doesn't 'pop' in and out of view
# Start a linear fade at particle_radius distance from the particle.
MUL_SAT near_fade, particle_depth, -particle_radius.z;
# Calculate final fade and apply the channel mask
MUL fade, near_fade, fade;
ADD_SAT fade, fade, program.env[6]; # saturate the channels that don't want modifying
# Set the color. Multiply by vertex/fragment color as that's how the particle system fades particles in and out
TEMP oColor;
TEX oColor, fragment.texcoord, texture[0], 2D;
MUL oColor, oColor, fade;
MUL result.color, oColor, fragment.color;
END