run method

  1. @override
Vector4? run(
  1. Float32List v,
  2. ShaderBindings b,
  3. FragmentContext c
)
override

Returns linear RGBA for attachment zero. Null discards the fragment.

Implementation

@override
Vector4? run(Float32List v, ShaderBindings b, FragmentContext c) {
  final s = readSurface(v, b, c);
  if (s == null) return null;
  applyCommonMaps(s, v, b, c);
  // Roughness sets the band count, so the ORM map matters here too.
  applyMetallicRoughnessMap(s, v, b, c);

  final lit = accumulateLights(
    s,
    b,
    c,
    shadowed: true,
    shade: (s, light) {
      // Fewer bands as roughness rises, so the slider still does something.
      final bands = 5.0 + (2.0 - 5.0) * s.roughness;
      var quantised = (light.nDotL * bands).floorToDouble() / bands;
      final fraction = fract(light.nDotL * bands);
      quantised += smoothstep(0.85, 1.0, fraction) / bands;
      // AccumulateLights multiplies by N.L, which is what the banding is meant
      // to replace, so divide it back out and keep the ramp.
      final ramp = quantised / math.max(light.nDotL, 1e-3);
      return s.albedo * ramp;
    },
  ).scaled(s.occlusion);

  // The rim belongs to the view, not to any one light: inside the loop it
  // would brighten with the number of lamps in the scene.
  final specularStrength = b.vec4('FragInfo', 'material', Vector4.zero()).w;
  final rim =
      math.pow(1.0 - s.nDotV, 3.0).toDouble() * specularStrength * 0.35;
  final ambient =
      (s.albedo.clone()..multiply(s.ambient + sampleLightmap(v, b, c)))
          .scaled(s.occlusion);
  final total = lit + ambient + Vector3.all(rim) + s.emissive;
  final debug = writeDebugView(c, v, b, s, total);
  if (debug != null) return debug;
  return writeLit(
    c,
    v,
    b,
    color: total,
    alpha: s.alpha,
    normal: s.normal,
    roughness: s.roughness,
  );
}