bind method
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,
) {
pass.setCullMode(renderCullMode);
pass.setWindingOrder(gpu.WindingOrder.clockwise);
final shader = fragmentShaderForLighting(lighting);
if (shadingModel == FmatShadingModel.shadowCatcher) {
_packEngineFragInfo(lighting, lighting.environmentMap);
pass.bindUniform(
shader.cachedUniformSlot('FragInfo'),
transientsBuffer.emplace(_fragInfoBytes),
);
// The catcher's generated fragment samples only the shadow atlas, the
// occlusion chain, and (in its shadow variant) the punctual textures.
// The radiance/BRDF/SH samplers and the fog block are compiled out of
// it, and binding an absent slot fails, so it takes its own bind set.
EngineLightingUniforms.bindShadowCatcherTextures(pass, shader, lighting);
} else {
final lit = shadingModel != FmatShadingModel.unlit;
final env = environment ?? lighting.environmentMap;
// An unlit material that declares engine inputs carries the FragInfo
// block too (the screen-UV mapping, the input gates, and the camera
// basis its accessors unproject with), but none of the lighting
// samplers, so it costs no texture unit. The generated shader declares
// the block only in that case, so this condition must match the
// emitter's.
if (lit || _sceneInputs.isNotEmpty) {
_packEngineFragInfo(lighting, env);
pass.bindUniform(
shader.cachedUniformSlot('FragInfo'),
transientsBuffer.emplace(_fragInfoBytes),
);
if (_sceneInputs.isNotEmpty) {
EngineLightingUniforms.bindSceneInputTextures(
pass,
shader,
lighting,
_sceneInputs,
);
}
}
if (lit) {
// TODO(material-permutations): add an SSAO sampler to filtered scene
// color permutations without exceeding the backend sampler limit.
EngineLightingUniforms.bindEngineTextures(
pass,
shader,
lighting,
env,
bindSsao:
_environmentLighting &&
!_sceneInputs.contains(RenderInput.filteredSceneColor),
bindEnvironment: _environmentLighting,
bindShadows: lighting.shadowMap != null,
bindDiffuseSh: !usesLightmapVariant,
cubeShader: usesRadianceCubeVariant(lighting),
);
if (usesLightmapVariant) {
bindLightmap(pass, shader, transientsBuffer);
}
if (_usesPlanarReflection) {
_bindPlanarReflection(pass, shader, transientsBuffer, lighting);
}
// Lit `.fmat` shaders include the lighting framework (and thus
// fog.glsl), so they carry the FogInfo block. Unlit `.fmat` shaders do
// not; fog on those is a TODO(fog): give the unlit `.fmat` template
// the fog block.
EngineLightingUniforms.bindFog(
pass,
shader,
transientsBuffer,
lighting,
);
}
}
// Every shading model's Surface() and debug hook may read
// GetViewDirection.
EngineLightingUniforms.bindViewInfo(
pass,
shader,
transientsBuffer,
lighting,
);
parameters.bind(pass, shader, transientsBuffer);
// Bind the fragment keep-alive block (name matches kFragmentKeepAliveBlock
// in the emitter) to zero. The generated fragment references every
// declared resource through it (the MaterialParams block, any unreferenced
// sampler, and the engine scene inputs) so none can be optimized out; the
// emitter declares it whenever the material has a parameter or an engine
// input, so this condition must match. `planar_reflection` is an engine
// input to the emitter but not a scene input here, so it is named
// separately.
if (parameters.hasAnyParameters ||
_sceneInputs.isNotEmpty ||
_usesPlanarReflection) {
pass.bindUniform(
shader.cachedUniformSlot('FragmentKeepAlive'),
transientsBuffer.emplace(_zeroKeepAlive),
);
}
}