Skip to main content
gamedev.net
🔒 Locked

Implementing a simple material system

Started by bisonmontana 11 replies 8.9k views
Original Post
bisonmontana
bisonmontana
Hi I'm currently working on a small 3d engine in opengl (my first 3d engine). So far I am using interleaved VBO's to render textured objects. I'd like to implement a small material system in order to spice up my renderings a bit. I've never done it before so I would like some criticism. I am planning to implement it like this: 1. Have some form of manager (possible a singleton class) with a dictionary (hash/tree table) storing materials by name, filling it up with new materials as I read in the models 2. Sort all model faces by material type (if they aren't already) building a list containing a set of ranges (of faces to be rendered with one material). 3. When I render lock the VBO's and loop trough the list of ranges, setting a material and then calling glDrawRangeElements() to draw that "range". Is that how you guys do it? have I overlooked something? is there a smarter way?. oh and should I store textures as "material" too? or store it separately in another manager? Hope my question isn't too general.
ma_hty
ma_hty
Just give it a trial. Let the design do the damage. Afterward, you will know what suit your need.
AverageJoeSSU
AverageJoeSSU
One piece of advice that really helped me had to do with shaders and a lighting model.
Are you planning on using OpenGLs lighting model for your engine?

keep in mind making your own lighting model, material, and shader system can be intense, but you can learn a TON.
------------------------------ redwoodpixel.com
speciesUnknown
speciesUnknown
I'm currently doing it like this:

material DB:
class material_db;#ifndef class_material_db#define class_material_db 1#include "material.h"#include "sdl_gl_include.h"#include <string>#include <map>#include <iostream>class material_db{private:	std::map<std::string,material*>materials;	material default_material; //bound when the texture passed in cannot be found.	std::string default_folder;public:	material_db();	~material_db(){};		bool load_table(std::string filename);		//load all files listed in file filename		bool load_material(std::string name);		//manually load materal with that name. returns false on error.		//material is loaded from {this.default_folder}/.		bool bind_material(std::string name);			//material of that name is bound. Returns false on error.};#endif


individual material:
class material;#ifndef class_material_h#define class_material_h 1#include "soil.h"#include "sdl_gl_include.h"#include<string>class material{private:public:	GLuint texture;	GLuint bump;	GLuint normal;	std::string texture_location;	float rgb[3];	float diffuse[3];	float ambient[3];	float emissive[3];	float specular[3];	float shinyness;	float transparency;	material();		void create_checker_texture();	//make a default texture	bool load_color_map(std::string F);	//takes actual path and filename		bool load_color_map(char * F);	//takes actual path and filename			void apply();	~material();	};#endif


Currently i've only implemented diffuse maps, but I plan to use a different renderer anyway. (I'm currently using a custom renderer but its functionality is limited). Its simple but effective.

SOIL is an OpenGL image loading library, which I use for all my OpenGL applications. Google SOIL Opengl to find it. It has my recommendation.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
CrazyCdn
CrazyCdn
Quote:
Original post by bisonmontana
...

1. Have some form of manager (possible a singleton class) with a dictionary (hash/tree table)
storing materials by name, filling it up with new materials as I read in the models

...


No, no and no. Get singletons out of your vocab right away. They are bad, bad and once more for emphasis, bad.

Search these forums and you will find out why from some very smart people.
"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin
CrazyCdn
CrazyCdn
@speciesUnknown

bool load_table(std::string filename);

Should likely be,

bool load_table(const std::string &filename);

Const correctness just makes code more readable and gives the compiler a better chance of catching your errors and knowing what you meant to do. Also pass by reference whenever possible requires less memory and CPU time (no copying of the class to another class).
"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin
dmatter
dmatter
Quote:
Original post by bisonmontana
1. Have some form of manager (possible a singleton class)

Correction - not a singleton class [smile]

Quote:
with a dictionary (hash/tree table)
storing materials by name, filling it up with new materials as I read in the models

How are you planning to be populating this table with materials when you're reading in models? It makes more sense to populate the table of materials when you read in materials.

