I'm aware that this approach is terrible but I can't think of an alternative.
Design-wise, your approach is fine. A rendering interface should know how to consume objects that describe a single (or set of) drawing requests and translate those into state configuration and draw calls for the underlying graphics API. For your renderer, it sounds like your
Renderable objects are exactly that: a representation of something to draw. So this is fine.
Implementation-wise, however, your approach has some minors flaws.
My current approach is to feed my renderer with a pointer to a "Scene", iterate through its nodes, test them with a static_cast(yeah you read that right) to a "Renderable" object (for example a static mesh class that contains a material and a vertex array) and draw.
It sounds like you are coupling the renderer to the scene this way (by having the renderer "know about" scene types, such as
Node; it sounds like
Renderable is a child of the latter and that the former is a broader type of scene than just a rendering one (that is, that you have
Node types that are not rendering-related).
static_cast isn't a good way to "test" nodes. It will always "succeed" in performing a downcast (take a look at point #2 in under the Explanation section in the above link about
static_cast), but using the resulting
Renderable may result in a crash because the
Node really was not a
dynamic_cast would be the right tool here.
But there's no reason to involve down-casting at all. Rather than give you renderer a
Scene directly, just change its interface so it gets a set of
Renderable objects, such as via a
const std::vector<Renderable*>& parameter. Now the
Renderer doesn't "know about" the
Renderable itself doesn't need to derive from
Node, it can just be a base class on its own. Now your renderer is decoupled entirely from
Scene and its related higher-level API.
Scene itself, store the array of
Renderable objects for the scene separately from the nodes in the scene. You can have nodes in the scene fill this list up with
Renderable objects they'd like to have drawn as part of the routine processing of your logic update. Then, at the start of rendering for that frame, you just give the renderer the list of
Renderable objects that the scene has built up for that frame, and let it do its job.
The key idea here is that the "scene" is a high level concept and the renderer is a lower-level one. High level APIs can know about, and use, lower-level ones but such visibility should not go the other way. By enforcing that kind of unidirectional data flow you can keep code less coupled and simpler.