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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions