Horizon: Zero Dawn is a great recent example of vegetation placement - their system is broadly categorized into authored source data and tooling, and the runtime placement system.
-
Source data (and authoring)
-
Painted / generated per-world-tile texture maps, e.g. Rivers, Big Trees, Biome X, etc. These can be whatever information is useful to you in your placement algorithm. It's common that things like 'Rivers' or 'Roads' are actually Signed Distance Fields so you can do things like query '10m away from roads'. You'll generally want access information derived from your terrain as well, like Slope, Curvature, etc.
-
'Placement Graphs', e.g. some way to express how you want to combine all your source data to place different assets. This could be a textual expression language, a node graph, defined in code, whatever, but the point is that you can take all your source data and define a set of rules for how something is placed
-
Probability(my_tree_a) = (distance(water) < 10) * keyframe(elevation, <some keyframes>) * ...
-
Runtime placement
-
When you approach a terrain tile in Horizon, they start placing nearby objects using their placement algorithm. This involves taking all the source data + placement graphs and evaluating them. In Horizon's case, they do this on the GPU in a massively parallel fashion to efficiently place thousands of objects.
-
Objects can be placed at pre-defined (ish) locations. You can generate these locations using blue noise, hex packing, etc. but you just want some kind of irregular-ish grid to get natural-looking uniform random placement.
-
At each location, evaluate all the possible placement graphs that could go there (grouped however makes sense to you). Generate some random value and randomly choose what to place at that location based on the evaluated results (it might be nothing!).
-
'Placing' something really means recording a pair <model ID, position>. You can also generate other information (scale, rotation, tilt, whatever). What you end up withs is basically a point cloud of various models scattered across your landscape. You can transform this and input it into your normal rendering pipeline however works best for you.
The more traditional approach is to move both steps 1 and 2 to offline tooling, and package up all your models and instances to be loaded by the game, but Horizon's approach allows some pretty awesome in-engine iteration (paint to 'Trees' texture and see things respond in real time). Far Cry 5 is an example of the more traditional offline approach iirc.
Edit: Tl;dr, you need some way to come up with a point cloud of models to render. You can do that on the GPU or on the CPU depending on the number of things in your environment, perf requirements, etc. If you're generating chunks, it makes sense to perform your placement algorithm when generating a chunk. When drawing a point from your point cloud, you can determine to draw the billboard or full model based on distance from the viewer, etc. like drawing a normal model.