Need Advice on an Infinite World Minimap / World Map in Godot (Lag, Ugly Visuals, Fog)

Godot Version

Godot 4.6.1 C#

Question

Hi everyone. I’m making a minimap + full world map for an infinite/procedural world in Godot, similar to a Minecraft-style map.

My current implementation is CPU-based: I sample terrain/biome colors into a texture, store exploration state in chunked bitmasks, cache static vegetation per chunk, draw markers in a separate overlay, and blend fog of war during map rendering.

The problem is that interaction feels very bad. Dragging and zooming are noticeably laggy, especially when the map is updating. I’m not sure whether this is just an optimization problem, or whether my overall map architecture is wrong for a very large/infinite world.

The second problem is visual quality. The map works, but it looks more like a debug/tool view than a polished in-game map. Terrain colors feel flat, the fog blending is not very nice, and the overall result is still ugly.

So I’d like to ask:

How do other games usually build minimaps/full maps for huge or infinite worlds?

Should this be chunk-based cached textures, shader-heavy rendering, LOD, or some combination?

In Godot, what is the best way to get smooth dragging/zooming and better-looking fog of war?

Should I focus on changing the rendering architecture first, or on shaders/visual polish first?

Any advice would be greatly appreciated.

Too many too broad questions, too little context provided.

At least show some game visuals and mockups of what type of map you’re aiming for.

Thanks, that’s fair. Here’s a more concrete summary of my current implementation.

My map is currently CPU-generated into textures. The full map is rebuilt into a pixel buffer and uploaded to a texture; the minimap is similar but smaller and throttled. Explored state is stored in chunked bitmasks, static vegetation is cached per chunk with an LRU cache, and markers are drawn in a separate overlay using the same tile-to-screen projection. Fog of war is blended during map rendering.

A very simplified version of the current full-map pipeline looks like this:

ForceFullRefresh(centerTileX, centerTileZ, zoom):
    exploredMask = FillExploredRect(visibleTileRect)
    PrewarmVisibleChunks(visibleChunkRect)
    BuildFogDistanceField(exploredMask)

    for each pixel (px, py):
        tx = centerTileX + round((px - half) * zoom)
        tz = centerTileZ + round((py - half) * zoom)

        color = SampleBiomeColor(tx, tz)
        color = BlendStaticVegetation(color, tx, tz)
        color = ApplyFog(color, tx, tz, exploredMask, fogDistanceField)

        buffer[px, py] = color

    UploadTexture(buffer)

And interaction currently works roughly like this:

OnDrag(deltaPixels):
    centerTile += deltaPixels * zoom
    RequestFullMapRefresh()

OnZoom(step):
    zoom = NextZoomLevel(zoom, step)
    RequestFullMapRefresh()

So my narrower question is:

Is this “rebuild the full texture on the CPU” approach reasonable for a very large/infinite world map in Godot?

For smooth dragging/zooming, should I keep a cached map texture and transform it first, then only rebuild when necessary?

For fog of war, would it be better to keep fog as a separate mask and combine it in a shader instead of baking it directly into the map texture?

I’ll also attach screenshots/mockups of both my current result and the visual style I’m aiming for.

Is this “rebuild the full texture on the CPU” approach reasonable for a very large/infinite world map in Godot?

During a loading screen sure, maybe during gameplay if its not going to cause stutters on your minimum spec hardware. The Debugger has tools to see how much RAM/VRAM is being used and to graph frametime/rate etc.

For smooth dragging/zooming, should I keep a cached map texture and transform it first, then only rebuild when necessary?

If the map is expected to change frequently, then you might want to consider a completely different approach. Something I saw elsewhere, you could use a proxy of the world (like collision polygons or low LOD versions) in GPU and render it differently with a shader, then output that to the minimap texture. This all depends on what exactly you’re going for though.

Edit:

I just read a bit deeper and you should absolutely be doing for each pixel (px, py): as a fragment shader. You could optimize it by skipping pixels and blending between them or something like that, but if you’re doing this on the GPU it will be very cheap. For reference, we do masking of parent/child Node2Ds which essentially renders everything twice but we can have many of instances of it (and it runs per-frame) until performance becomes a problem for the raspberry pi 5 which is my target. Likewise, the implementation of rendering a proxy of the world is extremely cheap for that game since its on the GPU, its cheaper than rendering the world normally since they render a wireframe without textures/effects.

Thanks, this is very helpful.

I think the part I’m still confused about is how to split the map into the “right” layers.

Right now my terrain + static vegetation are baked into the map texture on the CPU, while markers are drawn in a separate overlay with their own projection/hit detection. Fog of war is currently blended during map rendering. My concern is about dynamic actors like animals/monsters: if I bake them into the map texture, I lose interactivity on the full-screen map; but if I draw everything separately, I’m worried about performance and complexity.

What I ultimately want is:

a smooth draggable full-screen map,

smooth wheel zoom (continuous, not only 2x/4x steps),

good-looking fog transitions,

and interactive map objects when the player opens the full map.

So would the better architecture be something like:

terrain + static vegetation as cached map textures,

fog as a separate mask/shader layer,

dynamic actors / markers as a separate interactive overlay,

and drag/zoom applied as view transforms first, with delayed map re-rendering only when needed?

If that sounds like the right direction, I’d be really grateful for confirmation before I go deeper into rewriting this system. I feel a bit lost on the architectural side, so any guidance from people who’ve built large-world maps in Godot would mean a lot.