Code design question

Started by
17 comments, last by L. Spiro 12 years, 2 months ago
Hi,

I've got an question about my overall code design. A simple example, where I want to pass a camera varible to my 3d render class.

void Graphics3D::Render(Camera* pCamera)
{
m_pEffect->SetMatrix("ViewProj",pCamera->ViewProjectionMatrix());
}


vs.
void Graphics3D::Render(D3DXMATRIX mViewProjection)
{
m_pEffect->SetMatrix("ViewProj",&mViewProjection);
}


which way would you recommend? This would apply to many places in my code, its basically the
decision: "Should I pass my own classes or just single variables wherever possible?". Thinking about that I might want to pass more than 1 parameter per call. I might add seperate View/Projection-Matrices, Inverse/Transposed, etc.. Any advices?
Advertisement
If you are just wrapping the single variable in a class, then I would suggest using the second method, but I think that your Graphics3D class should keep track of camera information rather than the game loop passing it in.
I would use the first version because it abstracts the camera from the specific DX implementation.
Makes it easier to expand the code later on.

If you are just wrapping the single variable in a class, then I would suggest using the second method, but I think that your Graphics3D class should keep track of camera information rather than the game loop passing it in.


It could but isn't exactlly nesecary if you have multiple views in you system it is easier to have a camera manager cope with the camera because switching becomes easier. This is also solvable by haveing a pointer to the camera in the render class and updating the pointer. I usually manage the camera in the render system that also contains the actual renderer as this manages more then just issuing render calls.

It is also the place in my system where it decides wheter to pick a D3D(9,10 or 11) or OpenGL device, which are all decisions that shoul be taken at a higher level then your renderer.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Neither.
A graphics wrapper class is not responsible for drawing the scene and has no idea what a camera even is.
Rendering is, on a high level, governed by the scene manager, which tells objects in the game world they are about to be rendered and each object is allowed to decide how that should happen by using the API created by the graphics wrapper.
Any command, any order (though arbitrary orders may not be very useful, and you will generally want to stick to a limited subset of orders, such as set the shader first, then set its uniforms/data).

A wrapper class does understand what a view matrix is, but a function designed to set the view matrix would not be called “Render()”, it would be called “SetViewMatrix()”.

If this function is really intended to draw the whole game scene and you have left that detail out for the sake of a simple post:
#1: Again, it would be moved to a scene manager.
#2: In that case you would pass the camera, not the matrix.


You are looking for something more along the lines of this:

void CSceneManager::Render( const CCamera &_cCam, ) {
Graphics3D::SetViewMatrix( _cCam.ViewMatrix() ); // Assumes the wrapper is a bunch of static function calls.
// Other steps needed to render the scene.
}


or this:

void CSceneManager::Render( Graphics3D * pgGraphics, const CCamera &_cCam, ) {
pgGraphics->SetViewMatrix( _cCam.ViewMatrix() );
// Other steps needed to render the scene.
}



Etc.






If you are just wrapping the single variable in a class, then I would suggest using the second method, but I think that your Graphics3D class should keep track of camera information rather than the game loop passing it in.

A graphics class/wrapper has no idea what a camera is. This is a frustratingly common mistake people make early in their game-programming “careers”.
People think that because a camera class provides information so vital to rendering a scene that it must then be part of the rendering system.

Think about what a camera does and what information it has to do that.
It will probably keep a copy of the last field-of-view and aspect ratios it was sent for automatic resizing when the window resolution changes.
It will have a position and an orientation/view direction, which will consist of at minimum a forward and up vector, often a right vector.
It has a view frustum for culling composed of six planes.
Since it can be parented by scene objects and have scene objects as children, it will probably need to inherit from your CNode or CActor or CEntity or whatever base class is used by all of the objects in the scene.

Now how much of that data does the renderer actually need?
It needs the view matrix and the projection matrix. Neither of which are actually even used by the camera at all—they have to be generated for the soul purpose of giving the renderer what it needs.

