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 params = b.vec4('DecalInfo', 'params', Vector4.zero());
  final factor = params.w < 0.5;
  final keep = factor
      ? Vector4(1.0, 1.0, 1.0, 1.0)
      : Vector4(0.0, 0.0, 0.0, 1.0);
  final surfaceMap = b.textures['surface_texture'];
  final albedoMap = b.textures['albedo_texture'];
  if (surfaceMap == null || albedoMap == null) return keep;

  final u = v[0];
  final w = v[1];
  final surface = surfaceMap.sample(u, w);
  if (surface.w <= 0.0) return keep;

  final inverse = b.mat4('DecalInfo', 'inverse_view_projection');
  final camera = b.vec4('DecalInfo', 'camera', Vector4.zero());
  final forward = b.vec4('DecalInfo', 'forward', Vector4.zero());
  final view = b.vec4('DecalInfo', 'view', Vector4(0.0, 0.0, 1.0, 1.0));
  final eye = Vector3(camera.x, camera.y, camera.z);
  final axis = Vector3(forward.x, forward.y, forward.z);

  // `WorldAt`, with the target's coordinates taken into the view's.
  Vector3 worldAt(double atU, double atV, double depth) {
    final (:origin, :along) = pixelRay(
      inverse,
      (atU - view.x) / view.z,
      (atV - view.y) / view.w,
    );
    return origin +
        along * ((depth - (origin - eye).dot(axis)) / along.dot(axis));
  }

  final depth = surface.w;
  final at = worldAt(u, w, depth);
  final normal = decodeSurfaceNormal(surface.x, surface.y);

  // `WorldStep`: toward the neighbour nearer in depth.
  Vector3 worldStep(double du, double dv) {
    final ahead = surfaceMap.sample(u + du, w + dv);
    final behind = surfaceMap.sample(u - du, w - dv);
    final aheadGap = ahead.w > 0.0 ? (ahead.w - depth).abs() : 1e30;
    final behindGap = behind.w > 0.0 ? (behind.w - depth).abs() : 1e30;
    if (math.min(aheadGap, behindGap) >= 1e29) return Vector3.zero();
    return aheadGap <= behindGap
        ? worldAt(u + du, w + dv, ahead.w) - at
        : at - worldAt(u - du, w - dv, behind.w);
  }

  final dx = worldStep(params.y, 0.0);
  final dy = worldStep(0.0, params.z);
  final albedoTexel = albedoMap.sample(u, w);
  final albedo = Vector3(
    toLinear(albedoTexel.x),
    toLinear(albedoTexel.y),
    toLinear(albedoTexel.z),
  );

  final painted = albedo.clone();
  var kept = 1.0;
  final laid = Vector3.zero();
  final emitted = Vector3.zero();
  final count = (params.x + 0.5).floor();
  for (var i = 0; i < _maxDecals && i < count; i++) {
    final rowX = b.vec4('DecalInfo', 'axis_x', Vector4.zero(), at: i);
    final rowY = b.vec4('DecalInfo', 'axis_y', Vector4.zero(), at: i);
    final rowZ = b.vec4('DecalInfo', 'axis_z', Vector4.zero(), at: i);
    double row(Vector4 r, Vector3 p) => r.x * p.x + r.y * p.y + r.z * p.z;
    final lx = row(rowX, at) + rowX.w;
    final ly = row(rowY, at) + rowY.w;
    final lz = row(rowZ, at) + rowZ.w;
    if (lx.abs() > 0.5 || ly.abs() > 0.5 || lz.abs() > 0.5) continue;

    final fade = b.vec4('DecalInfo', 'fade', Vector4.zero(), at: i);
    final up = Vector3(rowY.x, rowY.y, rowY.z)..normalize();
    final facing = normal.dot(up);
    final byAngle = ((facing - fade.y) / math.max(fade.z, 1e-4)).clamp(
      0.0,
      1.0,
    );
    final byDepth = fade.w > 0.0
        ? ((0.5 - ly.abs()) / (fade.w * 0.5)).clamp(0.0, 1.0)
        : 1.0;

    final region = b.vec4('DecalInfo', 'region', Vector4.zero(), at: i);
    final pu = region.x + (lx + 0.5) * region.z;
    final pv = region.y + (lz + 0.5) * region.w;

    final slot = fade.x;
    final alongXu = row(rowX, dx) * region.z;
    final alongXv = row(rowZ, dx) * region.w;
    final alongYu = row(rowX, dy) * region.z;
    final alongYv = row(rowZ, dy) * region.w;

    final picture = _sampleSlot(
      b,
      slot,
      pu,
      pv,
      // The sampler picks the level from the larger axis in texels, which
      // is the footprint the GLSL computes by hand.
      du: math.max(alongXu.abs(), alongYu.abs()),
      dv: math.max(alongXv.abs(), alongYv.abs()),
    );
    final tint = b.vec4('DecalInfo', 'color', Vector4.zero(), at: i);
    final color = Vector3(
      toLinear(picture.x) * tint.x,
      toLinear(picture.y) * tint.y,
      toLinear(picture.z) * tint.z,
    );
    final alpha = (picture.w * tint.w * byAngle * byDepth).clamp(0.0, 1.0);
    final glow = b.vec4('DecalInfo', 'emissive', Vector4.zero(), at: i);

    _mixInto(painted, color, alpha);
    kept *= 1.0 - alpha;
    _mixInto(laid, color, alpha);
    _mixInto(
      emitted,
      Vector3(color.x * glow.x, color.y * glow.y, color.z * glow.z),
      alpha,
    );
  }

  final named =
      (math.max(albedo.x, math.max(albedo.y, albedo.z)) / _albedoFloor).clamp(
        0.0,
        1.0,
      );
  double swap(double paint, double was) =>
      1.0 + (paint - was) / math.max(was, _albedoFloor);
  if (factor) {
    final unlit = kept * (1.0 - named);
    return Vector4(
      swap(painted.x, albedo.x) * named + unlit,
      swap(painted.y, albedo.y) * named + unlit,
      swap(painted.z, albedo.z) * named + unlit,
      1.0,
    );
  }
  return Vector4(
    laid.x * (1.0 - named) + emitted.x,
    laid.y * (1.0 - named) + emitted.y,
    laid.z * (1.0 - named) + emitted.z,
    1.0,
  );
}