Shadows: Unless strictly spotlighted from a point directly above, would not realistically be "size of largest horizontal extent", per tree. (If strictly spotlighted from nearer than infinity, it would actually be larger, but general sky-glow would erode that anyway
except when clumped.)
So, abstracting rather than full-on ray-tracing, the Z+1 tree profile alone
should be sufficient as a shadow-overlay. If you ever wanted to add a half (or third?) intensity of shadow overlay for Z+2, then that would probably be the limit (in my head, quarter/ninth Z+3 shadowing would be chasing diminishing returns), and still work with 'dense' plantations with dappled gaps betwixt trunks at a ground-level.
Self-shading (the non-transparent Z+1 tree-slice bits get (Z+1)+1, etc, shadowing effects) and adjusted shade-gathering on raised landcape/construction within the relevent bits of footprint isn't much more than I'd expect from a quick search for some form of gangway running over a bit of ground one (or two) levels above and casting a diffuse footprint.
(I forget if you're time-of-day shading slope-faces... I don't think you currently are, for simplicity (and virtual irrelevence in Fort mode... not sure about Adv Mode, though) but some typically gnomic adjustment of shdow offset
could (approximately) follow the sun in external scenes, I suppose, and hue-shifting to approximate sun-elevation. But underground (and maybe aboveground under rainclouds?) would default to the simpler lighting.)
TL;DR; I think cast shadows (brought down from tree features, each time you hit the rendering of a trunk base, if not too keen on
continually querying the empty space above
Every. Single. Surface...) should be just one
or two of the higher layers rerendered as an overlaid shader. In my head (and recalling past fiddlings of my own) this would give results better than the minimum coding effort would suggest.
But might be interesting to compare with a single shadow the size of the (cumulative) extent, for both aesthetics and calculations needed[1] to establish.
[1] foreach layer (base..top) do {foreach element (layer.elements) do {if ( (shadow(element.position)==nul) || (shadow(element.position).type==leaf && (element.type==branch || element.type==trunk)) || (shadow(element.position).type==branch && element.type==trunk) then {shadow(element.position).type=element.type} }