PDA

View Full Version : OpenGL Engine in C++



Michael Steinberg
01-29-2001, 03:17 AM
So far I've always programmed with plain C but now I want to change to C++. My question is a bit unrelated, trust me. Well, how far should one abstract all the stuff. Should one create a Vector class with overloaded operators (that is REALLY low level abstraction), should one have every vertex of a model in a vertex object? I don't think so. Might it be that C++ should only serve as a high level abstraction layer as I think?

Any comments are highly appreciated!
Michael

XBCT
01-29-2001, 03:45 AM
Hi!
Difficult question if youīre asking about the general case....But Iīll try to answer your specific ones:

1.>Should one create a Vector class with overloaded operators (that is REALLY low level abstraction)<

I would definately say yes. It is so comfortable to write 3d-code with a good vector class and nice overloaded operators you simply donīt want to miss it if you used it once. The only thing here is to profile carefully to prevent nasty little details in class implementations which slow down things unexpectedly. Make alot of use of references and "inline". But there are alot of good vector classes out there to look at.

2.>should one have every vertex of a model in a vertex object?<
Depends on how many info your engine needs to be stored with one vertex....

P.s.: Perhaps I told you only stuff that you already know....If so - sorry! http://www.opengl.org/discussion_boards/ubb/wink.gif

HTH, XBTC!



[This message has been edited by XBCT (edited 01-29-2001).]

mcbastian
01-29-2001, 04:18 AM
Hi Michael!

I stood in front of the same problem last month.
I created a Vertex-class with all the vertex-math stuff in it (with overloaded operators).
but faces and texture-vertices are only a typedef struct http://www.opengl.org/discussion_boards/ubb/smile.gif
every 3d-object (better 3d-thing http://www.opengl.org/discussion_boards/ubb/smile.gif ) is a own class with a Loader in it, a faces-array, a vertex-class array (of pointers) and a texture-vertice-array. you also find a Display-function here, which displays the obj. I use this for effects (p.ex. the water-sphere effect from 3dmark... http://www.opengl.org/discussion_boards/ubb/smile.gif ), cause every effect has it's own display-function.
If you're interested in demo-code, don't hesistate to contact me http://www.opengl.org/discussion_boards/ubb/wink.gif If i finish my engine I will put it onto my site, but this is far away http://www.opengl.org/discussion_boards/ubb/smile.gif

greetings,

Sebastian

Michael Steinberg
01-29-2001, 05:18 AM
Thanks guys!
I don't hesitate... http://www.opengl.org/discussion_boards/ubb/smile.gif

Well, some time ago I created a vector class, maybe someone can look at that code an tell me wether it is a) correct and b) fast. I know it is not fast nor is it correct, but you could give me a lot of help and wouldn't have to give me your own code.
But then remember, that is the first thing I programmed with c++ and I don't have a c++ book...

Eric
01-29-2001, 05:20 AM
Michael, you can send it to me for checking if you wish !

Regards.

Eric

P.S.: you see, you finally make the move to C++ !

Michael Steinberg
01-29-2001, 05:24 AM
Oh BTW is it really good to give every object it's own rendering function.

You can't
- globally sort after textures
- do some fency stuff that incorporates multiple objects

I think it would be better to have a renderer object where you can register all the objects after what they are. It could make global sorting of it all etc. The object get called when they move or change. Well, I'm currently thinking about how to make a cool 3d pipeline in c++.
Maybe its also cool to just let every object draw itself. I'm gonna try it out all.

Thanks!!!

Michael Steinberg
01-29-2001, 05:32 AM
Yes, Eric! Thanks!
Well, indeed I make the change now if at all possible, since I came to the conclusion that abstract nice code is good code. It might make problems, but if I look at the gtk window system on linux I feel good...
Hope to be able to implement a similar window system which doesn't need its own precompiler.
Gonna send it to you within the next hour!

I still have problems understanding that & thing in for example:

void function( float &parameter );

What does that do? I only know that I don't seem to have to use the -> with structures in this case. I have never seen that construction before. Can anyone help?

Thanks!!!

Michael Steinberg
01-29-2001, 05:34 AM
ķmeter is
&[]parameter

Sigh. Well you might know what I mean... http://www.opengl.org/discussion_boards/ubb/smile.gif

[This message has been edited by Michael Steinberg (edited 01-29-2001).]

Eric
01-29-2001, 05:38 AM
For your pipeline/sort problem, I have an abstract class which is called C_Rendered and contains vertices, faces, normals, texcoords and materials (could be a shader). The Build() function (call it Render if you want: it is called Build here because it builds the display list for the object) is virtual. All my objects are derived from this class.

So you can still sort per shader even with each object having it own rendering/building function...

