Welcome to the third best practice for mobile from an artists perspective. We hope you will find everything you are looking for regarding materials and shaders in this guide.
What you will learn;
- The difference between a material and a shader.
- The best practices when dealing with both materials and shaders, along with game engine best practices when dealing with both.
- Transparency best practices from an artists perspective.
- All the other small best practices that fit into the area of dealing with materials and shaders.
The difference between a shader and a material
- A shader is a small program that tell the GPU how to draw an object on the screen and all the calculations that are needed to happen on it.
- A shader needs to be attached to a material to be used.
- Some of the scripting languages commonly used to author shader are HLSL and GLSL.
- A material is something that can be applied to an object/mesh to define the visual look of that object.
- A material is used to set the specific value of parameter that made available from a shader, such as the color, numeric values, etc.
A shader and a material can be implemented differently depending on the game engine. In Unity, different shaders can be created and applied to different materials, while in Unreal engine the shader used can be based on the target platform selected.
Use optimized shaders for mobile
Unity have collections of shaders targeted for mobile platforms, they are conveniently located in the mobile category. Use these shaders whenever possible for mobile game development.
- These shaders contain a lot of feature simplification to enable them to have better performance on a mobile based platform.
- In Unity this means that there will be less features compared with a standard shader, such as no color tinting.
Unreal Engine will use the most relevant mobile shader depending on the target platform selected. Using this mobile shader will result in a different look compared with the default Shader Model 5. We need to change the Preview Rendering Level to better visualize the final look in the device within the editor. Unreal Engine's material on mobile platforms is created using the same process with other platform, so they generally have similar visuals and behaviors.
Only use features that are needed. Unity and Unreal Engine compile the shader based on the material settings, and by removing features that are not needed (such as colour tinting, detail map, etc) we will have an overall less complex shader that can potentially help with performance on mobile platforms.
Use a fewer number of texture sampler in a material
For mobile platform the general rule of thumb is to use as few textures as possible. This is because more textures will likely result in more texture fetches, which means more bandwidth that will most certainly affect battery life. Also, as these textures need to be saved in memory, it will increase application size considerably.
- In Unreal Engine the recommended maximum number of texture sampler for mobile are 5 - https://docs.unrealengine.com/en-US/Platforms/Mobile/Materials/index.html
- It is still advisable to use a smaller number of textures as possible, as using all 5 texture sampler in a material will make a considerably expensive material.
- Using texture packing will help to reduce number of textures. Instead of using individual textures for the roughness/metallic, we can pack them into channels of a single texture.
- We can also use a numerical value instead of a texture for some parameters, such as metallic or roughness/smoothness (do this if possible and observe whether it will affect visual quality), this will further reduce number of textures used.
- In Unreal Engine, this is done by adding constant to the parameter slots, in Unity we need to add a value within the shader.
- Using an unlit shader will also reduce number of texture used as material is not affected by light, therefore will not need roughness or metallic texture.
An unlit shader is cheaper then lit
When creating a shader we can decide how the material will react to light. Most commonly used in mobile games are either lit or unlit shaders.
Unlit - fastest/cheapest shading model. Use this if targeting low end devices.
- Use an unlit shading model as much as possible especially if the game is targeting lower end devices.
- Unlit shading model as the name implied, not affected by lighting and only output emissive as color.
- As it is not affected by light, many calculations are not needed (such as specular, shadow, etc) resulting in cheaper/faster rendering.
- Going with stylized/cartoony art direction work really well with unlit shading and should be considered when making game for mobile platform.
Default lit - then it will need extra processing compared with unlit.
- More processing needed for this compared with unlit, but will enable the surface to be affected by light and have specularity.
- For most games this is probably the shading model that is most used.
A comparison between Lit and Unlit can be seen below, same tower mesh and texture set applied using different shader. Unlit is not affected by lights and in turn needs less computation because of it, this will potentially result in better performance of the game.
Transparency Best Practices
Avoid transparency and use opaque as much as possible
Use an opaque material whenever possible. On mobile platforms, usage of transparency is best avoided unless necessary. Rendering an object with transparency will always be more expensive than opaque, and having lots of these transparent object in game can/will affect performance, especially when these objects are rendered on top of one another many times.
This problem is known commonly as overdraw, when same pixel is drawn multiple times. The more layers of transparency we have, the most expensive the rendering becomes. For mobile platforms, overdraw can severely affect performance and we need to be mindful when building our level and try to keep overdraw to a minimum. Unreal Engine and Unity have a mode to visualize overdraw that can be used to track the severity of overdraw in a given scene.
If we have to use transparency in our game, profile the performance and compare between the different implementations.
There are different ways to do transparency in a shader, most commonly used are alpha test and alpha blend. There are different best practices that suggest one over the other, but the result will always vary from differing use cases, so our best practice is to always profile the performance difference between the two. For the mobile platform, one of the ways to do this is using Streamline, a software/tool developed by Arm to collect performance data of a device.
Alpha test implementation will make the object material look either 100% opaque or 100% transparent. We can also set threshold of the cutout for the mask. In Unreal Engine the term used is Masked blend mode, and Unity call this type of transparency Cutout.
Visually alpha blend allows the material to have range of transparency (and make an object look partially transparent), instead of completely opaque or transparent. Unreal Engine call this blend mode as Translucent, Unity use the term Transparent.
A comparison of alpha blend and alpha test can be seen in the image below. Alpha blend allows partial transparency while alpha test will result in sharp cutout.
- It is best to avoid cutout material (alpha test) unless it's really needed.
- Alpha test means that a material will be either 100% opaque or 100% transparent.
- Alpha test will generally not be recommended in mobile platform as it disables some of the optimization feature within the GPU. However, as always, we need to profile and compare the performance just to be sure.
- For a mobile platform, Unity recommend using alpha blend over alpha test https://docs.unity3d.com/Manual/MobileOptimisation.html. In practice we should always profile and compare performance between alpha test and alpha blend, as they are usually content dependent and need to be measured.
- In general, even alpha blend (all kinds of transparency) should also be avoided on mobile platform.
- If we need to have alpha blend in the game, try to make the coverage of the blend area small.
Alpha test transparency might work better for foliage
In a static view, both example foliage look perfectly fine, with alpha blend actually looking slightly better due to the soft edges compared with sharp cut in alpha test.
However, in motion, alpha blend looks wrong and the leaves doesn't seem to render in the correct order. Alpha test handle the transparency and the leaves order much better, but the edges will be harsher/aliased compared with alpha blend. Most of the time, the alpha test visual quality will be acceptable.
Other Shader and Material Best Practices
- Use the simplest shader possible, such as unlit and avoid using unnecessary features.
- Unity have a shader designed/written specifically for particles that can be a good implementation start.
- Try to minimize overdraw. Reducing the number and/or size of particles might help with this.
Profile and visualize shader complexity
Adding more texture sampler, transparency and other features can make a shader become more complex and affect the rendering.
Use viewmode Shader Complexity to check shader complexity inside the level. This view can give good estimates and an early indication on how expensive our shaders are. Green is good and red is more expensive.
In Unreal Engine 4, use the material stats feature to quickly profile material early in the process.
- View mode above is a good rough guideline for any artist. To have a further look in profiling we can use other tool such as Streamline for Arm Mali, but they do require further graphics knowledge to understand more about how the GPU works.
Do as many operations in vertex shader as possible
The Shader that used in a project is usually a combination of vertex shader and pixel shader. Vertex shader works on every vertices and pixel shader runs on every pixel. Usually there will be more pixels rendered than there are vertices on a screen, which means there will be more computation happening for pixels than vertices. It is recommended to move computation from the pixel shader to the vertex shader whenever possible.
That being said, moving this operation to vertex shader usually means having to move the processed data to fragment (varyings). So, even though this is generally a good idea, we have to pay attention to the tiler in case it then becomes the bottleneck. As usual we need to do further profiling after working on optimization.
A way to do this in Unreal Engine is using the Customized UVs feature, connecting nodes to them and then using a Texture Coordinate node in the pixel shader to target the customized UV. More information on this subject can be found here https://docs.unrealengine.com/en-US/Engine/Rendering/Materials/CustomizedUVs/index.html
Reduce the number of expensive math operation whenever possible.
We use mathematical operations within the shader to customize the look and behavior of it. Some examples of these math operation would be multiplication, addition, power, floor, logarithm etc.
These math operations are not equal in terms of performance cost, therefore we need to pay attention to their usage. Some of the more costly operations are sin, pow, cos, divide, noise. Basic operations such as additions and multiplications are generally faster. Try to keep the number of expensive math operation as low as possible. This number particularly needs to be kept lower on older devices, such as ones with GLES 2.0.
Always do performance profiling
This is more of a general best practice, but never assume anything and always do some profiling to understand where the real bottlenecks are in our app. We should also do further profiling to compare the before and after affect of any optimization.
Have you seen our other guides? Check them out now