It's possible with a texture as already mentioned, but using instance custom data is far less work, so I'd suggest trying that first.
Custom instance data can mean whatever you want it to mean. You set that data from script using MultiMesh.set_instance_custom_data(). and read it from shader via INSTANCE_CUSTOM built-in, as you said. Data is sent as Color object but it's just 4 floats that your shader can use for whatever purpose it needs. In this case, it'd be visibility info.
You just need to determine instance ids of blatts that are covered, use one of the custom floats as a flag that determines visibility (say 0 is invisible, 1 is visible), check that custom value in the vertex shader and skip drawing the instance if flag is not set.
Or more elegantly just scale the blatt to the ground by the value of that flag to avoid branching in shader (assuming blatt base is at y=0)
So in your script at startup, set all instances to be visible:
for b in blatt_instances:
multimesh.set_instance_custom_data(b, Color(1.0, 0.0, 0.0, 0.0) )
In your script when some blatts become invisible:
for b in newly_culled_blatt_instances:
multimesh.set_instance_custom_data(b, Color(0.0, 0.0, 0.0, 0.0) )
And in your vertex shader:
VERTEX.y *= INSTANCE_CUSTOM.r;