Regards.

Eric


[This message has been edited by Eric (edited 01-29-2001).]

Deiussum
01-29-2001, 07:56 AM
void function (float &blah)

is a "pass by reference" function. Basically what it does is that when you call that function...
float blah2;
function(blah2);

The address of blah is physically mapped to the address of blah2. Therefore, any changes you make to blah, will be seen in blah2 after the function exits.

It's similar to passing by pointer except that passing by pointer you actually pass the address.

functionByPointer(&blah2);

And then since it is a pointer, you have to use dereferencing to assign a value to it in the function.

*blah = 10.0;

You don't have to dereference it like that if you pass by reference since it sets the address of blah to be the same as the address of blah2. (&blah == &blah2)

Hope that makes sense.

Michael Steinberg
01-29-2001, 09:00 AM
Thanks Deissum!
I actually thought about that way, but why should one pass pointers to the functions then. It seems to be better to use these and's. Any idea?

Thanks Eric!
If I understand you well, you have sort of basic object class. The object classes are derived from them. Then you also have a renderer (which is sort of container of all these objects (more correctly of the basic objects)). The basic object class contains only render specific data.
I simply can't believe that all the different kind of objects (weapons, space ships, space stations etc.) can be made abstract to such a low layer. I mean, they all have polies, but consider special object such as an explosion or volumetric fog. The renderer would have to handle all these small effects. So why then bother deriving some special object class from the base class, when the renderer would have to do all the stuff. Also, a bone system. Bones have to be handled by the renderer because it will have to load the matrices up or pretransform the vertices. The object oriented model isn't too clear to me.

Gorg
01-29-2001, 09:03 AM
You should actually be using (const type& varname)

because

float j = 3.0
func(j);

void func( float& f )
{
f = 4.0; //doh!!! j just changed
}

void func( const float& f )
{
f = 4.0; //error f cannot change
}

As far object oriented goes, the way I do my rendering is the following. I have a renderer object which have function of the type :

setShadingAttributes( const attr*&);
setMesh( const mesh&*);
Render()
etc.
My scene graph uses those fonctions to render the scene. So it calculates which objects are in the view, sort them by attributes. and then call setShadingAttributes()
setMesh and set whatever and then calls render which uses the mesh and the shading attributes to render the object

The nice thing with that model is that the attributes can be derived. So I can simple shading(simple material color), RenderMan shaders, simple Quake like surface shaders.

So all attributes have a set fonctions which sets them a the current rendering attributes in the renderer

With the meshes, then a meshe can be a vertex array, a display list, or a Curved surface.

So all meshes have their own draw fonctions. So if it is a displaylist, glCallList is used, if it is stored in a curved surface, the tessalation is done, if it is a vertex array then glDrawElements is called. Etc..

All that without having the scene graph or the renderer now about how the attribute or meshes are implement.

You can make that scheme so powerfull that you can just change the renderer from opengl to Direct3d and you won't even have to restarts all your objects.

OO is powerfull.

Sorry about the bad orthograph. I am just writing too fast and no re-reading myself! http://www.opengl.org/discussion_boards/ubb/smile.gif

[This message has been edited by Gorg (edited 01-29-2001).]

[This message has been edited by Gorg (edited 01-29-2001).]

Michael Steinberg
01-29-2001, 09:09 AM
Thanks Gorg!

XBCT
01-29-2001, 09:15 AM
Hi!
To the sorting problem:
I think by far the best approach is to have a rendering backend which only takes faces\triangles as input and stores them in temporary vertex\texcoord\normal arrays which then are used as CVAīs for example. You set the current state of the backend to one shader and feed all faces with this shader into the backend. You flush(render) the backend if itīs buffer is full or if a face with another shader comes in from the front end....
You now have a pipeline which eliminates all unnecessary state\texture changes and you can still do what you want in your front end....
The point I try to get accross here is:
The actual renderer should only take faces\triangles and NOTHING else in order to be flexible and fast.
What you do in the front-end(Rendering functions for objects....) doesnīt matter much then and you can try alot of different things in the front end without writing different renderers everytime....

HTH, XBTC!

[This message has been edited by XBCT (edited 01-29-2001).]

Michael Steinberg
01-29-2001, 09:26 AM
I actually more thought about presorting. I don't think it could really get fast if one tries to sort >10k polys every frame.

Thanks!

Michael Steinberg
01-29-2001, 09:30 AM
Also, if you simply push them in, how would the renderer know which polys belong to each other. He could end up loading a matrix, rendering a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly, loading a matrix, render a poly.

God that I don't mess with too large models.... http://www.opengl.org/discussion_boards/ubb/smile.gif
Hey, I'm a bit crazy today, don't take it bad.

