Skip to content

3D: mipmapped minification + anisotropic texture filtering (completes textureFilter) #1511

Description

@obiot

Summary

Add mipmapped minification (trilinear) and optional anisotropic filtering for WebGL textures, so high-frequency textures stop shimmering/aliasing when minified (viewed small, at a distance, or at grazing angles). Extends the textureFilter work from #1506.

Motivation / scope

The mesh + sprite texture path currently sets TEXTURE_MIN_FILTER to the same value as TEXTURE_MAG_FILTER (gl.LINEAR or gl.NEAREST) — no mipmaps. That means a minified high-frequency texture (brick, foliage, text on a sign, photographic detail) seen at distance or oblique angles aliases/shimmers under motion, because each fragment samples one texel of a much larger image.

This is NOT needed for melonJS's stylized low-poly target — low-poly kits texture with flat/palette colors (e.g. Kenney colormap = large flat color blocks, very low-frequency), which barely alias under minification; plain LINEAR already looks clean. So this is a quality-completion for detailed-texture 3D, deliberately deferred out of #1506, not core.

Where it pays off: textured 3D with detailed/high-frequency maps viewed at varying depth (a textured ground plane receding to the horizon, distant detailed props, oblique walls).

Current state

  • MaterialBatcher.createTexture2D already has a mipmap param and a POT check (isPowerOfTwo(w) && isPowerOfTwo(h)), and WebGL 2 can mipmap NPOT — so some of the plumbing exists, but the min filter is never set to a *_MIPMAP_* mode.
  • WebGLRenderer._glTextureFilter() / getDefaultTextureFilter() (added in feat(gltf): material features Tier 2 + mesh-batcher perf + texture-filter decoupling #1506) resolve a single "linear"/"nearest" mode used for both min and mag.
  • setAntiAlias / setTextureFilter re-apply that single filter to both TEXTURE_MIN_FILTER and TEXTURE_MAG_FILTER.

Proposal

  1. Mipmapped min filter — when the resolved filter is linear and mipmaps are generated, set TEXTURE_MIN_FILTER to LINEAR_MIPMAP_LINEAR (trilinear) while MAG_FILTER stays LINEAR. For nearest, keep crisp (NEAREST / NEAREST_MIPMAP_NEAREST as appropriate). Generate mipmaps via gl.generateMipmap after upload (already partially wired); WebGL 1 requires POT — guard and fall back to plain LINEAR for NPOT on WebGL 1.
  2. Anisotropic filtering — query EXT_texture_filter_anisotropic (getExtension + MAX_TEXTURE_MAX_ANISOTROPY_EXT), expose an Application setting (e.g. anisotropy: number or "auto"/level, default off/1) and apply texParameterf(TEXTURE_MAX_ANISOTROPY_EXT, level) clamped to the GPU max. Graceful no-op when the extension is absent.
  3. Decide the knob: likely fold into the existing textureFilter story — e.g. a mipmap/anisotropy Application setting, and/or a per-Mesh opt-in, mirroring Mesh.textureFilter.
  4. Update setAntiAlias/setTextureFilter/_reapplyTextureFilter so the MIN side picks the mipmapped variant (the MIN/MAG split the current code collapses).

Considerations

  • MIN vs MAG split: today _reapplyTextureFilter(filter) sets both to one enum — needs to become min/mag-aware.
  • POT / WebGL version: NPOT mipmaps need WebGL 2 (ties into WebGL2-only: drop WebGL1 path, adopt VAOs (per-flush attribute setup → bindVertexArray) #1509 WebGL2-only — simplifies if that lands first).
  • Memory/upload cost: mipmaps add ~33% texture memory + generation time; default conservatively (off, or only when textureFilter: "linear" and explicitly opted in).
  • Premultiplied alpha + mipmaps: verify generateMipmap interacts correctly with the engine's premultiplied-alpha uploads (edge darkening on transparent texels).
  • Canvas renderer: no-op (no per-texture filtering), consistent with textureFilter.

Acceptance criteria

  • A detailed-texture mesh/sprite minified in motion shows no shimmer with mipmaps enabled (visual check); plain path unchanged when disabled.
  • MIN_FILTER uses a *_MIPMAP_* mode when mipmaps are on; MAG unchanged.
  • Anisotropy applied up to the GPU max when the extension exists; clean no-op otherwise.
  • Default behavior unchanged (opt-in), so low-poly scenes pay nothing.
  • WebGL 1 NPOT falls back gracefully.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions