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.