run method
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,
);
}