Skip to content

fix: Plumb material prop to MeshTextureLayer so rasters can render unlit#613

Open
charlieforward9 wants to merge 1 commit into
developmentseed:mainfrom
charlieforward9:feat/material-prop-unlit-raster
Open

fix: Plumb material prop to MeshTextureLayer so rasters can render unlit#613
charlieforward9 wants to merge 1 commit into
developmentseed:mainfrom
charlieforward9:feat/material-prop-unlit-raster

Conversation

@charlieforward9

Copy link
Copy Markdown

Closes #612

Problem

Raster imagery drawn through MeshTextureLayer (so RasterLayer, RasterTileLayer, and COGLayer) is uniformly darkened whenever the deck.gl scene has a LightingEffect with ambient intensity < 1 — a common setup when the same scene lights 3D extrusions. Rendered pixels come out at roughly fileValue × ambientIntensity (we measured 40% and 60% in two production views), while BitmapLayer imagery in the same scene shows true values.

The default material: { ambient: 1.0, diffuse: 0.0, ... } intends to neutralize lighting, but the phong ambient term is material.ambient × ambientLight.color × ambientLight.intensity × surfaceColor — neutral only when the scene's ambient intensity is exactly 1.0. And there is no consumer-facing escape hatch: _subLayerProps can't reach the mesh layer through the tile-layer indirection.

Change

  • RasterTileLayer / RasterLayer: new optional material?: Material prop (deck.gl's standard type; false = unlit), forwarded down the chain only when set (...(material !== undefined && { material })) so the current default behavior is untouched for existing consumers. COGLayer inherits the prop via RasterTileLayer with no deck.gl-geotiff changes.
  • MeshTextureLayer.draw(): translates a falsy material into phongMaterial: { unlit: true } shader-module props.

The translation is needed because deck.gl v9 regressed material: false: LightingEffect.getShaderModuleProps() forwards the layer's raw material prop as the phongMaterial module props, and luma.gl's ShaderInputs.setProps() coerces falsy module props to {}

const moduleProps = props[moduleName] || {};

— which re-applies the default lit phong material (ambient 0.35, diffuse 0.6). The unlit escape hatch already exists in the shader (if (material.unlit) return surfaceColor; in @luma.gl/shadertools); it's just unreachable via material: false in v9. MeshTextureLayer.draw() already sets per-frame module props after the effect's props are applied (Layer._drawLayer order), so the translation deterministically wins. This also fixes material: false for anyone passing it directly to MeshTextureLayer today.

Result:

new COGLayer({ ..., material: false })  // renders pixel values verbatim, like BitmapLayer

We considered a raster-specific boolean (e.g. lighting: false) instead — happy to rename, but material follows the SimpleMeshLayer convention and gives custom-material control for free.

Testing

  • tests/raster-layer.test.ts: forwards material: false / material object to MeshTextureLayer; key absent (not undefined) when unset, pinning the default-preserving conditional spread.
  • tests/raster-tile-layer/material-forwarding.test.ts (new): same assertions across RasterTileLayer._renderSubLayersRasterLayer.
  • tests/mesh-layer.test.ts (new): material: falsesetProps called with phongMaterial: {unlit: true}; default and custom materials leave phongMaterial untouched.

pnpm --filter @developmentseed/deck.gl-raster test (146 passed), pnpm lint, pnpm --filter @developmentseed/deck.gl-raster typecheck all green (the docs workspace typecheck failure pre-exists on main — missing generated typedoc sidebar).

Verified end-to-end in our production app (AGROZOOM COG imagery over a scene lit at ambient 0.4/0.6): material: false renders identically to a patched unlit shader; omitting the prop reproduces today's behavior exactly.

…nlit

Raster imagery drawn through MeshTextureLayer is darkened by any scene
LightingEffect with ambient intensity < 1: the phong ambient term is
material.ambient * ambientIntensity * surfaceColor, so the ambient-only
default material is only lighting-neutral at intensity 1.0.

Expose the standard deck.gl `material` prop on RasterTileLayer and
RasterLayer, forwarded (only when set, preserving current defaults) down
to MeshTextureLayer. COGLayer inherits the prop via RasterTileLayer.

Because deck.gl v9 forwards a raw `material: false` to luma.gl where
falsy module props are coerced to {} (re-applying the default lit phong
material), MeshTextureLayer.draw() translates a disabled material into
phongMaterial {unlit: true}, which the shader already honors.

Closes developmentseed#612

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feat label Jul 3, 2026
@charlieforward9 charlieforward9 changed the title feat: Plumb material prop to MeshTextureLayer so rasters can render unlit fix: Plumb material prop to MeshTextureLayer so rasters can render unlit Jul 3, 2026
@github-actions github-actions Bot added fix and removed feat labels Jul 3, 2026
Comment on lines +178 to +190
// deck.gl documents `material: false` as the way to render a mesh unlit,
// but in deck.gl v9 the LightingEffect forwards the raw `false` to
// luma.gl's ShaderInputs, which coerces falsy module props to `{}` and so
// silently re-applies the *default* phong material (ambient 0.35,
// diffuse 0.6). Translate a disabled material into the phongMaterial
// module's `unlit` flag ourselves so raster values render verbatim (like
// `BitmapLayer`). This runs after the LightingEffect's module props are
// applied for the frame (`Layer._drawLayer` sets effect shaderModuleProps
// before calling `draw`), so it reliably wins.
if (!this.props.material) {
shaderProps.phongMaterial = { unlit: true };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like an upstream bug? Do you expect third party layers to have to know all this?

@kylebarron

Copy link
Copy Markdown
Member

Unless you can explain in plain language, without AI, what the problem is, we need this change, how we can test it, and how we can avoid regressions in the future, I'm going to close this. Because I don't understand what it's doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Raster imagery darkened by scene LightingEffect; no consumer control over mesh material/lighting

2 participants