readSurface function

Surface? readSurface(
  1. Float32List v,
  2. ShaderBindings bindings,
  3. FragmentContext c, {
  4. bool transformed = false,
})

ReadSurface from surface.glsl.

Null is the GLSL's discard: a masked material's fragment under its cutoff. Every model that calls this hands the null on, the way a discarded fragment reaches no lighting loop. Transcribed once the picking stage needed the same hole — an id pass that saw a fence where the picture shows the thing behind it would be picking something the eye cannot see — and a scene pass without it would then have disagreed with the pick.

transformed is the layered stage's MapUv — see mapUv.

Implementation

Surface? readSurface(
  Float32List v,
  ShaderBindings bindings,
  FragmentContext c, {
  bool transformed = false,
}) {
  final (u: mapU, v: mapV, footprint: uv) = mapUv(
    kMapBaseColor,
    v,
    bindings,
    c,
    transformed: transformed,
  );
  final tint = bindings.vec4('FragInfo', 'base_color', Vector4(1, 1, 1, 1));
  final texture = bindings.textures['base_color_texture'];
  final texel = texture == null
      ? Vector4(1, 1, 1, 1)
      : texture.sample(
          mapU,
          mapV,
          du: uv.du,
          dv: uv.dv,
          // The albedo is the one map a floor's checks live in, and the one
          // this engine's samplers ever ask taps for. The other maps below
          // pass the axis maxima alone, which is what they always did.
          dudx: uv.dudx,
          dvdx: uv.dvdx,
          dudy: uv.dudy,
          dvdy: uv.dvdy,
        );

  // Texture and tint are sRGB; the vertex colour is authored linear, per glTF.
  final albedo = Vector3(
    toLinear(texel.x) * toLinear(tint.x) * v[kVColour],
    toLinear(texel.y) * toLinear(tint.y) * v[kVColour + 1],
    toLinear(texel.z) * toLinear(tint.z) * v[kVColour + 2],
  );
  final textured = texel.w * tint.w * v[kVColour + 3];

  // Alpha masking, glTF's third alpha mode, before anything else for the
  // reason the GLSL gives: a discarded fragment should not pay for the
  // lighting loop. A negative cutoff is "not masked", the encoding the engine
  // writes into `material2.x`.
  final cutoff = bindings
      .vec4('FragInfo', 'material2', Vector4(-1.0, 1.0, 1.0, 1.0))
      .x;
  if (cutoff > 1.0) {
    // `P7`'s coverage, which never reaches this rasteriser: it answers false
    // to `supportsAlphaToCoverage`, so the engine writes the plain cutoff.
    // Cut at the cutoff it carries rather than keep a fragment that one
    // sample a pixel has no coverage to spread.
    if (textured < cutoff - 1.0) return null;
  } else if (cutoff >= 0.0) {
    if (textured < cutoff) return null;
  } else if (cutoff < -1.5) {
    // `gfx-16n`: hashed, the fourth mode. Anchored to the world rather than
    // to the screen so the pattern travels with the surface, and stays put
    // through a shift of the origin — see [hashedAlphaNoise].
    final noise = hashedAlphaNoise(
      v[kVWorld],
      v[kVWorld + 1],
      v[kVWorld + 2],
      cutoff,
    );
    if (textured < noise) return null;
  }
  // What survives a mask's cut is opaque, as `surface.glsl` writes it.
  final alpha = cutoff >= 0.0 && cutoff <= 1.0 ? 1.0 : textured;

  final normal = Vector3(v[kVNormal], v[kVNormal + 1], v[kVNormal + 2]);
  final length = normal.length;
  if (length > 1e-6) normal.scale(1.0 / length);
  // `if (!gl_FrontFacing) s.n = -s.n;`: the back of a double-sided surface
  // is lit from its own side.
  if (!c.frontFacing) normal.negate();

  final material = bindings.vec4('FragInfo', 'material', Vector4.zero());
  final camera = bindings.vec4('FragInfo', 'camera_position', Vector4.zero());
  final world = Vector3(v[kVWorld], v[kVWorld + 1], v[kVWorld + 2]);
  // `P7`: against the view axis through an orthographic lens — `TowardsEye`.
  // The stages without the fog block keep the eye's point, as the GLSL's
  // `F3D_NO_FOG` ones do, and read as perspective here.
  final view = orthographic(bindings)
      ? towardsEye(v, bindings)
      : Vector3(camera.x, camera.y, camera.z) - world;
  final viewLength = view.length;
  if (viewLength > 1e-6) view.scale(1.0 / viewLength);

  // Hemispheric, blended on the geometric normal before any map perturbs it:
  // ambient of this kind says which half of the world a face can see, and
  // millimetres of bump relief are not an answer to that.
  final sky = bindings.vec4('FragInfo', 'ambient_sky', Vector4(1, 1, 1, 1));
  final ground = bindings.vec4(
    'FragInfo',
    'ambient_ground',
    Vector4(1, 1, 1, 1),
  );
  final up = normal.y * 0.5 + 0.5;
  final ambient = Vector3(
    (ground.x + (sky.x - ground.x) * up) * material.z,
    (ground.y + (sky.y - ground.y) * up) * material.z,
    (ground.z + (sky.z - ground.z) * up) * material.z,
  );

  // `L5`: what `g_albedo` carries into the albedo buffer, sRGB-encoded as
  // `WriteSurfaceGeometry` stores it.
  c.albedo = Vector4(
    toSrgb(albedo.x.clamp(0.0, 1.0)),
    toSrgb(albedo.y.clamp(0.0, 1.0)),
    toSrgb(albedo.z.clamp(0.0, 1.0)),
    1.0,
  );
  return Surface(
    albedo,
    alpha,
    normal,
    world,
    ambient,
    material.x.clamp(0.0, 1.0),
    material.y.clamp(0.02, 1.0),
    view,
    math.max(normal.dot(view), 1e-4),
    Vector4(v[kVTangent], v[kVTangent + 1], v[kVTangent + 2], v[kVTangent + 3]),
  );
}