Skip to content

Mysteries: NDC Conversion

Noel Tautges edited this page Jan 8, 2022 · 2 revisions

NDC Conversion

Status: patched, but not understood

Description

Certain vertex shaders manually convert their clip space position into Normalized Device Coordinates and pass it to the fragment shader as a texture coordinate:

Outer Wilds/Effects/Flame-1

u_xlat0.x = u_xlat1.y * _ProjectionParams.x;
u_xlat0.w = u_xlat0.x * 0.5;
u_xlat0.xz = u_xlat1.xw * vec2(0.5, 0.5);
vs_TEXCOORD3.zw = u_xlat1.zw;
vs_TEXCOORD3.xy = u_xlat0.zz + u_xlat0.xw;

ShaderVariablesFunctions.hlsl

float4 ndc = input.positionCS * 0.5f;
input.positionNDC.xy = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w;
input.positionNDC.zw = input.positionCS.zw;

The z component is then divided by _ProjectionParams.y in the fragment shader:

u_xlat10 = vs_TEXCOORD3.z / _ProjectionParams.y;

This division does not achieve the intended value under OpenGL. Instead, an adjustment must be made involving the w component:

u_xlat10 = (vs_TEXCOORD3.w - vs_TEXCOORD3.z) / _ProjectionParams.y / 2.0;

The situation in Outer Wilds/Particles/Additive-1 is worse; there, the code creates 2 variables from this calculation before taking the minimum of them:

u_xlat10 = vs_TEXCOORD2.z / _ProjectionParams.y;
u_xlat10 = (-u_xlat10) + 1.0;
u_xlat10 = u_xlat10 * _ProjectionParams.z;
u_xlat10 = max(u_xlat10, 0.0);
u_xlat15 = u_xlat10 + _ProjectionParams.y;
u_xlat10 = u_xlat10 / _CameraFadeDist;
u_xlat10 = clamp(u_xlat10, 0.0, 1.0);
u_xlat5.x = (-u_xlat15) + u_xlat5.x;
u_xlat5.x = u_xlat5.x / _GeomFadeDist;
u_xlat5.x = clamp(u_xlat5.x, 0.0, 1.0);
u_xlat5.x = min(u_xlat5.x, u_xlat10);

If no adjustment is made, taking u_xlat5.x as the result of the minimum produces the intended result. If the adjustment is made, taking u_xlat10 as the result of the minimum produces the intended result. Making the adjustment fixes one code path and breaks the other.

Fixing this peculiar side effect results in an unseemly patch with an extra variable.

Questions

  1. Why does the adjustment work to get the correct value?
  2. Why doesn't it work in Outer Wilds/Particles/Additive-1?

Sample of fragment shaders affected

  • Outer Wilds/Effects/Flame-1
  • Outer Wilds/Effects/Smoke Column-4
  • Outer Wilds/Effects/Volumetric Light-6
  • Outer Wilds/Environment/Giant's Deep/Current-3
  • Outer Wilds/Environment/Ice-22
  • Outer Wilds/Environment/Water Underwater-44
  • Outer Wilds/Environment/Water-66
  • Outer Wilds/Particles/Additive-1
  • Outer Wilds/Particles/Alpha-12
  • Outer Wilds/Particles/Cutoff-114

Notes

Clone this wiki locally