Other Shader and Material Best Practices

In a situation where overdraw is unavoidable (such as when using particles) make the shader as simple as possible.

  • Use the simplest shader possible, such as unlit, and avoid using unnecessary features.
  • Unity has a shader designed/written specifically for particles, which 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 samplers, 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 provide an early indication of how expensive the shaders are. Green is good and red is more expensive.
Unreal Shader Complexity  

In Unreal Engine 4, use the material stats feature to quickly profile the materials early in the process.

Unreal Material Stats 
View mode above is a good rough guideline for any artist. To have a further look in profiling, we can use other tools, 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 most common shader used in projects 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 optimizations.

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 operations 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 operations 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 an app. Further profiling is also recommended to compare the before and after effect of any optimization.

Next Steps

Have you seen our other guides? Check them out now