-
Notifications
You must be signed in to change notification settings - Fork 19
Mesh Renderer Legacy
Current Wiki index can be found here
NOTE FOR OS X USERS - The MeshRenderer
requires an OpenGL 3.2 compatibility context as it mixes SFML and OpenGL 3.2 rendering. From the SFML OpenGL page:
OpenGL versions above 3.0 are supported by SFML (as long as your graphics driver can handle them). Support for selecting the profile of 3.2+ contexts and whether the context debug flag is set was added in SFML 2.3. The forward compatibility flag is not supported. By default, SFML creates 3.2+ contexts using the compatibility profile because the graphics module makes use of legacy OpenGL functionality. If you intend on using the graphics module, make sure to create your context without the core profile setting or the graphics module will not function correctly. On OS X, SFML supports creating OpenGL 3.2+ contexts using the core profile only. If you want to use the graphics module on OS X, you are limited to using a legacy context which implies OpenGL version 2.1.
In short the MeshRenderer
is unavailable on OS X. The rest of xygine works fine, however. Windows and linux users are unaffected, providing their graphics hardware drivers support the necessary compatibility context.
The MeshRenderer
is responsible for drawing the model components in a scene. The MeshRenderer
automatically translates the given scene's camera into 3D space and aligns it with the 2D world so that 3D models will fit, creating a 2.5D like effect. The mesh renderer itself is an SFML drawable, so can be drawn on top of a scene like any other drawable class, but can also return a drawable component, allowing the 3D view to be drawn on any layer in a Scene
. The 3D rendering system is made up from a collection of classes which together allow the MeshRenderer
to integrate with a Scene
, after a small amount of setup. MeshRenderer
objects are created in the same scope as the scene to which they are applied. They also need their update()
and handleMessage()
functions called once per frame, usually directly after updating the scene/handling scene messages.
Models are the 3D components which are attached to entities within the scene. They are comprised of Meshes and one or more Materials. The MeshRenderer
is (as its name implies) in charge of the rendering of Model
components, unlike other drawables, therefore valid Model
components can only be created via the MeshRenderer
factory functions MeshRenderer::createModel()
.
The Mesh
class stores the vertex data used by a model. A single Mesh
can be shared between multiple Model
components, and even have different materials assigned. As such Mesh
objects should be resource managed, for which there is a resource management class. For convenience the MeshRenderer
manages its own MeshResource
instance, with which a Mesh
can be registered along with a unique ID. Mesh data is created or loaded by ModelBuilder
objects, of which xygine contains implementations for creating quads, cubes, spheres or loading IQM format model files. The ModelBuilder
interface is designed to be inherited so that the user can implement their own loaders for specific model data types should they need. To load an IQM model for instance:
xy::IQMBuilder ib("path/to/mesh.iqm");
meshRenderer.load(Models::MyMeshID, ib);
this is only required once for each Mesh / ID combination, as the loaded model is now resource managed by the MeshRenderer
. To create a Model
component:
auto modelComponent = meshRenderer.createModel(MyMeshID, messageBus);
auto entity = xy::Entity::create(messageBus);
entity->addComponent(modelComponent)
scene.addEntity(entity, xy::Scene::Layer::FrontMiddle);
Creating a Model
component this way has the advantage of auto-assigning a default material, managed by the MeshRenderer
, so that components can be used quickly and easily when drafting code.
Materials define how a model is drawn. A single Material
may be assigned to multiple models, and multiple materials may be assigned to one or more sub-meshes within a Model
. Each Material makes use of single shader program, resource managed in the same way as all shaders in xygine, which defines how the material is drawn. xygine uses a deferred rendering system and contains a set of shaders designed specifically to work with it. While user shaders can be created they must match the uniform and attribute interface that xygine uses. xygine's shaders cover smooth, coloured, textured and bump mapped rendering, as well as vertex painted meshes. All the shaders also have a vertex skinned variant, used by animated models. These can be combined as necessary using sets of defines to declare which features are needed in a particular shader. To load a shader for a textured model with bump mapping:
m_shaderResource.preload(ShaderId::TexturedBumped, DEFERRED_TEXTURED_BUMPED_SKINNED_VERTEX, DEFERRED_TEXTURED_BUMPED_FRAGMENT);
A full list of shader definitions is available in xygine/mesh/shaders/DeferredRenderer.hpp
. Once a shader is loaded into the resource manager it is ready to be used with the MaterialResource
class to create materials.
The MaterialResource
class, as its name implies, is used to manage Material
instances. Much like any other resource, materials are loaded in the resource manager, providing a unique ID and a shader to be used to render it. Materials can then be retrieved, properties modified, then applied to any models which require them.
auto& catMat = m_materialResource.add(CatMatID, m_shaderResource.get(ShaderID::TexturedBumped));
catMat.addUniformBuffer(meshRenderer.getMatrixUniforms());
catMat.addProperty({ "u_diffuseMap", textureResource.get("images/cat_diffuse.png")} );
catMat.addProprety({ "u_normalMap", textureResource.get("images/cat_bump.png")} );
catMat.addProperty({ "u_maskMap", textureResource.get("images/cat_mask.png")} );
When a new Material
is added to the resource manager a reference to it is returned so that initial setup can be performed. All materials need a reference to the uniform buffer containing the matrices used by the mesh renderer before they will draw. If a model does not appear to be drawn it is always worth checking that this property is set. Additional properties are optional, and do not exist in all materials, depending on the shader used to render them. In this case the bump mapped shader requires a diffuse map, a normal map and an optional mask map. Mask maps contain information used by certain shaders to define how a material looks.
- The red channel controls the specular intensity. The higher the value the glossier the specular highlights will appear
- The green channel controls the amount of specular highlighting rendered. The higher the value the brighter the specular highlight.
- The blue channel controls self-illumination. This effect is done by simply not shading the fragment colour, making it appear lit even in dark scenes. It is also used by the
MeshRenderer
to create an optional 'glow' effect on illuminated fragments, which can be enabled or disabled withMeshRenderer::enableGlowPass()
- The alpha channel controls how much of the active reflection map is blended with the fragment. Higher values give a more reflective appearance.
Some models use UV coordinates greater that 1 or less than 0, causing the model to render incorrectly (usually black). To fix this make sure the texture used by the material has setRepeat()
set to true.
Materials need only be configured once. Whenever a reference to a material is returned from the resource manager the properties remain unchanged and the material is ready to be applied directly to the Model
. To set a model's material use either
model->setBaseMaterial(materialResource.get(MouseMatID));
which will apply the material to the first Mesh within the model and, optionally, all sub-meshes. Alternatively
model->setSubMaterial(materialResource.get(MouseHeadMatID), headMeshIndex);
will apply the material to a specific sub-mesh.
The MeshRenderer
simulates up to 8 active point lights (more may exist in the scene) and a single directional light. Point lights are created via the PointLight
component and attached to entities within the scene. The directional light can be configured via xy::Scene::getSkyLight()
. Lights are all active by default, when they have an intensity value greater than zero. To cast shadows, however, point lights must have enableShadowCasting()
explicitly called on them. Materials which cast shadows also require a specific pass to be enabled on them. First a shadow rendering shader must be created, using the skinned variant for animated models:
shaderResource.preload(ShaderID::ShadowSkinned, SHADOW_VERTEX_SKINNED, xy::Shader::Mesh::ShadowFragment);
Then this shader can be used to create a shadow map pass on existing materials
auto& batMat = materialResource.get(BatMatID);
batMat.addRenderPass(xy::RenderPass::ID::ShadowMap, shaderResource.get(ShaderID::ShadowSkinned));
batMat.getRenderPass(xy::RenderPass::ID::ShadowMap)->setCullFace(xy::CullFace::Front);
The last parameter to set the cull face direction is optional. It usually gives better results to use a model's rear-facing triangles to cast shadows, however some models may have rear facing triangles omitted completely (it makes sense from a modelling point of view, as we're overlaying models on a 2D world there may be no point creating triangles which will never be seen) in which case the cull face direction can be left as default.
####Drawing 3D components
Drawing the 3D models can be done in multiple ways. The most efficient is to draw the MeshRenderer
directly. The MeshRenderer
contains its own internal view which must be set at least once to match the current scene, usually that of the default context when using the StateStack.
meshRenderer.setView(getContext.defaultView);
This usually only needs to be done once, but you may wish to handle the WindowResized message to update the view, should the window be resized to a different aspect ratio at runtime. Once this is done the scene can be drawn directly on the output buffer after the scene
...
renderTarget.draw(scene);
renderTarget.draw(meshRenderer);
...
Drawing 3D components this way is most performant, as they are rendered directly to the output at the correct resolution. The one major drawback of this method, however, is that 3D components will always appear on to of the 2D scene, and cannot be used on a layer elsewhere. This is usually fine, but in some cases it may be preferable to use the MeshDrawable
component.
The MeshDrawable
component can only be created with MeshRenderer::createDrawable()
. This function returns a component containing a buffer of the 3D scene, which can be attached to an Entity
in the 2D scene, as any other component. This way the buffer may be added to a specific layer, and 2D objects may be drawn either in front or behind it. When using the MeshDrawable
component it is no longer necessary to set the MeshRenderer
view explicitly but, unfortunately, incurs a performance hit, as 3D components are buffered via an intermediate texture. While this method also allows post processes to be applied easily to a combined 2D/3D scene, using the multiple view post process method (as opposed to adding post processes directly to the scene) allows post process efects to be used with the MeshRenderer
when drawing 3D components using the first method.
Some modelling packages such as Blender use a z-up coordinate system, which differs from the y-up coordinate system used by OpenGL (and therefore xygine). Models loaded with the IQM ModelBuilder (or a custom ModelBuilder) may appear incorrectly oriented within the scene, or much much smaller as xygine units equate approximately to pixels. The Model component has its own set of transforms which can be applied once to correct this.
auto model = meshRenderer.createModel(MyModelID, messageBus);
model->setBaseMaterial(materialResouce.get(MyMaterialID));
model->rotate(xy::Model::Axis::X, 180.f);
model->setScale({20.f, 20.f, 20.f}); //remember if you can't see your model it might be too small to see!
auto entity = xy::Entity::create(messageBus);
entity->addComponent(model);
entity->setWorldPosition(spawnPosition);
scene.addEntity(entity, xy::Scene::Layer::FrontFront);
The translation, rotation and scale properties can also be used to create rudimentary animations of models, such as players turning left and right or planets rotating in space. If a model appears not to be drawn make sure it is scaled large enough to be seen. Models and materials can be quickly previewed, scaled and manipulated with the material editor found in the xyTools project supplied as part of xygine.