run method

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

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

Implementation

@override
Vector4? run(Float32List v, ShaderBindings bindings, FragmentContext c) {
  final s = readSurface(v, bindings, c);
  // Null is the GLSL's `discard`, which `ReadSurface` performs for a masked
  // material under its cutoff — the emitted shader gets it from the same
  // header, so both sides drop the same fragments.
  if (s == null) return null;

  final footprint = uvFootprint(c);
  // `P8`: a lighting hook makes this a lit model — the maps applied, the
  // lights gathered through the block — as the emitted shader does.
  final light = program.light;
  if (light != null) applyCommonMaps(s, v, bindings, c);
  final inputs = <String, List<double>>{
    'albedo': <double>[s.albedo.x, s.albedo.y, s.albedo.z],
    'alpha': <double>[s.alpha],
    'normal': <double>[s.normal.x, s.normal.y, s.normal.z],
    'view': <double>[s.view.x, s.view.y, s.view.z],
    'nDotV': <double>[s.nDotV],
    'metallic': <double>[s.metallic],
    'roughness': <double>[s.roughness],
    'occlusion': <double>[s.occlusion],
    'emissive': <double>[s.emissive.r, s.emissive.g, s.emissive.b],
    'ambient': <double>[s.ambient.x, s.ambient.y, s.ambient.z],
    'uv': <double>[
      v[CpuMeshLayout.varyingUv],
      v[CpuMeshLayout.varyingUv + 1],
    ],
    'world': <double>[s.world.x, s.world.y, s.world.z],
    'instance': <double>[
      v[CpuMeshLayout.varyingInstance],
      v[CpuMeshLayout.varyingInstance + 1],
      v[CpuMeshLayout.varyingInstance + 2],
      v[CpuMeshLayout.varyingInstance + 3],
    ],
  };
  // `P8`: the draw's `MaterialParams`, which is `RenderMaterial.parameters`,
  // member by member as the GLSL reads its block — or the block of the
  // material's own a uniform names (D21).
  final uniforms = <String, List<double>>{
    for (final parameter in program.parameters)
      if (parameter.uniform)
        parameter.name: ?bindings.read(parameter.block!, parameter.name),
  };
  List<double> sample(MaterialTextureSlot slot, double u, double w) {
    final texture = bindings.textures[slot.bindingName];
    // White is what `surface.glsl` falls back to for an unbound base
    // colour, so a material sampling a slot this draw did not fill
    // looks the same on both sides.
    if (texture == null) return const <double>[1, 1, 1, 1];
    final texel = texture.sample(u, w, du: footprint.du, dv: footprint.dv);
    return <double>[texel.x, texel.y, texel.z, texel.w];
  }

  if (light != null) {
    // `lit` as the emitted `main` adds it up: the lights through the
    // block, by radiance, n·l and shadow, then ambient and lightmap under
    // the occlusion, then the emissive — `materialLitInput` — or as the
    // version 2 hooks and switches say, through `composeMaterialLit`.
    // Version 3: the row of the light being shaded, which a block that
    // reads it gathers through `_rowLights`.
    var row = (kind: 0.0, params: Vector4.zero());
    Vector3 shade(Surface s, LightSample light) {
      final half = (light.direction + s.view)..normalize();
      final answer = evaluateMaterialLight(
        program,
        MaterialSurfaceValues(
          inputs: <String, List<double>>{
            ...inputs,
            'lightDir': <double>[
              light.direction.x,
              light.direction.y,
              light.direction.z,
            ],
            'halfDir': <double>[half.x, half.y, half.z],
            'nDotL': <double>[light.nDotL],
            'nDotH': <double>[light.nDotH],
            'vDotH': <double>[light.vDotH],
            'lightKind': <double>[row.kind],
            'lightParams': <double>[
              row.params.x,
              row.params.y,
              row.params.z,
              row.params.w,
            ],
            for (final name in program.lightTypes.keys)
              name: <double>[
                switch (uniforms[materialLightTypeMember(name)]) {
                  final code? when (row.kind - code[0]).abs() < 0.5 => 1.0,
                  _ => 0.0,
                },
              ],
          },
          uniforms: uniforms,
          sample: sample,
        ),
      );
      return Vector3(answer[0], answer[1], answer[2]);
    }

    final direct = program.readsLightRow
        ? _rowLights(
            s,
            bindings,
            c,
            shade,
            directional: program.state.directional,
            onRow: (kind, params) => row = (kind: kind, params: params),
          )
        : program.state.directional
        ? accumulateLights(s, bindings, c, shade: shade, shadowed: true)
        : _withoutDirectional(s, bindings, c, shade);
    final lightmap = sampleLightmap(v, bindings, c);
    final lit = composeMaterialLit(
      program,
      MaterialSurfaceValues(
        inputs: inputs,
        uniforms: uniforms,
        sample: sample,
      ),
      direct: <double>[direct.x, direct.y, direct.z],
      lightmap: <double>[lightmap.x, lightmap.y, lightmap.z],
    );
    inputs['lit'] = lit;
  }

  // Version 2: this fragment's depth along the view axis, and the opaque
  // scene's behind it, as the emitted `main` reads them.
  if (program.inputsUsed.contains('viewDepth') || program.readsSceneDepth) {
    final depth = viewDepth(v, bindings);
    inputs['viewDepth'] = <double>[depth];
    if (program.readsSceneDepth) {
      final behind = _sceneDepthAt(bindings, c);
      final eye = bindings.vec4('FogInfo', 'eye', Vector4.zero());
      final forward = bindings.vec4('FogInfo', 'forward', Vector4.zero());
      final orthographic =
          bindings.vec4('FogInfo', 'projection', Vector4.zero()).x > 0.5;
      final at = orthographic
          ? s.world +
                Vector3(forward.x, forward.y, forward.z) * (behind - depth)
          : Vector3(eye.x, eye.y, eye.z) +
                (s.world - Vector3(eye.x, eye.y, eye.z)) *
                    (behind / (depth > 1e-6 ? depth : 1e-6));
      inputs['sceneDepth'] = <double>[behind];
      inputs['scenePosition'] = <double>[at.x, at.y, at.z];
    }
  }

  final result = evaluateMaterial(
    program,
    MaterialSurfaceValues(inputs: inputs, uniforms: uniforms, sample: sample),
  );

  // `blend premultiplied`: the colour is already times its alpha, and
  // `writeLit` weighs a blended surface's by it — so it is divided out
  // first, where there is an alpha to divide by. At nought the colour is
  // light added through a surface that covers nothing, which the blend
  // equation keeps and this one cannot; it is the emitted stage's case
  // alone, and a golden would show it.
  final premultiplied =
      program.state.blend == MaterialBlend.premultiplied &&
      premultiplies(bindings);
  final undo = premultiplied && result[3] > 0.0 ? 1.0 / result[3] : 1.0;

  // `WriteSurface(rgb, a)` in `color.glsl`, which is the one-argument form:
  // a surface that cannot say how polished it is is written fully rough, so
  // nothing reflects off it. The same call `unlit.frag` makes and the same
  // transcription `UnlitShader` makes of it.
  return writeLit(
    c,
    v,
    bindings,
    color: Vector3(result[0] * undo, result[1] * undo, result[2] * undo),
    alpha: result[3],
    normal: s.normal,
    roughness: 1,
  );
}