Skip to main content
gamedev.net
🔒 Locked

Creating a render chain

Started by INVERSED 11 replies 1.5k views
Original Post
INVERSED
INVERSED
I know that we''ve been going over this a lot in a couple of other threads, but if I don''t put my thoughts to paper, I will remain confused. Here''s what I''m thinking, tell me if I''m missing anything. Please not that some of the things I''m thinking diverge somewhat from what was discussed pre prep 1. Define a bunch of effects in a file. These determine how geomentry should look 2. Define a bunch of graphic materials in a file. These define the effect to use and the texture to use with it. engine init 3. On engine load, parse the effect file into a list of effects 4. Check for hardware support of shaders. 5. Attach shaders to effects render 6. traverse the scenegraph so that all matrices can be properly computed. This also updates an object, like updating the behavious of an object or animations 7. when you get to an object generate a render pass object. This object stores the geometry that needs to be rendered. It also has a pointer to what shader should be used based on the material specified. 8. render pass objects will be gathered in 4 seperate lists ( reflect, rrefract, main, and transparent ). Each list will be sorted by states, so in otherwords by material (as opposed to by effect ). A few questions for those willing to answer. Why are effects in a text file? They don''t seem very dynamic or porgrammable. Really, the shader seems to do all the work, and it''s hardcoded how well a shader renders an effect. Are the necessary streams for the effects the only thing you pull out of the effect file? If I change an effect file, what should be the physical change on the screen? What is the difference between the reflect and refract passes. Reflect would be for mirrors and what not, but when do you need to refract? Finally, what would be in a preprocess and post process pass?
Write more poetry.http://www.Me-Zine.org
jamessharpe
jamessharpe
quote:
Original post by INVERSED
I know that we''ve been going over this a lot in a couple of other threads, but if I don''t put my thoughts to paper, I will remain confused. Here''s what I''m thinking, tell me if I''m missing anything. Please not that some of the things I''m thinking diverge somewhat from what was discussed

pre prep
1. Define a bunch of effects in a file. These determine how geomentry should look
2. Define a bunch of graphic materials in a file. These define the effect to use and the texture to use with it.

engine init
3. On engine load, parse the effect file into a list of effects
4. Check for hardware support of shaders.
5. Attach shaders to effects

render
6. traverse the scenegraph so that all matrices can be properly computed. This also updates an object, like updating the behavious of an object or animations
7. when you get to an object generate a render pass object. This object stores the geometry that needs to be rendered. It also has a pointer to what shader should be used based on the material specified.
8. render pass objects will be gathered in 4 seperate lists ( reflect, rrefract, main, and transparent ). Each list will be sorted by states, so in otherwords by material (as opposed to by effect ).




This all seems ok, except you''re missing the steps for bouncing the shader passes to other shaders for multi-pass algo''s.

quote:

A few questions for those willing to answer. Why are effects in a text file? They don''t seem very dynamic or porgrammable. Really, the shader seems to do all the work, and it''s hardcoded how well a shader renders an effect. Are the necessary streams for the effects the only thing you pull out of the effect file?



The effects are only in a text file for ease of development as far as I can tell. The only reason I can see for it is to enable updating the effect definition list to include new effects, although I can''t see why you don''t hard-code the base effects and optionally load an effects file if you want to allow the engine to have extra effects added for new shaders.

quote:

If I change an effect file, what should be the physical change on the screen?



If you mean change one of the effect definitions, you''ll probably crash the engine or get some very weird results, since the shaders will be expecting certain streams. Unless of course you come up with a special meta-description of effects and shaders that allows you do dynamically redetermine the best shader for the job - although this would be a pretty complex system to implement!

quote:

What is the difference between the reflect and refract passes. Reflect would be for mirrors and what not, but when do you need to refract? Finally, what would be in a preprocess and post process pass?


Refraction can be used for effects such as snell''s law in water simulations. The preprocess step can be used to generate textures either procedurally via VS/PS or using the given data.
The post process step can be used for effects such as deferred shading, blurring etc..

In my implementation I also add a final pass that is only executed on the first call to the main render function. This pass is to draw overlays to allow for a GUI system to be passed through the same pipeline.

James
INVERSED
INVERSED
Thanx for the reply. I think I can pull that off. I might have to go for not using the effects in a file, but whatever.
Write more poetry.http://www.Me-Zine.org
sBibi
sBibi
quote:
Why are effects in a text file? They don''t seem very dynamic or porgrammable.


I guess, like jamessharpe said, for ease of development...

quote:
Really, the shader seems to do all the work, and it''s hardcoded how well a shader renders an effect.


not with what Yann described in his last post, ie: meta bouncing

quote:
Are the necessary streams for the effects the only thing you pull out of the effect file?