It is already clear that the camera is not actually related to the renderer at all, but wait, there is more.
Not only is there no reason at all for a graphics class to know what a camera is, it is a horribly bad idea for it to know what one is.
By knowing what a camera is, suddenly the graphics engine knows what a frustum is. Suddenly your “matrix library” is no longer enough, and you have to import a full-sized math library to get those CPlane3 and CFrustum classes. The same library may very well have the CAabb and CSphere classes, along with the math for testing those against the frustum.
Suddenly the graphics engine knows what scene nodes are. It knows what actors or entities—whatever you want to call them—are, which is part of the core functionality of managing an entire scene and all of the objects in it.
With all these extra libraries it has to import, it probably learned what oct-trees are and probably learned somewhere along the way how to do physics.
Where does the graphics engine end and the game engine begin?

And what did it really want out of all of this?
A view matrix and a projection matrix.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

In my mind
void Graphics3D::Render(D3DXMATRIX mViewProjection)
Win!

Reason: a function should have minimum knowledge to accomplish its function.
The less knowledge the better.
Because it reduces coupling.

If you pass camera to Render, then Render can only serve for your camera (tight coupling).
If you pass only the D3DXMATRIX, then Render can serve for any classes/modules that can provide that matrix (loose coupling).

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.


In my mind
void Graphics3D::Render(D3DXMATRIX mViewProjection)
Win!

Reason: a function should have minimum knowledge to accomplish its function.
The less knowledge the better.
Because it reduces coupling.

If you pass camera to Render, then Render can only serve for your camera (tight coupling).
If you pass only the D3DXMATRIX, then Render can serve for any classes/modules that can provide that matrix (loose coupling).


Yes but in this case it's part of that data of a render object and as such the function should read:
void Graphics3D::Render(const RenderObject& object)

But that's wrong because the Graphics layer should only be concerned with submitting vertex buffers and setting up the correct render states, without having to reflect the underlaying hardware.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Wow, quite a lot of feedback. Thanks to all of you, I got some good ideas, but I think I need to further explain how Graphics3D & my renderer works.

Graphics3D is basically just the combination of a factory and storage for my models. A model consists of a mesh, a material and an effect. I create models calling m_Graphics3D.NewXXX, which calls new, returns the model and stores it inside a vector. This class does some nice stuff for me in the first place: It sorts models based on effect, material, and vertex buffer. It will get some more optimized functionality like automatically handling models with instanced meshes, or static geometry with shared vertex buffers etc.. to allow efficient rendering.
So calling Render() does nothing else than to render every 3D-Model. However I'd like to be able to easily render everything from different cameras, to different rendertargets, etc.. so thats why I decided to pass eigther the camera or the viewmatrix in. I'm working on being able to supply additional parameters, like the rendertargets etc. to the Render-MethodThat way I can do stuff like this:

pGraphics3D->Render(CameraMatrix,vRenderTargets,MATERIAL|NORMAL|POSITION); // render material, normals and world position to render targets from camera view
//do some other stuff
pGraphics3D->Render(LightMatrix,lpShadowBuffer,DEPTH); //render depth to shadow buffer from lights camera
//do some more stuff
for(int i = 0;i<6;i++)
{
pGraphics3D->RenderToCube(mCube,lpCubeMap,i,MATERIAL); //render material to side i of a cubemap
}


You see? In my point of view, this makes rendering, especially for special effects, very effecient. All that work is done inside a render class. A scene graph will take care of parent<->child dependencies, and View-Frustum-Culling will be done somewhere else, too. What do you think of it now?

What do you think of it now?

It is basically the same situation I explained about why the graphics engine knowing about your camera is bad, but expanded to include models as well.
A model, like a camera, is a higher-level structure than just the graphics data contained within it. The graphics library doesn’t care what an AABB is or whatever physics or collision information may accompany the model, nor does it care about animation data. You may not have that data nor plan to, but considering theoretically that you did, it would help you realize that a model is much more than just graphics data, and that the graphics data wanted by the graphics library is a very small sub-set of the entire object.