Quote:
2. Sort all model faces by material type (if they aren't already) building a list containing a set of ranges (of faces to be rendered with one material).

Don't sort faces individually at runtime, do it as a pre-process.

A single model may have several materials, therefore its geometry should be pre-sorted and grouped by material into separate geometry-chunks. At runtime those chunks are simply handed over to the render-core and no per-face sorting need be done.

It's to be expected that several models may happen to share a material (or a few materials), in which case each model will have its own geometry-chunk for that material. At rendertime the render-core will receive all the geometry-chunks and it can sort on a per-chunk (not per-face) basis by material so that chunks with the same material are all rendered together.

Quote:
3. When I render lock the VBO's and loop trough the list of ranges, setting a material and then calling glDrawRangeElements() to draw that "range".

Yep.

Quote:
oh and should I store textures as "material" too? or store it separately in another manager?

Actually, I find it helps to make a distinction between a shader and a material:
A shader is used to setup the pipeline state (like blending state, enabling lights, binding textures, etc)
A material is used to actually associate a shader with the data it needs (so which textures to use, colours, etc)

speciesUnknown
speciesUnknown
Quote:
Original post by Mike2343
@speciesUnknown

bool load_table(std::string filename);

Should likely be,

bool load_table(const std::string &filename);

Const correctness just makes code more readable and gives the compiler a better chance of catching your errors and knowing what you meant to do. Also pass by reference whenever possible requires less memory and CPU time (no copying of the class to another class).


Thanks for the heads up, I'll make these modifications some time. I'm one of those terrible self-taught self-styled programmers...
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
CrazyCdn
CrazyCdn
Quote:

Thanks for the heads up, I'll make these modifications some time. I'm one of those terrible self-taught self-styled programmers...


Best way to be, you really wanted to learn. Unlike a few people I know who went to school and I still help out lol.
"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin
Krohm
Krohm
Quote:
Original post by bisonmontana
1. Have some form of manager (NOT a singleton class) with a dictionary (hash/tree table)
storing materials by name, filling it up with new materials as I read in the models
I suggest to avoid using the name unless for direct, expensive content lookups.
For example, when content is loaded, an header builds a name->resource mapping. The resource manager actually loads them up and builds a assetResourceList->globalResource association.
Later references in the asset are given by IDs rather than names to speed up lookups - and I was surprised in seeing the benefit (it's nothing to scream for but it's a valuable improvement anyway).
Further headers are "merged" in the global one before loading.

Also, I would suggest to leave hashes out. While the THETA(1) thing may sound a big deal at first, it really doesn't buy you much over O(n) unless you have thousands of materials in fly at the same time. HL2 has thousands of materials in the whole game so this is unlikely to happen.
Quote:
Original post by bisonmontana
2. Sort all model faces by material type (if they aren't already) building a list containing a set of ranges (of faces to be rendered with one material).
As dmatter suggests, doing this in preprocessing is going to save alot of time. I'm not saying you should do this at runtime. You should do this before, such as in a "map compile" stage. Assuming the loaded asset to be already optimal will make everything easier and nicer. There's no real reason to NOT build "geometry chunks" at asset generation.
Quote:
Original post by bisonmontana
3. When I render lock the VBO's and loop trough the list of ranges, setting a material and then calling glDrawRangeElements() to draw that "range".
Personally I haven't found much improvement in setting the range nicely. I suppose it's an evolutionary artifact. While we're at it, I would like to know if somebody got an improvement using this.
Quote:
Original post by bisonmontana
oh and should I store textures as "material" too? or store it separately in another manager?
Textures are NOT materials and shouldn't be stored as such. Doing this would complicate lookups pretty much (I can say this by experience) and although providing the "default material" isn't difficult, it doesn't look as elegant as specifying it manually at DCC stage and the benefit is debatable at best.

Note that a "material" system nowadays is considerably more complex than you might expect. For example, depending on your tech expected life-time it may make more sense to deprecate the GL material system completely and go shader-only. This generally requires some reworking in the data structures as well since they may need to incapsulate whole "blobs" per vertex in an opaque way.
This in turn implies that you must make sure the blobs does fit the actual material which will be loaded, a simple signature is generally enough, but more involved approaches are possible. Do NOT think at CRC'ing or SHA'ing the material code as a whole, this would make impossible to replace materials without rebuilding everything. However, in some cases this could be useful or even required. I personally like more the idea of allowing material replacing as long as the signature is compatible... whatever it exactly means. ;)
Quote:
Original post by Mike2343
Best way to be, you really wanted to learn. Unlike a few people I know who went to school and I still help out lol.
Copy. Sometimes they do some really funny stuff.
"I've proofed this executes in THETA(n) so it's better than yours"
"Good, but I see you're leaking half of your pointers"
Believe it or not, it's a true story! ;) (OMG, our schools here are so bad at delivering industry-oriented developers! I'm sure this was a rather extreme case, I apologize to everyone but I just want to say the hard way rocks better!)
Previously "Krohm"
speciesUnknown
speciesUnknown
Quote:

Copy. Sometimes they do some really funny stuff.
"I've proofed this executes in THETA(n) so it's better than yours"
"Good, but I see you're leaking half of your pointers"
Believe it or not, it's a true story! ;) (OMG, our schools here are so bad at delivering industry-oriented developers! I'm sure this was a rather extreme case, I apologize to everyone but I just want to say the hard way rocks better!)


QFT except that on my C course they are teaching about safe memory management by default. (they just covered malloc and free and the tutors seem to really know what they are doing. Perhaps we're lucky?)
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Krohm
Krohm

It's just that your professor know The Right Way and teaches you a Real Language. I'm sure he know his business! It's not a question of luck but rather a functional, efficient formation infrastructure!
Previously "Krohm"
bisonmontana
bisonmontana
First of all, thanks for all the lengthy replies. I'll be reading them a few times more to be sure I don't miss anything :).

to dmatter and krohm:
Don't worry, I am doing all the face sorting and material lookups when I load the geometry, not while rendering. Oh and I am building the material database while loading materials and not models of course :).

What I ended up doing is basically this:

I load all materials into a material database, as of right now my material class also holds a texture id (I don't think I understand the difference between shaders and materials) and it seems logical since mesh file formats and 3d editors associate materials and textures.

For each mesh, not touching the order of the vertices, I instead sort the faces according to material.

This preprocess leaves me with 2 VBO's per mesh, one containing vertices, normals and texcoords (interleaved), the other containing faces sorted by material.
My preprocessor also builds a structure I call a FaceMaterialList for each material used in the mesh. This structure holds a direct reference (no hash/tree lookup while rendering) to a material as well as VBO/array offset and length.

When I render the mesh I simply loop trough all FaceMaterialLists, setting material and drawing the range of faces with glDrawRangeElements.

Krohm:
Replacing the lightning system with something homemade is a bit to advanced for me as of yet. I am still using the fixed pipeline for everything :).

Why the hate for the singleton pattern?

Oh perhaps I should mention that I am doing this project in java with lwjgl. But I am a lot more familiar with C++. Reason I chose java is because it will allow me to do accelerated 3d on 3 platforms, just by clicking on a webstart link (of course I am aware of the performance penalties but I am not making the next Crysis).

oh and a little screenshot, no textures just materials and some test models I borrowed from Scifimeshes.com (great site): http://indianernavn.dk/s.png

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.