actually, I don''t use exactly the same system. in my implementation, each effect has a default shader that renders it, that is, each shader is an effect.

when linking effects to shaders, if the effect''s shader isn''t available for one reason or another (poor hardware), the bouncing takes place:

each effect in the effect files has a list of the other effects it is similar to an that can be rendered instead, with a priority. (you can have multiple files, there is a script command that allows you to scan a directory for effects file, I thought that would be more modular that one single file with all effects in it. when you add an effect, you don''t need to touch the only single effect file, you can make an archive with a dll and a new effect file containing only the new effects)

so that is the first "bouncing" type YannL talked about, some kind of hardcoded bouncing, but not that hardcoded, as it''s only in a text file, and you don''t need any recompilation of any kind.

if no effects can be rendered in place of the current effect, the bouncing switches to meta bouncing, and tryes to find out the effect that best fits the visual appearance of the current effect.

I''m still implementing that part, but what I do right now (if you guys have ideas about that it would be great!), is each effect has a list of properties, each property having a weight, and a priority.

right now, I only have:

- diffuse levels
- bump levels
- specular

more will be needed of course, like reflectivity, refractivity, etc... but right now, that''s only for testing.

let''s say we have a surface that has one diffuse and one detail texture, one bump texture, and that is very shiny.
we will have:

diffuse_levels 2
bump_levels 1
specular 90

so if we try to define the surface appearance, we also need to set the "priorities" of each of those fields.
the bump map can add almost nothing to the surface aspect, in that case, the priority should be low compared to the base diffuse texture and the shininess:

diffuse_levels 2 100
bump_levels 1 10
specular 90 50

so all that is in the effects files, and each effect has these. if we don''t care about a particular field, it just doesn''t have to be written (by default, all fields are set to 0 priority)

the problem I have right now is, what if the detail texture hardly adds anything to the surface aspect? in that case, we would need multiple "diffuse" fields, same with bump, so more something like:

diffuse 0 100
diffuse 1 5
bump 0 10
specular 90 50

and when the engines searches the best fit effect to replace the current one (for witch all the other resolving and bouncing attempts have failed), it takes into account those priorityes.
so if the engine finds an effect that has 5 priority for bump, 50 priority for diffuse 1, and 100 priority for diffuse 0, and another effect that has 5 priority for bump, 0 priority for diffuse 1 (no detail), 100 priority for diffuse 0, the current effect will link to that second effect (the one that best matches the required priorities)


btw, I don''t store the vertex streams in the effects files, as only the shaders know what vertex streams they need. it''s only once that shader bouncing is done, and once all dependencies are resolved, the engine queryes each shader through a callback to get the streams they need.

I''m still searching ways to improve all that, so if you guys have ideas, I will be very thankful

INVERSED> hope that helps, even if it isn''t an _exact_ implementation of what YannL described...
McZ
McZ
what stuff should go into the material file/class

I have a class defined like this

class cMaterial {
public:

int EffectID;

cColor4f Ambient; // the same as if it would be float Ambient[4]
cColor4f Diffuse;
cColor4f Specular;
float Shininess;

// Transparency
eBlendFactor SrcBlendFactor;
eBlendFactor DstBlendFactor;

// Textures
cTexture *Diffuse;
cTexture *Bumpmap;
cTexture *Lightmap;
// more textures?
};

class cTexture {
public:

cTextureData *TextureData; // the texture data ( the same texture data may be used by different textures )

// some texture stuff like blending, scale, rotating whatever?
...
};

what else may I need?

the engine ignores everything in the cMaterial class that isn''t supposed to be used by the effect.

or should I define it some other way?
jamessharpe
jamessharpe
quote:
Original post by McZ

the engine ignores everything in the cMaterial class that isn''t supposed to be used by the effect.

or should I define it some other way?


Why don''t you use a data stream for the data - the shader will know what data it is expecting and in what order, so you can just pass it a stream of data and it can find the data. This way you are not putting any limits on the data that you can pass, bloating the structure where one float parameter may do.

James
INVERSED
INVERSED
Ok, I''ve thought about it more and made some decisions. First, I like SBibi''s idea for the effect files, so I think my effect files are essentially going to be a weighting system for the various textures and what not used, so an effect would look like this in my engine

Effect( 100 ){
.description "Diffuse texture and light"
.diffuse0 10
.vnormal 10
}

These weights will be multiplied by a shaders ability to do these various things. I think I''m going to need a way for the priority system to measure when a shader does too much work as well. I don''t want a situation where say a cubemap gets applied to a simple object. I think I''ll generate a best fit priority, and whoever comes closest to that renders the effect. for instance, the above effect would have a priority of 200. So a shader that say lacked diffuse light would be at a 100, while a shader that added a detail texture pass might be at say 210.


