Hi,
I've seen some topics about this theme but I'm not sure I've found that I'm looking for.
I'm sorry, it will be a bit long, hope someone will read this. And sorry for my broken english! :)
Let me write about my current design/implementation. I've tried to mix some ideas from the UE4 and Unity.
First, here is a list of the classes with small descriptions.
Shader:
The lowest level class in this group. It can be Vertex or Fragment/Pixel shader. Nothing more or less. In the OpenGL implementation, it contains the shader object id and the shader compilation function.
Effect:
Slightly higher level; this class is a group of Shaders. This class can be used to set parameters (uniforms). In the OpenGL implementation, it contains the program object id and the linking function (using the compiled shaders). So this is a single, valid effect which can be used to render objects. Also this class is interpreting the full source code and generates some things, like the used attributes and uniforms. This way a Mesh can be checked if it has enough vertex data (attribute) to be rendered by this effect.
Material:
This is a mid/high-level class; This class could be called as EffectVariantCollection, because it is: The shaders can be written with defines, ifdefs and so on. So a single Effect can be compiled into multiple variants. A good example is the Unity's Standard shader, which is a big über-shader (or Effect in my case) and is compiled into multiple variants based on keywords. This is what my Material class does: it can compile an effect multiple times with different defines added to the beginning of the source code.
MaterialInstance:
This is the highest level class; it simply contains a reference to a valid Material and can be attached to Meshes. A MaterialInstance contains a list of keywords which are concatenated with the keywords provided by the renderer. The combined keywords are used to "select" the proper Effect variant, using the referenced Material.
So comparing with the Unity's terminology:
Unity Shader = Material
Unity Material = MaterialInstance
I've written a simple text parser and this is how I can define Materials. This text file is similar to the Unity's Shader file.
I've just started implementing parameter handling and I'm not sure about some things. Here is the cropped code:
class MaterialParameter
{
public:
enum Type
{
Float = 0,
Vec2,
...
}; // enum Type
MaterialParameter(const std::string& name, const Type type);
const std::string& GetName() const;
Type GetType() const;
template <typename T>
static Type TypeOf(); // specialized for float32, Vector2, etc.
protected:
const std::string name;
const Type type;
}; // class MaterialParameter
typedef std::shared_ptr<MaterialParameter> MaterialParameterPtr;
template <typename T>
class MaterialParameterValue final : public MaterialParameter
{
public:
T value;
MaterialParameterValue(const std::string& name, const T& defaultValue);
const T& GetDefaultValue() const;
private:
const T defaultValue;
}; // class MaterialParameterValue
typedef MaterialParameterValue<float32> MatParamFloat;
typedef MaterialParameterValue<Vector2> MatParamVec2;
// ...
typedef std::shared_ptr<MatParamFloat> MatParamFloatPtr;
typedef std::shared_ptr<MatParamVec2> MatParamVec2Ptr;
// ...
I also have a MaterialParameterCollection class which contains a map<string, parameter> and some functions:
class MaterialParameterCollection
{
public:
template <typename T>
std::shared_ptr<T> GetParameter(const std::string& name) const;
template <typename T>
bool AddParameter(const std::string& name, const T& defaultValue);
void ApplyToEffect(const IEffectPtr& effect) const;
private:
typedef std::map<std::string, MaterialParameterPtr> Parameters;
Parameters parameters;
MaterialParameterPtr GetParameter(const std::string& name, const MaterialParameter::Type type) const;
}; // class MaterialParameterCollection
And the biggest problem is with the ApplyToEffect function. Of course I can iterate over the map, switch on the type, cast and call the set uniform function, like in the current implementation:
void MaterialParameterCollection::ApplyToEffect(const IEffectPtr& effect) const
{
for (auto it = parameters.begin(); it != parameters.end(); ++it)
{
const MaterialParameterPtr& param = it->second;
IShaderUniformPtr uniform = effect->GetUniform(param->GetName());
if (!uniform)
continue;
switch (param->GetType())
{
case MaterialParameter::Float:
effect->SetUniform(uniform, static_cast<MatParamFloat*>(param.get())->value);
break;
This first solution I've used was a virtual function in the base MaterialParameter class and the template class just overrided it and called the effect's SetUniform fcuntion. However, the reason why I'm using this approach are the textures. Because I cannot set a texture directly (to OpenGL at least) just a "sampler id". But the parameter itself does not know anything about the other parameters so a TextureParameter does not know which id it should use.
- The renderable objects can now only be sorted by effect first then textures (but I think it's okay, I would do this anyway).
- Is this an acceptable approach?
- Any tips?
Let me know If anyone interested in some details.
Thanks