Deiussum
01-29-2001, 10:35 AM
Originally posted by Gorg:
You should actually be using (const type& varname)


Only if you don't want the value to change. For smaller things like ints and floats, if you don't want the value to change it's just as efficient to pass by value. (Copying a 32-bit address isn't any more expensive than copying a 32-bit value)

You should use the format you give if you want to pass large structs to a function, but don't want them to be editable. Then it will pass only a 32-bit address as opposed to say 256-bit struct (or more). Making it const will ensure that it then can't be edited.

Often times you pass things by reference BECAUSE you want to get the value back. For instance..

void GetPosition(float& x, float& y, float& z);

float x,y,z;

GetPosition(x,y,z);

Michael Steinberg
01-29-2001, 11:39 AM
void GetPosition(float *x, float *y, float *z);

GetPosition( &x, &y, &z );

Would be far clearer, wouldn't it be? Noone could see out of the function call wether the floats get changed or not. If you send pointers to them that is quite probable because you could also pass them as values if the function wouldn't change them.

I think I should collect a c++ book somewhere. Actually, I would use classes just like structures with some functions in them to clarify that the functions belong to that type of "structure" (For example normalize into the vector class). And I would still use normal arrays and structures for classes.
How fast are objects? I mean if you have overloaded operators. Is it as fast to use objects like Vectors as it is when using structures?
Any recommended books about c++?

Thanks!

Deiussum
01-29-2001, 12:42 PM
Depends on your point of view. Pass by reference was added to C++ partly so that the calling conventions could be kept the same as passing by value. (i.e. not having to pre-pend variables with &)

Passing by pointer could be considered "uglier" because
a) you have to add the & to pass the address
b) you have to dereference the pointer inside the function in order to change the value (i.e. *x=10.0)

Passing by pointer and passing by reference are essentially the same except for the calling convention and the way the variables are used inside the function.

XBCT
01-29-2001, 12:43 PM
>Also, if you simply push them in, how would the renderer know which polys belong to each other. He could end up loading a matrix, rendering a poly, loading a matrix,....<

Calm down! http://www.opengl.org/discussion_boards/ubb/wink.gif
Very simple it isnīt important which poly belongs to which other....I talk about the very(!) back end of the engine here.
The backend only renders triangles which need completely the same rendering state. Surely you should fill the backend only with triangles which are already sorted by texture.

>I actually more thought about presorting. I don't think it could really get fast if one tries to sort >10k polys every frame.<

Iīm talking about static scenes here....
You only need to sort them once and that ahead of time.

P.s.: The architecture I described is exactly the one Q3 and my Q3Engine use so trust me it works very well....

HTH, XBTC!

Deiussum
01-29-2001, 12:49 PM
It's been so long that I've looked at books on plain C++, that I don't have any really good recommenations for books to learn C++. A book that I've found interesting is the The ANSI/ISO C++ professional programmer's guide. (or something like that.) It explains a lot of the latest standards. (New casting methods, a little STL, etc.) It probably wouldn't really be good as a beginner's book. But once you get the hang of C++ more, it's pretty informative on the latest standards.

If you optimize your code well enough, objects can be just as fast as standard C coding. For instance, overloaded operators, and other smaller functions can be written inline so that they don't incur a function call overhead.

Michael Steinberg
01-29-2001, 01:09 PM
Q3's written in C++?

Well, anyway, what do you mean? If the backend does nothing but drawing triangles, you can simply let it out. The list of triangles should be the same for an object (well, for me it's not, but that's another problem) over the game. Is it better to sort after states and then after texture (means, sort for every object, not global)? In any first person shooter, you can optimize the whole world. You can sort and sort and if you're ready again sort, because the world is one object (or can be considered as one). Now, I'm talking about a space shooter. I will most probably sort after texture for every single object. If the NV20 is good enough, it might also be an option to not texture at all.

After all, my problem is, that I've thought about so many graphics effects, that I can't see let's say an embryo of them that they share. How to do it with deriving then?

I can also imagine people that derive where it isn't necessary, just to stay completely in the oop. I don't want that "I HAVE TO DERIVE A CLASS BECAUSE IT LOOKS LIKE I WOULD BE A GOOD C++ PROGRAMMER!!!!!!" syndrome. Why to make a vector class? There is only one * and multiple vector multiplications, so you still have to stick with normal functions or have to use really weird operators. Why to put a vertex into a class where a simple array could serve as well? I only see overhead in that.

What I can imagine is however, to have these windows-system and global system parts in classes. That could guarantee that the interface to the system parts wouldn't change so one can easily modify it's intern functionality without having to look for dependent parts somewhere else in the engine.