The issue I''m having now is how to best get my data to my shader. For instance, I have a simple heightmap class. When I create that heightmap, should I generate as much data as would possibly be used at first. If I do that, I''m unsure as to when to scale it back. In the setup I have now, the geometry sits in the scenegraph, and when rendering the scene we traverse the graph, figure out what geometry should be drawn, and create an object that can be placed in a rendering chain. I was thinking I could send the data to the render object, which I suppose would be the shader. I could send it everything, and it could sort out what it did and didn''t want. Anyone have a better way? I''m just not sure how to get around the problem of not knowing what your shader wants at the level of your geometry class. Sure, 90% of the time You just want a texture, a detail, and a bump, but what id you suddenly decide on two detail textures. My thinking is that only the effect file should need to change, and the engine will follow suit. That''s the wish list at least
Write more poetry.http://www.Me-Zine.org
McZ
McZ
quote:
Original post by jamessharpe

Why don't you use a data stream for the data - the shader will know what data it is expecting and in what order, so you can just pass it a stream of data and it can find the data. This way you are not putting any limits on the data that you can pass, bloating the structure where one float parameter may do.

James


something like this then?

void *DataStream;

and then the shader decodes the datastream with the specefic dataformat defined by the Effect class..

but I don't understand how I should store the textures and so... could I use the above cTexture class that has the information on how the texture should be rendered and a pointer to the texture?!

but I don't really understand how I should decode the data? can I use some if stuff and a loop of some kind?

