Polycode provides a material system that lets you create and assign shader-based materials to meshes. You can create materials visually in the Polycode IDE or directly in code. Polycode includes a number of default materials you can use.
Polycode includes a number of default materials included in default.pak that we can assign out of the box.
To assign a material in the IDE entity editor, select the entity you want to assign the material to (it has to be a SceneMesh or a SceneMesh subclass like ScenePrimitive or SceneLabel) and click on the "Change" button in the material property sheet. This will open the material browser and allow you to select the new material. The material browser displays the materials arranged by resource pools (see Resource management and Entity Instances for details).
After a material is selected, you will see its options displayed below the material property sheet. In these options, you can assign textures used by the material and change shader unifroms (controllable proprties of the shader).
If the material already defines its own textures and properties, you will see them here and can override them per each individual assignment.
If you want to apply a material from a custom material library, you first need to link this material library in the entity settings of the entity editor.
Click on the entity settings button at the top of the entity editor property pane, then click on the "Link materials file" button. After selecting a material library file, it will become available in the material browser when you change materials and you will be able to access the materials available in this material files.
You can assign materials in code to any SceneMesh subclass. You can either assign a material by name or by a direct reference to a material resource. If we assign materials by name, we need to specify a resource pool (see Resource management) to get search for the material in. All default materials are loaded into the global resource pool.
Scene *scene = new Scene(Scene::SCENE_2D); ScenePrimitive *box = new ScenePrimitive(ScenePrimitive::TYPE_VPLANE, 0.5, 0.5); ResourcePool *globalPool = Services()->getResourceManager()->getGlobalPool(); box->setMaterialByName("Default", globalPool); scene->addChild(box);
scene = Scene(Scene.SCENE_2D) box = ScenePrimitive(ScenePrimitive.TYPE_VPLANE, 0.5, 0.5) globalPool = Services.ResourceManager:getGlobalPool() box:setMaterialByName("Default", globalPool) scene:addChild(box)
Note that materials which require lighting will show up black unless lights are added to the scene.
Alternatively, you can request a material manually from a resource pool and assign it directly.
Material *material = (Material*)globalPool->getResource(Resource::RESOURCE_MATERIAL, "Default"); box->setMaterial(material);
material = safe_cast(globalPool:getResource(Resource.RESOURCE_MATERIAL, "DefaultTextured"), Material) box:setMaterial(material)
If you want to load materials from a custom material library file, you need to load it into a resource pool. You can load it either into the global resource pool or your own custom resource pool (see Resource management) via the MaterialManager.
You can set textures and uniform properties to applied materials at runtime. There are two sets of options for materials, one applied to the material itself and shared across all of the instances of the material and one specific to the material binding of each SceneMesh. This allows you to control material properties globally for each material and set properties of the material for each individual binding.
Most of the time, you want to control the properties for each individual binding. This is done using the ShaderBinding class member of SceneMesh, accessible via the getLocalShaderOptions method. A ShaderBinding contains a list of textures and uniforms that are applied to the material shaders as uniforms. When a material is assigned to a SceneMesh, its local shader options are blank, effectively using the ShaderBinding settings of the material. If you add parameters or textures to the local shader binding, they will override the same settings in the global material.
Parameteres can be of different types, depending on the shader's uniform types, but they all share the same class LocalShaderParam. Depending on the type of parameter, the correct method must be used to set the parameter data. Here's a list of all supported parameter types, their GLSL uniform type equivalents and data set and get methods.
ProgramParam::PARAM_NUMBER - float - setNumber - getNumber
ProgramParam::PARAM_VECTOR2 - vec2 - setVector2 - getVector2
ProgramParam::PARAM_VECTOR3 - vec3 - setVector3 - getVector3
ProgramParam::PARAM_COLOR - vec4 - setColor - getColor
ProgramParam::PARAM_MATRIX - mat4 - setMatrix4 - getMatrix4
Here is an example of setting the diffuse_color uniform. This uniform is of type PARAM_COLOR.
ShaderBinding *binding = box->getLocalShaderOptions(); LocalShaderParam *param = binding->addParam(ProgramParam::PARAM_COLOR, "diffuse_color"); param->setColor(Color(1.0, 0.0, 0.0, 1.0));
binding = box:getLocalShaderOptions() param = binding:addParam(ProgramParam.PARAM_COLOR, "diffuse_color") param:setColor(Color(1.0, 0.0, 0.0, 1.0))
Binding textures or cubemaps works similarly via the ShaderBinding. You can bind any existing Texture via the addTexture method of ShaderBinding.
Texture *texture = Services()->getMaterialManager()->createTextureFromFile("dummy.png", false, false, globalPool); binding->addTexture("diffuse", texture);
texture = Services.MaterialManager:createTextureFromFile("dummy.png", false, false, globalPool) binding:addTexture("diffuse", texture)
To set parameters globally in the material, instead of the material binding instance, use the ShaderBinding of the Material itself. Keep in mind that these parameters are added automatically when a material is created, so we want to get the exiting local params, not add them as we did to the material binding instance.
To get the material shader binding, we use the getShaderBinding method, which takes an shader index. For all non-screen materials, the shader index will always be 0, as they only use 1 shader. For scren (post-processing) materials, which can use multiple shader passes, each shader has its own shader binding.
ShaderBinding *binding = material->getShaderBinding(0); binding->getLocalParamByName("diffuse_color")->setColor(Color(1.0, 0.0, 0.0, 1.0));
binding = material:getShaderBinding(0) binding:getLocalParamByName("diffuse_color"):setColor(Color(1.0, 0.0, 0.0, 1.0))
The Polycode IDE comes with a material editor that allows you to create your own materials and save them to material files.
Material files (.mat) are XML-based files that can contain definitions for shaders, materials, cubemaps and post-processing materials. The Polycode IDE lets you edit all of these.
To create your own materials, you need to first create a material library. Add a new file to your project and choose "Material Library" in the file templates.
After opening the material file, all of the shaders, materials, cubemaps and post materials will be listed in the material browser on the right side.
To create a new material, click on the material create button above the material browser. This will add a new material to the material library and open it for editing. Here you can define the material name, shader to use and the color blending mode of the material. The shaders available to you in the dropdown are the default shaders plus any shaders created in this material file.
If you want to create a custom shader for your material, you need to create a new shader in the material library. Click on the create shader button on top of the material browser to create the new shader. The new shader will be added to the material browser and will open for editing. Here you can define the name of the shader, the vertex and fragment programs that it will use and the number of lights passed to it by the renderer. After the shader is created, it will be available to you in the shader dropdown of all the materials in this material library.
You can also create materials directly in code without using material files.
The code below demonstrates creating a shader using custom vertex and fragment programs and then creating a material using the custom shader, however, you can create materials using the default shaders that come with Polycode.
MaterialManager *materialManager = Services()->getMaterialManager(); ShaderProgram *program = materialManager->createProgramFromFile("my_shader.frag"); globalPool->addResource(program); program = materialManager->createProgramFromFile("my_shader.vert"); globalPool->addResource(program); Shader *myShader = materialManager->createShader(globalPool, "glsl", "MyShader", "my_shader.vert", "my_shader.frag", false); myShader->numPointLights = 4.0; myShader->screenShader = false; globalPool->addResource(myShader); Material *myMaterial = materialManager->createMaterial(globalPool, "MyMaterial", "MyShader"); globalPool->addResource(myMaterial);
materialManager = Services.MaterialManager program = materialManager:createProgramFromFile("my_shader.frag") globalPool:addResource(program) program = materialManager:createProgramFromFile("my_shader.vert") globalPool:addResource(program) myShader = materialManager:createShader(globalPool, "glsl", "MyShader", "my_shader.vert", "my_shader.frag", false) myShader.numPointLights = 4.0 myShader.screenShader = false globalPool:addResource(myShader) myMaterial = materialManager:createMaterial(globalPool, "MyMaterial", "MyShader") globalPool:addResource(myMaterial)