So, your graphics engine contains your models.
So how do you plan to handle terrain? Just throw that into the gargantuan graphics library? Duplicate it but specialized for rendering terrain?

You may not even be planning to ever have terrain, but theoretically asking yourself these kinds of questions helps you understand where things really need to be and what they really need to do.
You thought the graphics library was the appropriate place for your models because you considered that they were the only thing you would render (and that a model does nothing else but be rendered).

Then you also need to ask yourself what others would expect from your graphics library should they want to use it (again, only hypothetically). If I use your graphics library, am I required to use your model format? All I want is an easier interface with the hardware. Why do I need to take in this model format? I have my own.
Your graphics library renders every model, but I already have a library whose job is specifically to efficiently manage the scene and all the objects in it, and thanks to some of the spatial partitioning information it keeps (for example) it can cull and render objects more quickly than a graphics library that simply renders everything.
By using your library, I forfeit my efficient rendering?

If you had considered both, “What if I were to theoretically add terrain?”, and, “What do people want by using my library?”, you may have arrived at the more-suitable conclusion: “I could add terrain to the graphics library, but since I myself am not planning to use terrain, it is likely others using my library also probably don’t want terrain. I should make my graphics library more abstract and have it provide an easy-to-use interface that other libraries can use for whatever kind of rendering they want to do. Then I can have models and terrain as separate libraries that can both access the graphics library by themselves without the graphics library knowing about all kinds of things it shouldn’t. Probably the best organization is the following:
#1: Graphics library: Provides a convenient interface with Direct3D, OpenGL, whatever. Has wrappers for textures, vertex buffers, index buffers, and shaders. It is not a crime for it to also link to some kind of vector/quaternion/matrix math library, if necessary.
#2: Model library: Higher-level then the graphics library. Graphics and rendering are only a small subset of what a model actually is and does. It not only includes the graphics library, but also the physics library etc. (if you were ever to add physics).
#3: Terrain library: Same level as the model library. Same exact concept. Just a different way of loading/handling its data, and rendering it.
#4: Engine library: This is the core library that stands above all others. It knows what everything is, and it brings all of these other libraries together in peace and harmony.
Within this library is a scene manager which knows about every single type of entity in the game world, from cameras to models to terrain.
It makes things efficient via spatial partitioning schemes, sweep-and-prune implementation, etc.
It manages the phyics engine directly. When it wants to perform a logical update, it uses its efficient management of the game world to efficiently run over the objects in the world and ask for the data needed for use by the physics engine (which it then passes off to the physics engine).
When it is time to render, it efficiently gathers objects in view, inform them they are about to render, handles special request by the objects to update a reflective cubemap, and finally lets objects render themselves however they decide to do so.
It handles creation of shadow maps, reflection maps, etc.
It is a mastermind that orchestrates everything.”


With such a design, I am free to use your graphics library without needing to use your model format or terrain.
If I want to use your models, I can also use your model library. No harm done.
If I want the scene to be managed efficiently for me, including the generation of those cubemaps you mentioned, I can use your engine library.


The point is that your “convenience”—that is having cubemaps generated automatically, having a way to render all the objects in the scene with one call, etc.—is not the issue.
These things are needed. You just didn’t put them in the right places.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

A model, like a camera, is a higher-level structure than just the graphics data contained within it. The graphics library doesn’t care what an AABB is or whatever physics or collision information may accompany the model, nor does it care about animation data. You may not have that data nor plan to, but considering theoretically that you did, it would help you realize that a model is much more than just graphics data, and that the graphics data wanted by the graphics library is a very small sub-set of the entire object.[/quote]

Obviously, we are not talking about the same thing. Maybe we have a different understanding of what a model is? For me, a model is just a renderable object, with a fixed set of properties: A mesh(vertex+index-buffer,vertex declaration), a material (material-texture,bump-map,specular/diffuse-components) and an effect (shaders+render-states), position, rotation, scaling (as well as the matrices). Further, it will get properties like opacity, visibility, etc... however, my models will most certainly not contain anything dedicatet to physics or such.. What you mean will actually be handled by my Entity-System. An entity like a character will simply get a pointer to a model for modifying its properties.


