bind method

  1. @override
void bind(
  1. RenderPass pass,
  2. TransientWriter transientsBuffer,
  3. Lighting lighting
)
override

Binds this material's render-pass state, uniforms, and textures.

The base implementation enables back-face culling with clockwise winding on the Y-down rasterizer (accepting model-space CCW front faces). Subclasses must call super.bind and then bind any per-material uniforms and textures expected by their fragment shader. lighting carries the IBL EnvironmentMap (and its intensity) plus the analytic lights and shadow resources that materials shade against.

Implementation

@override
void bind(
  gpu.RenderPass pass,
  TransientWriter transientsBuffer,
  Lighting lighting,
) {
  _ensurePreparedVariant();
  final prepared = _preparedVariant;
  if (prepared != null) {
    prepared
      ..name = name
      ..doubleSided = doubleSided
      ..depthBias = depthBias
      ..lodFade = lodFade
      ..lightListOffset = lightListOffset
      ..lightListCount = lightListCount
      ..lightChannelMask = lightChannelMask
      ..modelScaleX = modelScaleX
      ..modelScaleY = modelScaleY
      ..modelScaleZ = modelScaleZ
      ..environment = environment;
    prepared.bind(pass, transientsBuffer, lighting);
    return;
  }
  super.bind(pass, transientsBuffer, lighting);

  // The variant the pipeline was built from, which differs from
  // [fragmentShader] when the bound environment picks the cube radiance
  // layout. Slots must come from the shader actually drawn with.
  final shader = fragmentShaderForLighting(lighting);
  final EnvironmentMap env = environment ?? lighting.environmentMap;

  // FragInfo std140 layout (624 bytes / 156 floats). EngineLightingUniforms
  // packs the shared engine lighting, image-based-lighting, and shadow
  // fields (identical for every lit material); this material fills only its
  // own, disjoint fields:
  //   [0..3]    vec4  color
  //   [4..7]    vec4  emissive_factor
  //   [120]     float vertex_color_weight
  //   [121]     float metallic_factor
  //   [122]     float roughness_factor
  //   [123]     float has_normal_map
  //   [124]     float normal_scale
  //   [125]     float occlusion_strength
  //   [132]     float alpha_mode (0 opaque, 1 mask, 2 blend)
  //   [133]     float alpha_cutoff
  //   [138]     float specular_aa_variance
  //   [139]     float specular_aa_threshold
  // A shared scratch (zeroed each bind, matching a fresh allocation's
  // unwritten slots) instead of a per-draw allocation; emplace below copies
  // the bytes out immediately.
  final fragInfo = _fragInfoScratch..fillRange(0, _fragInfoScratch.length, 0);
  EngineLightingUniforms.packInto(
    fragInfo,
    lighting,
    env,
    nodeChannelMask: lightChannelMask,
    modelScaleX: modelScaleX,
    modelScaleY: modelScaleY,
    modelScaleZ: modelScaleZ,
  );
  fragInfo[0] = baseColorFactor.r;
  fragInfo[1] = baseColorFactor.g;
  fragInfo[2] = baseColorFactor.b;
  fragInfo[3] = baseColorFactor.a;
  fragInfo[4] = emissiveFactor.r;
  fragInfo[5] = emissiveFactor.g;
  fragInfo[6] = emissiveFactor.b;
  fragInfo[7] = emissiveStrength;
  fragInfo[120] = vertexColorWeight;
  fragInfo[121] = metallicFactor;
  fragInfo[122] = roughnessFactor;
  fragInfo[123] = normalTexture != null ? 1.0 : 0.0;
  fragInfo[124] = normalScale;
  fragInfo[125] = occlusionStrength;
  fragInfo[132] = alphaMode.index.toDouble();
  fragInfo[133] = alphaCutoff;
  fragInfo[138] = specularAntiAliasingVariance;
  fragInfo[139] = specularAntiAliasingThreshold;
  // dielectric_f0 [172..174]: packInto wrote the plain 0.04; a scalar ior,
  // specular factor, or specular color replaces it with their product so
  // the draw stays on the standard shader.
  if (_hasScalarSpecularConfiguration) {
    final f0 = dielectricSpecularF0(
      ior: ior,
      specular: specular,
      specularColor: specularColor,
    );
    fragInfo[EngineLightingUniforms.dielectricF0Index] = f0.x;
    fragInfo[EngineLightingUniforms.dielectricF0Index + 1] = f0.y;
    fragInfo[EngineLightingUniforms.dielectricF0Index + 2] = f0.z;
  }
  fragInfo[EngineLightingUniforms.fadeIndex] = lodFade;
  // radiance_blend.zw [162]/[163]: this item's punctual-light slice
  // (count, offset) into the per-frame light-index buffer.
  fragInfo[162] = lightListCount.toDouble();
  fragInfo[163] = lightListOffset.toDouble();
  pass.bindUniform(
    shader.getUniformSlot("FragInfo"),
    transientsBuffer.emplace(_fragInfoBytes),
  );

  final textureTransforms = _textureTransformsScratch;
  _packTextureTransform(
    textureTransforms,
    0,
    baseColorTextureTransform,
    baseColorTextureTexCoord,
  );
  _packTextureTransform(
    textureTransforms,
    8,
    metallicRoughnessTextureTransform,
    metallicRoughnessTextureTexCoord,
  );
  _packTextureTransform(
    textureTransforms,
    16,
    normalTextureTransform,
    normalTextureTexCoord,
  );
  _packTextureTransform(
    textureTransforms,
    24,
    emissiveTextureTransform,
    emissiveTextureTexCoord,
  );
  _packTextureTransform(
    textureTransforms,
    32,
    occlusionTextureTransform,
    occlusionTextureTexCoord,
  );
  // The base record's padding float carries one flag the shader branches on
  // to skip the five UV-transform evaluations, set when any record
  // transforms its UVs or selects UV set 1. The identity transform
  // reproduces the raw UV bit-exactly, so the flag only gates work.
  final transformedUvs =
      !baseColorTextureTransform.isIdentity ||
      !metallicRoughnessTextureTransform.isIdentity ||
      !normalTextureTransform.isIdentity ||
      !emissiveTextureTransform.isIdentity ||
      !occlusionTextureTransform.isIdentity ||
      baseColorTextureTexCoord != 0 ||
      metallicRoughnessTextureTexCoord != 0 ||
      normalTextureTexCoord != 0 ||
      emissiveTextureTexCoord != 0 ||
      occlusionTextureTexCoord != 0;
  textureTransforms[7] = transformedUvs ? 1.0 : 0.0;
  pass.bindUniform(
    shader.getUniformSlot('TextureTransforms'),
    transientsBuffer.emplace(ByteData.sublistView(textureTransforms)),
  );

  _bindSlot(pass, shader, 'base_color_texture', baseColorTexture);
  _bindSlot(pass, shader, 'emissive_texture', emissiveTexture);
  _bindSlot(
    pass,
    shader,
    'metallic_roughness_texture',
    metallicRoughnessTexture,
  );
  _bindSlot(pass, shader, 'normal_texture', normalTexture, normal: true);
  _bindSlot(pass, shader, 'occlusion_texture', occlusionTexture);
  // Image-based-lighting atlas, BRDF LUT, and shadow map. Shared with
  // PreprocessedMaterial: the sampler choices (radiance repeat/clamp, LUT
  // clamp/clamp, shadow nearest/clamp) and the white shadow placeholder
  // live in EngineLightingUniforms.
  EngineLightingUniforms.bindEngineTextures(
    pass,
    shader,
    lighting,
    env,
    // The no-shadow twin declares no shadow_map sampler; the same call
    // that picked `shader` above decides whether the slot exists.
    bindShadows: !usesNoShadowVariant(lighting),
    bindDiffuseSh: !_usesLightmapVariant,
    cubeShader: usesRadianceCubeVariant(lighting),
  );
  // The same condition picked `shader`, so the lightmap goes to a shader
  // that declares its sampler.
  if (_usesLightmapVariant) {
    EngineLightingUniforms.bindLightmap(
      pass,
      shader,
      transientsBuffer,
      texture: resolveTextureSource(lightmapTexture),
      transform: lightmapTextureTransform,
      texCoord: lightmapTextureTexCoord,
      intensity: lightmapIntensity,
      rgbm: lightmapRgbm,
      sampler: textureSourceSampler(lightmapTexture),
    );
  }
  EngineLightingUniforms.bindFog(pass, shader, transientsBuffer, lighting);
}