Oh, I'm getting crazy. http://www.opengl.org/discussion_boards/ubb/frown.gif http://www.opengl.org/discussion_boards/ubb/smile.gif

Maunikar
01-29-2001, 01:21 PM
Well, the big thing is that there's no cost inherent to putting something into a class, so long as there are no virtuals.

Why make a vector class? Because you can add vectors together and have it look like any other fundamental type. Because it encapsulates the math to a layer that you don't always have to look at, leaving the higher level logic more clear to read. If you inline those functions, you don't even incurr the function overhead.

The "I MUST DERIVE" syndrome goes away. You start to recognize patterns that lead to very efficient and effective class structures. The biggest benefit I've found is that your code reflects the inherent order in a well-designed OO system. Maintenance and modification are a breeze.

As with everything else, though, YMMV.

Maunikar/Jason

Gorg
01-29-2001, 03:57 PM
Learning C++ is more than just learning a language, it is moving into a paradigm.


But still, here are the books I really like(for 00 and C++)

the C++ programming language. Bjarne Stroustrup

Effective C++, Meyers

Exceptional C++, Herb Sutter

The C++ standard library a tutorial and reference Nicolai M.Josuttis

Design Patterns. Gamma,Helm,Johnson,Vlissides

A System of Patterns. by a bunch of people.

There is also More Effective C++, but if you read all the other I just said, 90% of the stuff in the book you'll have already read.


And if you want to modify value by using passing by reference or by using a pointer. Here is my view on it:

With references, the user might not know that value get change.

with pointers, you never now if the pointer is valid,but You have to assert. I only use pointer if I only need to modify one value. If I wanted to modify 2 or more values, then I simply return a pair or make a structure. But again, the objects might be huge, then you can avoid the copy by using pointers.

So both solution have their +/-, if well documented, the reference technique saves you an indirection and you don't need to assert.

But my rule of thumb is : return new objects unless there is a really important reason to have modified and returned by the parameter.



[This message has been edited by Gorg (edited 01-29-2001).]

XBCT
01-30-2001, 02:21 AM
Hi!

>Q3's written in C++?<
Nope but it uses the concept I mentioned....

The backend doesnīt alot more than just drawing triangles.
It does ALL rendering....
You pass a triangle and itīs shader and the backend renders it with multiple passes\textures and all state changes just as it is specified in the shader.
However grasping the whole Q3 architecture by some explanation from me is a little difficult, especially without code or if you arenīt really involved\intersted in such a project....
So if you are really interested then look into the source of Titan(a Q3Clone).

Greets, XBTC!

Michael Steinberg
01-30-2001, 08:40 AM
Well, I don't have abything against looking at some code, but the inet isn't currently on my comp, so I'll have to wait some days.
Titan you say? If they're so good to write a clone, why don't they just write their own game? I will never understand these cloners... http://www.opengl.org/discussion_boards/ubb/smile.gif

Eric
01-30-2001, 10:26 PM
Forgot to get involved in the "by pointer/by reference" debate: one thing that has not been mentioned is that the pointer can take a convenient value of NULL while the object cannot be a NOOBJECT as in other programming languages....

It happens that some of my functions need a pointer to be passed: if it is not NULL, they use it. If it is NULL, they do something else (usually take a default value)...

Thought I would add this to the discussion !

Best regards.

Eric

XBCT
01-31-2001, 02:33 AM
Hi!
>Titan you say? If they're so good to write a clone, why don't they just write their own game? I will never understand these cloners...<

Yes Titan....Do know why they are so good?
You get good from cloning(Donīt confuse this with the political discussions at the time http://www.opengl.org/discussion_boards/ubb/wink.gif)!
I have nearly completed my own Q3 clone now and you canīt imagine what I have learned from it....
There is no better learning mehod(for realtime 3d programming) I think as cloning a good engine which already exists and tweaking it until it is nearly as good as the original. And with Q3 you learn real cool stuff like realtime bezier-patch rendering and all that shader stuff....Concepts which can very well be used in every modern engine.
Can you say "I can code a engine with Q3īs feature set and quality"?
After cloning one you can.....

Greets, XBTC!

Michael Steinberg
01-31-2001, 03:53 AM
I never saw John Carmack cloning an Unreal though... http://www.opengl.org/discussion_boards/ubb/smile.gif Well, just kidding. I'm currently very ill (it's some illness that goes through all the country, nearly everyone who didn't take care gets it.)

Michael Steinberg
02-01-2001, 05:31 AM
I can't find Titan. There's always Titan AE popping out, even if I also search after 3d engine. Could you give me a link, XBCT?

DFrey
02-01-2001, 06:49 AM
http://talika.fie.us.es/~titan/