Summary
melonJS has no distance fog — no hits for fog anywhere in src/. For a 3D scene it is the cheapest single thing that stops geometry looking flat and pasted-on: it gives depth cues the projection alone does not, and it hides the far plane so props can pop in without a visible edge.
Every outdoor 3D scene wants it, and there is currently no supported way to get it.
What exists today
- A custom mesh shader.
mesh.addPostEffect(new GLShader(...)) replaces the built-in shading, and the mesh contract passes uProjectionMatrix / uViewMatrix / uModelMatrix with aVertex, so view-space depth → mix(color, uFogColor, f) is a few lines, dual-language GLSL + WGSL in one object. This works, but it means giving up the built-in lit/unlit shading to get fog, and writing it per project.
- CPU-side tinting of discrete props by distance from the camera. Fine for scattered rocks and trees, wrong for large ground meshes where the gradient has to vary across a single mesh.
- A post-effect cannot do it:
toFrameTexture() is colour only, and no depth texture is exposed to ShaderEffect (WebGPU's depthTexture is internal to the pipeline). Screen-space fog needs depth, so this route is closed without also surfacing a depth attachment.
Proposal
Fog as renderer state consumed by the mesh batchers, so it composes with the existing lit and unlit paths instead of replacing them.
- application setting and a runtime accessor:
fog: { color, near, far } (or density for exponential)
- uniforms uploaded alongside the existing light block; linear and exp² are both one line in the shader
- applies to
Mesh, InstancedMesh and Sprite3d, after lighting and emissive so a glowing object still fades with distance
- no-op when unset, so nothing changes for existing scenes
- Canvas has no shader path, so it ignores fog like the rest of the 3D tier
Open questions
- Whether fog belongs to the camera (it is a viewing property, and a scene with two cameras could reasonably want two settings) or the application.
Camera3d already owns the clip planes fog is defined against, which argues for the camera.
- Whether to fog the clear colour too, or leave that to the app — a sky that does not match the fog colour is the usual way this looks wrong.
- Whether
Sprite3d billboards should fog by their centre depth or per-fragment.
Notes
Found while checking whether melonJS could build a sledding game from a screenshot — the far mountains fade toward the sky colour, which is what makes the valley read as deep. Everything else in the scene mapped onto existing features; fog and a 3D trail (#1621) were the two gaps.
Summary
melonJS has no distance fog — no hits for
foganywhere insrc/. For a 3D scene it is the cheapest single thing that stops geometry looking flat and pasted-on: it gives depth cues the projection alone does not, and it hides the far plane so props can pop in without a visible edge.Every outdoor 3D scene wants it, and there is currently no supported way to get it.
What exists today
mesh.addPostEffect(new GLShader(...))replaces the built-in shading, and the mesh contract passesuProjectionMatrix/uViewMatrix/uModelMatrixwithaVertex, so view-space depth →mix(color, uFogColor, f)is a few lines, dual-language GLSL + WGSL in one object. This works, but it means giving up the built-in lit/unlit shading to get fog, and writing it per project.toFrameTexture()is colour only, and no depth texture is exposed toShaderEffect(WebGPU'sdepthTextureis internal to the pipeline). Screen-space fog needs depth, so this route is closed without also surfacing a depth attachment.Proposal
Fog as renderer state consumed by the mesh batchers, so it composes with the existing lit and unlit paths instead of replacing them.
fog: { color, near, far }(ordensityfor exponential)Mesh,InstancedMeshandSprite3d, after lighting and emissive so a glowing object still fades with distanceOpen questions
Camera3dalready owns the clip planes fog is defined against, which argues for the camera.Sprite3dbillboards should fog by their centre depth or per-fragment.Notes
Found while checking whether melonJS could build a sledding game from a screenshot — the far mountains fade toward the sky colour, which is what makes the valley read as deep. Everything else in the scene mapped onto existing features; fog and a 3D trail (#1621) were the two gaps.