The order of the three normal rules is load-bearing
The derivative normal is computed unconditionally, before any
guard-dependent branch. The epsilon guard below reads an interpolated varying,
so branching on it is non-uniform control flow — where GLSL leaves
dFdx/dFdyundefined, because normal validity can differ between fragments
of the same 2×2 quad. Only the cheap gl_FrontFacing flip may sit behind the
guard; never the derivative evaluation.
The epsilon guard is written negated (!(dot(N, N) >= eps)), which is not a
style choice: NaN fails every comparison, so a corrupt store's NaN normal
fails >= and takes the fallback. The positive form dot(N, N) < eps would let
it slip through and normalize into NaN shading.
The two-sided flip applies to the stored normal only. gl_FrontFacing ? N : -N exists because double_sided defaults true and §5's whole-triangle cull
exposes the interior back faces of a sliced closed isosurface: without the flip,
the stored back normal shades on the wrong side of its gradient, producing an
inverted result that can collapse toward uAmbient.
The derivative normal needs no flip — cross(dFdx, dFdy) is defined by the
rasterized fragment, not by the winding, so it always faces the viewer — and
flipping it would reintroduce exactly that inverted shade, worst precisely at
the degenerate vertices the guard is there to rescue. So the exemption is per
FRAGMENT, not per variant.
The near fade is evaluated here, not in the vertex stage
The three sibling types evaluate perspectiveNearFade per VERTEX (points/gsplats)
or partly so (lines clip the segment in the vertex stage), because an instanced
quad has one center depth and a per-vertex value is exact for the whole sprite. A
triangle is not a sprite: it spans depth, so a per-vertex fade would interpolate
the RAMP across the face and a large triangle straddling the fade band would
render a linear smear instead of the smoothstep. Hence the fade is computed here,
off the existing vViewPos varying.
Fragment stage.
The order of the three normal rules is load-bearing
dFdx/dFdyundefined, because normal validity can differ between fragments of the same 2×2 quad. Only the cheapgl_FrontFacingflip may sit behind the guard; never the derivative evaluation.!(dot(N, N) >= eps)), which is not a style choice:NaNfails every comparison, so a corrupt store's NaN normal fails>=and takes the fallback. The positive formdot(N, N) < epswould let it slip through and normalize into NaN shading.gl_FrontFacing ? N : -Nexists becausedouble_sideddefaults true and §5's whole-triangle cull exposes the interior back faces of a sliced closed isosurface: without the flip, the stored back normal shades on the wrong side of its gradient, producing an inverted result that can collapse towarduAmbient. The derivative normal needs no flip —cross(dFdx, dFdy)is defined by the rasterized fragment, not by the winding, so it always faces the viewer — and flipping it would reintroduce exactly that inverted shade, worst precisely at the degenerate vertices the guard is there to rescue. So the exemption is per FRAGMENT, not per variant.The near fade is evaluated here, not in the vertex stage
The three sibling types evaluate
perspectiveNearFadeper VERTEX (points/gsplats) or partly so (lines clip the segment in the vertex stage), because an instanced quad has one center depth and a per-vertex value is exact for the whole sprite. A triangle is not a sprite: it spans depth, so a per-vertex fade would interpolate the RAMP across the face and a large triangle straddling the fade band would render a linear smear instead of the smoothstep. Hence the fade is computed here, off the existingvViewPosvarying.