Sunday, December 13, 2015

How to handle materials in an Entity/Component system


My E/C implementation is the basic one where Entities are just ID's, Components are data and Systems act on the Data. Right now I'm having trouble with object materials and rendering in general. For simple objetcs I have a ModelComponent, tied to a RenderSystem, ModelComponent has the vertex buffer ids that the render system uses. A simple MaterialComponent would probably have color or specular strenght, etc, but I wanted it to be flexible enough to allow for more than one render pass and general "effects" that are not as easy as a simple variable in the MaterialComponent.


Trying to solve these problems I came up with two solutions:



1 - Super-generic Material Component


Something like this:


struct Material : public Component
{
ShaderData* shader;
std::vector> uniforms;
[...]
};

and in the render system I would loop and pass the uniforms to the shader. I suppose this would be slow, but fast enough for my purposes.



2 - Another layer of abstraction, MaterialData


Having a class to wrap specific materials, that could be inherited by whatever specialized material, the base class would have something like void set_shader_constants(ShaderData* d) but the implementation is up to each class, and the MaterialComponent would have a pointer to a MaterialData object.


I'm not sure which approach I would prefer, but neither of these touch the subject of multiple passes, or other complex rendering techniques.


Any idea on how to accomplish this?



Answer



Materials are a graphics concept, and belong in your renderer. A renderer is too low-level a piece of architecture to be built on top of an entity system. Entity systems should be for higher-level game objects. Not everything needs to be a component, and in fact, it's generally a bad idea to try to force everything into a single paradigm like that. It creates a least-common-denominator solution.


Consequently I would advise that you take a different approach:



  • A material is just another type in your renderer.

  • Your renderer has a type that represents "a thing to be drawn to the screen." Frequently these are called "render instances" or "renderables" or even "models." This type has a reference to the material it will use when drawing and provides a public API to allow a consumer of the renderer to set that material to whatever is desired.



This is essentially asking you to take your ModelComponent and rename it Model, removing the dependency on the entity/component layer and thus moving it to a lower layer of abstraction, alongside the rest of your renderer.


Then, you do this:



  • In the same layer of abstraction as your other components, you have some kind of "aspect component" which represents the visual presentation of an entity. This component just contains a reference to some renderable (as described above), which in turn contains the reference to a material. The component may provide an API to expose the renderable (thus allowing clients to manipulate it) or it may wrap the renderable's API to control exposure. That's up to you.


This addresses the component interdependence issue you're running into by having models and materials both be components; an entity should either have an aspect or not, and that aspect should be able to encode everything about the presentation of the entity, including the material.


This also affords you the flexibility to take other approaches with the material object that would be harder to do with that object as a component because of the lack of parity with the rest of the render system abstraction.


Your issue of allowing for more complex effects and multiple passes is one that could be solved primarily in the material, by exposing functions to query and set named shader constants exposed by the material's shader file. This is especially true if you use effect files (in D3D) which support multiple passes and the like. Even if you aren't using effect files, you can expose the idea of multiple passes from the material, each with distinct shaders, and allow the material API to provide manipulators for that. It would be easier and cleaner to integrate into the rendering API most likely, since material is now at the same level of abstraction.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...