My foliage system creates mesh instances from the RenderingServer to make use of per-instance occlusion culling. They are not part of the scene tree and thus are not included in LightmapGI bakes.
However, my instances are effectively persistent as they are generated deterministically in EnterTree() and freed only at ExitTree(), so it would be unfortunate to have to use dynamic GI or return to batched multimeshinstance3ds.
I assume Meshinstances draw themselves with RenderingServer instances under the hood somewhere, and LightMapGI recognizes those, but I haven’t found any way to hijack that process. (It seems hard-coded to only scan for mesh nodes)
Is there a way to get LightMapGI to recognize RenderingServer Instances or should I settle for a compromise?
That function doesn’t seem to light the foliage and lightmap.lightdata.getusercount doesn’t report any new users; maybe I’m using it wrong?
My code is roughly:
Rid LightMapRid = lightmapGI.GetInstance();
Vector2I texSize = new Vector2I(
lightmapGI.LightData.LightmapTextures[0].GetWidth(),
lightmapGI.LightData.LightmapTextures[0].GetHeight());
}
for(int i=0;i<count;i++) {
int pixelPortion = texSize.X/totalToMake;
Vector2 size = new Vector2(pixelPortion, pixelPortion);
Vector2 pos = new Vector2(i*pixelPortion, texSize.Y-pixelPortion);
Rect2 lightMapPos = new Rect2(pos, size);
//make instance rid
RenderingServer.InstanceGeometrySetLightmap(instanceRid, LightMapRid, lightMapPos, 0);
}
(i also tried normalized texture coords)
no light shows on the mesh and the .exr baked file doesn’t have anything either.
I’m guessing the rect2 arg is the position I want in the lightmap texture and the int arg is the layer of the texture, but nothing I passed it worked.
I don’t quite follow, it seems to me LightmapGIData is supposed to receive the slice index when I add a new user to it.
I tried changing the slice index, and it seems the lightmap is affecting the instances–I just couldn’t tell because the lowest slice index’s fill color was black. Changing the slice index to a higher slice where the fill color is lighter lights them all with that flat fill color. They don’t seem to bake themselves onto the texture, however.
Since your generation is deterministic, you’d need to bake to generated nodes and then transfer the baked data to corresponding nodeless geometry instances on the server.
However I looked at the LightmapGIData resource implementation. There are functions to retrieve the uv rectangle and slice index per user object, but they for some reason are not exposed to scripting. Unless you’re willing to manually parse the binary data stored in *.lmbake file or implement a gdextension - it looks like this is currently not possible to do from a script.
EDIT: There’s an undocumented hack that lets you do it. See @mrcdk’s example below.
The node to nodeless data transfer strategy seems promising.
It’s unfortunate the user functions aren’t exposed, I suppose I’ll go see how complex setting up a gdextension is tomorrow.
Thank you for your time.
You can. The script needs to be a @tool script, it needs to be attached to a Node3D or a child of a Node3D, and it must implement these 2 methods:
get_bake_meshes() that returns a flat array of interleaved Mesh/Transform3D like: [mesh1, transform1, mesh2, transform2,…]
get_bake_mesh_instance() that returns the RID of the mesh instance
Example:
@tool
extends Node3D
var RS = RenderingServer
var instances: Array[MeshInstance]
func _init() -> void:
set_notify_transform(true)
func get_bake_meshes() -> Array:
var ret = []
for instance in instances:
ret.push_back(instance.mesh)
ret.push_back(global_transform * instance.transform)
return ret
func get_bake_mesh_instance(idx: int) -> RID:
var instance = instances.get(idx)
if instance:
return instance.rid
else:
return RID()
func _notification(what: int) -> void:
if what == NOTIFICATION_ENTER_TREE:
_update_visibility()
elif what == NOTIFICATION_EXIT_TREE:
pass
elif what == NOTIFICATION_ENTER_WORLD:
for instance in instances:
RS.instance_set_scenario(instance.rid, get_world_3d().scenario)
RS.instance_set_transform(instance.rid, global_transform * instance.transform)
elif what == NOTIFICATION_EXIT_WORLD:
for instance in instances:
RS.instance_set_scenario(instance.rid, RID())
elif what == NOTIFICATION_VISIBILITY_CHANGED:
_update_visibility()
elif what == NOTIFICATION_TRANSFORM_CHANGED:
for instance in instances:
RS.instance_set_transform(instance.rid, global_transform * instance.transform)
elif what == NOTIFICATION_PREDELETE:
_clear_mesh_instances()
func _get(property: StringName) -> Variant:
if property == "_data":
return get_bake_meshes()
return null
func _set(property: StringName, value: Variant) -> bool:
if property == "_data":
_clear_mesh_instances()
var arr = value as Array
if not arr or not arr.size() % 2 == 0:
return false
for idx in range(0, arr.size(), 2):
if not arr[idx] is Mesh or not arr[idx + 1] is Transform3D:
continue
var instance = _create_mesh_instance(arr[idx], arr[idx + 1])
instances.push_back(instance)
return true
return false
func _get_property_list() -> Array[Dictionary]:
var props: Array[Dictionary]
props.push_back({
"name": "_data",
"type": TYPE_ARRAY,
"usage": PROPERTY_USAGE_STORAGE
})
return props
func _update_visibility() -> void:
for instance in instances:
RS.instance_set_visible(instance.rid, is_visible_in_tree())
func _clear_mesh_instances() -> void:
for instance in instances:
RS.free_rid(instance.rid)
instances.clear()
notify_property_list_changed()
func _create_mesh_instance(mesh: Mesh, local_transform: Transform3D = Transform3D()) -> MeshInstance:
var instance = MeshInstance.new()
instance.mesh = mesh
instance.transform = local_transform
instance.rid = RS.instance_create()
RS.instance_set_base(instance.rid, instance.mesh.get_rid())
RS.instance_attach_object_instance_id(instance.rid, get_instance_id())
if is_inside_tree():
RS.instance_set_scenario(instance.rid, get_world_3d().scenario)
RS.instance_set_transform(instance.rid, global_transform * instance.transform)
return instance
func _generate() -> void:
# generate MeshInstance here
pass
class MeshInstance:
var mesh: Mesh
var transform: Transform3D
var rid: RID
The order of the meshes needs to be the same when baking and when running.
It also has to be seen by the LightmapGI node so it needs to be a child or sibling of the node.
Also, the LightmapGIData data is marked as internal but you can still access and modify it with light_data.user_data It needs to be a flat array with interleaved: path to node NodePath/uv of the island in the lightmap textures Rect2/slice index in the lightmap textures int/instance sub index int