Here goes, hope I can answer all of your questions as some of them don't have a definitive answer but are more a personal preference. Also, some of it depends on things like the platform you develop for, how important speed is, how important extensibility is, etc.
When creating a central rendering system for all game objects in a given scene I am trying to work out the best way to go about passing the scene to the render system to be rendered.
Great start, but with one note. There is absolutely nothing wrong with a generalized system for rendering, but keep in mind that any generalized system is almost always a slower system and more important, costs a lot of extra code to write and maintain. There are cases where foregoing such systems for the simplest in place solution (in your case, no rendering manager or a render manager with intrinsic knowledge of the game) might be the best solution.
That being said, go for it and write a complex system if you need it, or if you just feel like it. Just think about the reasons for everything you do.
If I have a scene managed by an arbitrary structure, i.e., an octree, bsp trees, quad-tree, kd tree, etc. What is the best way to pass this to the render system?
The obvious problem is that if simply given the root node of the structure, the render system would require an intrinsic knowledge of the structure in order to traverse the structure.
My solution to this is to clip all objects outside the frustum in the scene manager and then create a list of the objects which are left and pass this simple list to the render system, be it an array, a vector, a linked list, etc. (This would be a structure required by the render system as a means to know which objects should be rendered).
Good idea! I don't have much to say against this system, but since you asked for improvements and caveats, let's go through some.
First of, a good idea would be to keep a separate data structure for the objects in the scene and the renderable objects. Objects in the scene might be complete classes with lots of data on object state necessary for gameplay but totally irrelevant for rendering. So create a very simple struct for renderables, just add pointers to the data you need for rendering, or even simpler, make it a struct with two fields, a sort key and a pointer/index/id to the render data structure that resides somewhere else in memory.
Second, store your data in a way that makes multiple passes easy. You never want to render your scene just once, you may need multiple passes for certain graphical algorithms. Also, not every pass needs the same objects. To give a hypothetical example, you first render your shadow map from the light perspective (different objects visible/culled), then you want to render a quick depth pass with all opaque geometry to fill the depth buffer, then you render your main pass, followed by particles at a different resolution, and in the end you need to do some post processing using the scene you just rendered. If you want to do deferred rendering, that adds some more passes with different settings and objects.
Third, data may need to be sorted differently for each pass. For filling the depth buffer, you may want to sort front-to-back, for particles you may want to render back to front.
As a solution to some of these issues, you could decouple clipping/culling from the scene manager and add it to the chain, so you just pass all objects from the scene manager through the clipping manager which only adds visible objects to the render queue. This might also make it easier to optimize culling by first storing all objects to be rendered in an array and then going through them sequentially, which might be more efficient in terms of cache usage etc. Then again, maybe you already do this in a very efficient manner in your scene manager, which I can imagine if you use an octree or something like that. Up to you.
What comes to my mind right now is a solution where you create different buckets for different render passes. Each bucket could have its own culling system and have an efficient tree structure that automatically sorts data when it's added (trees are great for that). So the scene manager just walks through its set of objects to be rendered and adds a "Render Struct" to the render manager. This render struct is now copied and passed to each bucket. If it gets through the culling system, it's added to the tree.
Then again, this is a really complex solution, maybe it's waaaay to complex for your situation. Also, I have no idea if all the things I just said are actually that efficient. It all depends, as always, on the data and what you need to do with it. So, know your data and always profile/test your code to see if the beautiful and complex generic system you worked so hard on is that much faster than the simple and fast solution (note that I'm not advocating quick and dirty solutions, simple and fast does not mean it's a hack! You should never use hacks!)
The list would of course attempt to minimise OpenGL state changes by grouping objects that require the same rendering operations to be performed on them.
That's pretty old advice actually. It's bandied around a lot, but it's origins lie in the very first generations of GPUs. It was definitely good advice back 10 years ago, but there isn't much advice from those days that's still true today so I would want to do some further research on that if I were you, before you base your rendering code on that.
I have been thinking a lot about this and started searching various terms on here and followed any additional information/links but I have not really found a definitive answer. The case may be that there is no definitive answer but I would appreciate some advice and tips.
There is no definitive answer to anything in the world of computer science and gaming. Ideas that were great 10 years ago are useless now, and the latest and greatest innovations of today will be outdated 5 or 10 years from now. The reason is simple and two-fold: data and architecture. Both change constantly. What works on an ARM system with limited CPU power and memory may be useless on a 3+ GHz quad core with 8 GB of RAM. What works on a close to metal PowerPC based console may be useless on an Android game that runs JIT-ed bytecode.
So architecture changes, but data used in games changes a lot too. How many objects in your game? How many polygons, how many lights, what kind of shaders do you use. How many objects change and how much of the scene is static?
My question is, is this a reasonable solution to the problem? Are there any improvements that I could make? Are there any caveats I should know about?
Know your data and your architecture through and through and you will be able to tell what the best solutions are. Not sure, then profile it! Never assume, always measure. So is your solution a good one? It certainly is, but there are also cases where it might be overkill, either because your data does not need a complex solution like that, or because it might kill performance if you use low-power hardware. It might also be too general where a system tailored to your scene could be a lot faster. You know your situation better than anyone, so you tell us :) Hope this helps!