Baking from a script

The editor's lightmap tool (Cmd L, Bake) is the usual way to bake. A build pipeline that bakes every map without opening the editor runs the same calls:

extends SceneTree

var map: HammeriteMap = null
var _frames: int = 0

func _initialize() -> void:
    HammeriteTextureLibrary.search_paths = ["res://textures"]
    map = HammeriteDataFile.read("res://maps/level.hmap") as HammeriteMap
    # Built into the scene as the game builds it: a light is found through the node its entity
    # builds, so a map that is only read has nothing to bake with.
    var map_3d := HammeriteMap3D.new()
    root.add_child(map_3d)
    map_3d.map = map

func _process(_delta: float) -> bool:
    _frames += 1
    if _frames < 3:
        return false                                  # let the scene finish building

    _bake()
    return true

func _bake() -> void:
    var settings := HammeriteBakeSettings.new()        # or load one kept beside the map
    settings.bounces = 4
    var lightmapper := HammeriteLightmapper.new(map, settings)
    if await lightmapper.bake(_progress) != OK:       # probes, patches, light, atlases
        push_error("could not bake: this needs the GPU")
        quit(1)
        return
    HammeriteDataFile.write(map, "res://maps/level.hmap", false, true)
    quit()

func _progress(stage: String, current: int, total: int) -> void:
    print("%s %d/%d" % [stage, current, total])

Run it with the GPU available - not --headless, which has no rendering device:

godot --path <project> --script res://tools/bake.gd
  • Everything a bake reads besides the map is a HammeriteBakeSettings: bounces, the light falloff, how much bounce and emission, the probe grid. It is a resource, so a map's can be kept beside it and every bake of the map made the same; each lightmapper reads only its own.
  • A map with variants bakes the one in effect. Choose one with map.set_active_variant() before baking; each variant's last bake is kept with the map.
  • map.get_bake_warnings() lists what the bake could not do: faces too large for an atlas, more than four light groups on a face.
  • Baking needs the Forward+ or Mobile renderer; see Installing.

Baking while a game or tool keeps running

Set yield_to and the lightmapper gives that SceneTree a frame every tenth of a second or so, so a window can draw its progress; bake() is then awaited. cancel() stops it at the next of those frames, and the lighting the map had is put back as it was:

lightmapper.yield_to = get_tree()
cancel_button.pressed.connect(lightmapper.cancel)
match await lightmapper.bake(_progress):
    OK: print("baked")
    ERR_SKIP: print("cancelled; the lighting is as it was")
    ERR_UNAVAILABLE: print("this renderer cannot bake")

The steps bake() takes are there for a tool that has to do something between them: map.generate_light_probes(), then prepare(), transfer(), write_lightmaps() and write_probes(), in that order and each awaited. A tool taking them itself puts the lighting back on a cancel (map.snapshot_lighting() before, map.restore_lighting() after).

Nothing may edit the map while it runs: a bake lights the world as it was when it began.

Hammerite 1.0 · built from a6dd634, 2026-10-10