You thought the graphics library was the appropriate place for your models because you considered that they were the only thing you would render (and that a model does nothing else but be rendered).

Then you also need to ask yourself what others would expect from your graphics library should they want to use it (again, only hypothetically). If I use your graphics library, am I required to use your model format? All I want is an easier interface with the hardware. Why do I need to take in this model format? I have my own.
[/quote]

As you see, a model does not neccessarly mean a character model. So anything made out of vertices can be represented by a model. If you mean "model format" in the way of a file format, than you can eigther add a method LoadMeshFromMyFormat(wstring lpFileName) or modify the existing LoadMeshFromFile-Method. All you have to do is to create a vertex buffer as well as an index-buffer (later on you won't even need this if you don't want to) and a vertex-declaration from your format, and call Mesh* mesh = new Mesh(lpVertexbuffer, lpIndexBuffer, lpDeclaration). In the Graphics3D-Class, you would just modify the call of NewCharacter (or whatever) to use the new mesh factory method.

On the other hand, if you meant model format like an own class you would like to use: this should even be possible. I need to restructure my renderer just a little bit, so you could theoretically skip the Graphics3D-interface and use your own. Graphics3D is really just a simplification interface, allowing me to create a renderable and display it right away, without any overhead. Maybe the title Graphics3D is a bit misleading. I hope you get the idea, and that its by none the core of my graphics-interface. I came up with this because you could basically seperate renderables into two categories: 3d-models and 2d-sprites. So I also have a Graphics2D-interface which handles "sprites" the same way that my Graphisc3D-libary does.


Your graphics library renders every model, but I already have a library whose job is specifically to efficiently manage the scene and all the objects in it, and thanks to some of the spatial partitioning information it keeps (for example) it can cull and render objects more quickly than a graphics library that simply renders everything.
By using your library, I forfeit my efficient rendering?
[/quote]

Now you know that graphics and scene-objects are more or less seperated, it should become clear that you can still use your culling, spatial partitioning, etc.. , at least in theory. Either a function call that tells the model not to render in the next Render()-Call, or a function that takes a bool-array telling what Models to render and what not. Obviously it might be a little bit less performant than if you just didn't call Render() on anything not on screen. I can accept that for now, and maybe later on there will be a way to completely eliminate this little performance issue.

If you had considered both, “What if I were to theoretically add terrain?”, and, “What do people want by using my library?”, you may have arrived at the more-suitable conclusion: “I could add terrain to the graphics library, but since I myself am not planning to use terrain, it is likely others using my library also probably don’t want terrain. I should make my graphics library more abstract and have it provide an easy-to-use interface that other libraries can use for whatever kind of rendering they want to do. Then I can have models and terrain as separate libraries that can both access the graphics library by themselves without the graphics library knowing about all kinds of things it shouldn’t. Probably the best organization is the following:[/quote]

Well, if you mean terrain from a heightmap, its as easy as loading from your own mesh format: just add a factory method that reads the heightmap in, and threat it as a mesh that gets passed to a model. There will be different types for my models, like static/dynamic/etc., so terrain might fit to a static model. I will also provide methods/interfaces to easily manipulate vertex data, if you want dynamic terrain like in a strategy game editor or such..


With such a design, I am free to use your graphics library without needing to use your model format or terrain.
If I want to use your models, I can also use your model library. No harm done.
If I want the scene to be managed efficiently for me, including the generation of those cubemaps you mentioned, I can use your engine library.[/quote]

Ok, reading what you understand under graphics-libary makes it even more clear to me that I obviously confused the name for my Graphics3D-class. I think I described everything more clear now. Do you still consider my design wrong? I just think it most convient, because now I entirely splitted graphics and game logic, and allowed me to draw renderables without having to add tons of lines of code for every new game entity I create. I think I will present my whole source code as soon as I have my whole graphics/rendering-stuff done, and ask for some more commentary..

This topic is closed to new replies.

Advertisement