e.g.
cColor4f Ambient;
for ( int i=0; i{
if( DataFormat & DF_AMBIENT )
{
// store the ambient color in some struct for later use
Ambient = cColor4f( DataFormat[ i ], DataFormat[i+1], DataFormat[i+2], DataFormat[i+3] );
i += 4;
}

if ( DataFormat & DF_DIFFUSE_TEXTURE )
{
// store the diffuse texture in some struct for later use.
...
i += sizeof( cTexture );
}
}

// use the stuff from above
....

[edited by - McZ on December 27, 2003 6:35:42 PM]
jamessharpe
jamessharpe
quote:
Original post by McZ

something like this then?

void *DataStream;

and then the shader decodes the datastream with the specefic dataformat defined by the Effect class..



Yes, Exactly.

quote:

but I don''t understand how I should store the textures and so... could I use the above cTexture class that has the information on how the texture should be rendered and a pointer to the texture?!



What you have is a TextureManager with which the Ctexture function is registered. The Manager will give you a Handle/ID for that texture. This is how you refer to it in the Params. In my case I simply use the GLuint ID since this makes the binding very simple.

quote:

but I don''t really understand how I should decode the data? can I use some if stuff and a loop of some kind?

e.g.
cColor4f Ambient;
for ( int i=0; i{
if( DataFormat & DF_AMBIENT )
{
// store the ambient color in some struct for later use
Ambient = cColor4f( DataFormat[ i ], DataFormat[i+1], DataFormat[i+2], DataFormat[i+3] );
i += 4;
}

if ( DataFormat & DF_DIFFUSE_TEXTURE )
{
// store the diffuse texture in some struct for later use.
...
i += sizeof( cTexture );
}
}

// use the stuff from above
....

[edited by - McZ on December 27, 2003 6:35:42 PM]


You just get from the effect class the locations of the different texture types i.e. a function in the effect class :
int GetTexOffset(TexType type)

This then tells you the offset into the stream that the textureID is. You can then have a function GetUserDataOffset, which returns a pointer to a stream that''s format is known to the shader e.g. it may contain A texture matrix, then the ambientColor, then the opacity value.

Of course you can reduce the number of items in the UserData and increase the number of Get*Offset functions. Depends on how many things you want to be able to have specific access to. I''d recommend having the commonly used parameters in specific functions so that you can change to lower shaders and still access the data, otherwise they would need to know how the data is laid out in memory.

I hope that''s of some help. I''ve not actually implemented this myself yet, so I''m not yet aware of the hazards, but hopefully there are none.


McZ
McZ
how do I know the offset for the texture then? or what I need.. I understand that I have to store the data in the stream in a specefic order to calculate the offset, but I don't really know how I should do.

lets say this is my order and size
AMBIENT ( 4 floats )
DIFFUSE ( 4 floats )
SPECULAR ( 4 floats )
SHININESS ( 1 float )
DIFFUSE_TEXTURE ( 1 unsigned int )
...

if I want the ambient color then that one is allways first.. but only if it exists.. how do I know that? and how do I get the Diffuse texture offset depending on what exists before..

if I have this format ( AMBIENT | DIFFUSE | DIFFUSE_TEXTURE )

then the offset to the texture ID would be 8*sizeof(float) (32 bytes?) into the data stream.

but if the format is ( DIFFUSE_TEXTURE ) then the texture id is first in the stream ( well the only thing there is )


if I only have floats ( AMBIENT | DIFFUSE ) then I can access the stuff something like this

float *ambient = (float*)DataStream;
float *diffuse = (float*)DataStream + 4; // 4 is becouse the diffuse offset is 4 floats into the DataStream.

could I use the same aproach for the texture offset thing? although I can't figure out how to calculate the offset.. what if the data type is different then? becouse the above only is float then that aproach works nice.. will it work like that if the size is different?

[edited by - McZ on December 27, 2003 7:57:43 PM]
INVERSED
INVERSED
Has anyone figured out how best to store the raw vertex data. In other words, the position, the normal, the texture coords etc. The example I give would be for say a heightmap terrain. I was thinking that it would be inflexible to hardcode what data the heightmap would make use of, because it you set it up to use say just a normal and a texture, and then decided to change the effect to one that used a detail texture, the height mao vertices would not contain the proper data. The idea that occurs to me is to have sort of a compile step where the level is loaded with as much data as possible, which is then reduced to what is necessary by the shader, and somehow passed back to the terrain. I''m curious, how do other people do it?
Write more poetry.http://www.Me-Zine.org
jamessharpe
jamessharpe
quote:
Original post by INVERSED
Has anyone figured out how best to store the raw vertex data. In other words, the position, the normal, the texture coords etc. The example I give would be for say a heightmap terrain. I was thinking that it would be inflexible to hardcode what data the heightmap would make use of, because it you set it up to use say just a normal and a texture, and then decided to change the effect to one that used a detail texture, the height mao vertices would not contain the proper data. The idea that occurs to me is to have sort of a compile step where the level is loaded with as much data as possible, which is then reduced to what is necessary by the shader, and somehow passed back to the terrain. I''m curious, how do other people do it?


The way you store it is either interleaved or in sets of vertices. i.e. either position, tex coord, colour, position, tex coord, colour etc...
or Vertex, Vertex, ... , tex coord, tex coord, ... , colour, colour ...

This format is implicitly linked to the effect class used for
this particular geometry chunk. So to find out where the data is you have functions in the effect class which return the offset of each element type and the stride, which is sufficient data to set up vertex pointers, or copy the data to VBO''s.

You''re thinking along the right lines with the minimum data required. You need a scene compiler of sorts that looks at the effect bound and puts the data into the correct streams, based upon the effect class definitions. Of course this assumes that the effects assigned to each chunk is kept constant. If you require different effects to be able to change, I''d recommend creating a second geometry chunk and use the scene graph to switch between the two effects i.e. have two copies of the data OR have a single copy which has the maximum data required but custom effects which work together to allow the shaders to retrieve the data from the same stream.

quote:
Original post by McZ
how do I know the offset for the texture then? or what I need.. I understand that I have to store the data in the stream in a specefic order to calculate the offset, but I don''t really know how I should do.

lets say this is my order and size
AMBIENT ( 4 floats )
DIFFUSE ( 4 floats )
SPECULAR ( 4 floats )
SHININESS ( 1 float )
DIFFUSE_TEXTURE ( 1 unsigned int )
...

if I want the ambient color then that one is allways first.. but only if it exists.. how do I know that? and how do I get the Diffuse texture offset depending on what exists before..

if I have this format ( AMBIENT | DIFFUSE | DIFFUSE_TEXTURE )

then the offset to the texture ID would be 8*sizeof(float) (32 bytes?) into the data stream.

but if the format is ( DIFFUSE_TEXTURE ) then the texture id is first in the stream ( well the only thing there is )



Exactly, this is how you setup the offsets in the effect class. The effect class knows what data is needed to render it, and so you can calculate the offsets within the effect class.

quote:

if I only have floats ( AMBIENT | DIFFUSE ) then I can access the stuff something like this

float *ambient = (float*)DataStream;
float *diffuse = (float*)DataStream + 4; // 4 is becouse the diffuse offset is 4 floats into the DataStream.



If you know in the shader class that all the parameters are floats, then you can do this, although the 4 should not be hardcoded it should be a call to the effect class method GetDiffuseOffset();

So an example would be:

float * ambient = (float *)(DataStream + effect->GetAmbientOffset());
float * diffuse = (float *)(DataStream + effect->GetDiffuseOffset());
TexHandle diffuse = (TexHandle)(DataStream + effect->GetTexOffset(TEX_DIFFUSE));
McZ
McZ
I have tried to put that into my Effect class.. but I can''t figure out how to get the textureOffset thing work correct.. so far I can''t

Topic Locked

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

Sign in to reply to this topic.