Avatar Optimization
Optimizing your avatar or world or 3D asset is a massively important part of the avatar creation process. In general, increasing the render efficiency of your avatar means that it has less impact on your, and other people's framerates.
VRChat has performance ratings, but these are only rough guides to actual optimisation. They're not that bad when you actually understand optimization, but this article will focus on one thing: Getting the time it takes for your avatar to render down. It's possible to make an 'Excellent' avatar that is godawful, or a Very Poor avatar that's extremely performant.
The Process
Actually working on optimizing your avatar will be a balancing act between how many shits you give, what kind of environment you expect to use the avatar in, and how many features you want.
It's recommended to focus your efforts on making appropriate avatars, rather than min-maxing optimisation every time. For example, an avatar you're going to use in a crowded instance you will definitely want to optimise as much as you can, else you'll be performance blocked and no-one will see your cute avatar. For instances where it's just a few people... or just two people.... then you can basically not optimise at all and be fine. Plus, some optimisaiton is harder than others. For most avatars doing the quick wins can get you a lot of extra frames for little effort.
Optimisation covers both blender and unity since the final asset is built in both. Some things are kind of blended between editors.
Focus areas:
- Number of distinct mesh objects
- Vertex Count (number of polygons)
- Texture Size
- Material Quantity
- Shader settings
- Use of Blendshapes on high poly-count meshes
- Number of bones
- Physbones (previously dynamic bones) count
- Number of animator controller layers
- Other crap added (particles, Lights, etc)
The main render/resources to be focused on are:
- VRAM / RAM usage
- GPU Frametime / Framerate (How long the avatar actually takes your GPU to render)
- CPU Frametime (How long the CPU takes to go through all the avatar's logic and drawcalls and shit)
- Download size
Unity Rendering tidbits
Understanding how unity renders things is important. Details may be in the focus areas but in general:
- Inactive game objects contribute to file size and VRAM/RAM usage, but if they are deactivated they are not rendering and do not effect frame times.
- Unity treats the VRAM and RAM as a sort of hybrid cache; the takeaway is that assets from inactive game objects get moved to RAM after a while, but pulling shit from RAM to VRAM is still slower than pulling from VRAM to present something onto the screen. Larger the inactive game objects, the larger the lag spike when the game object is toggled on.
- Textures are uncompressed in VRAM. Texture size is the single largest overriding factor in VRAM usage.
Focus Areas
These should be roughly sorted by ease of optimisation. Removal tends to be the most efficient optimisation for any part of an avatar.
Other Crap added (Particles, Lights, etc)
Reduce these as much as possible, basically. Easy to optimise since you have to go out of your way to add these things.
For the Dynamic Penetration System, this uses lights to coordinate mesh bending.
If they're turned off they won't do anything though. Optimise by leaving them OFF by default, and try not to leave them on accidentally.
- Remove from optimised avatars
- Leave off when not in use
Shader settings
Generally: don't use effects you don't need, and try and use the same shader as much as possible. Shaders are basically code that runs on your GPU and the fancier the effect the longer it takes. GPUs are fast at running them so you honestly don't need to worry about performant shaders much at all.
Don't be scared from using shader effects and material maps and caps and such, it has a very minor impact.
- Avoid using heavy shaders like ones with transparent grab passes
- Performance: Opaque > cutout > transparent
- Leave culling on if you can. Nobody actually needs to see your nipples from the inside.
- Using maps, cutouts, speculars, etc etc all have a minor performance draw
- funny poiyomi effects also have a small impact
Number of bones
More bones = more shit that the game has to calculate when you move around. To optimise, reduce the amount of superfluous bones.
Minimal effort: Delete bones you aren't using. For example you deleted a skirt mesh, get rid of the bones too.
Low effort: Use CATS to merge the bones. Merging them with CATS will take the weights from one bone and merge them into the other, and delete one of the bones.
Find Model Options > Merge Weights.
Select the bone you want to merge and remove; then use ctrl-click to select another bone. Then hit Merge Weights - To Active. Or select one bone and hit 'To Parents' to merge it to the parent. This is most useful for reducing the amount of bones in a chain; some hair has a million bones and even with physbones it's overkill.
- Reduce Bone count by deletion or merging
Number of Animator Controller Layers
The number of layers in your animator does actually matter when it comes to performance. Generally don't worry at all about it under 20 layers; but if you start to make loads of layers you can save some performance by redu cing them down. additionally, weight 0 layers take up CPU time and that's a unity bug.
- Try not to do more than 20 layers
- Zero weight layers still count
Use of Blendshapes on high-poly meshes
Shapekeys work by recording the difference on every single vertex between one state and the other and interpolating between them for the partial shapekey activation effects. This actually takes additional processing power and gets more difficult the more vertexes there are.
Number of Distinct Mesh Objects
Every seperate mesh requires a drawcall to activate. This is a set of instructions that lags the GPU until all the resources required to render the mesh are loaded and calculated. This happens every frame. Uncombined meshes basically waste GPU time on absolutely nothing and there is usually no reason to have them seperate.
Join meshes in Blender. Use ctrl-j to join your meshes. One except: High poly areas with shapekeys, like the face. Do not join the face to everything else.
Before joining everything like a madman, there are several reasons you'd actually want seperate meshes:
- To have different outfits/props.
- to save performance on shapekeys.
- To reduce the amount of polygons rendering at once.
Actual optimisation is going to be a balancing act between enough meshes to get the functionality you want, without being too laggy. However; when making a maximal optimised avatar you should have only two meshes; the face, and everything else.
Note that only active meshes actually take a drawcall. You could have 10 meshes, but if only face, clothing, body are the three meshes all at once, this will actually perform the same as a 3 mesh avatar. The VRC performance stats can't tell the difference between 10 all at once and just 3.
- Minimise meshes that are rendering at the same time
- Join meshes
- For unjoined meshes, do not have many active at once.
Vertex Count / Poly Count
Fairly intuitive, more polygons the more rendering that needs to be done.
Polygons are also often referred to as verts, so a vertex count can be thought of as an analogous measure, as polygons themselves can be tris, quads, or beyond.
The general rule is to reduce the amount of polygons to a sensible limit. Like all other optimization efforts, there is a balancing act between effort, performance and fidelity. A model with more polygons will usually have more detail, although detail can be added with effects such as normal maps, mat caps and other shader rendering effects. Also keep in mind that shapekeys are per separate mesh object, and the amount of vertexes affects how much computation is needed to process a shapekey when active.
Reducing vertex count can be done in a number of ways, some more time intensive or destructive than others.
Mesh Deletion
The simplest and most effective means of vertex reduction is simply to delete mesh you don't need. This can be excess ribbons, adornments or other features, or deleting mesh that isnt even visible, such as skin under clothing, or layered outfits.
To delete mesh that is part of a mesh object, [>>todo: explain the difference between a mesh object and linked vertexes] select the certexes you want to remove in edit mode and press delete. To help you select mesh the following can be used:
- Circle select - c
- Box select - b (middle mouse button deselects from current selection)
- shift + clicking individual verts
- select linked - l and ctrl-l
- Different views: wireframe, xray mode
- h and alt-h to hide current selection to preview your deletion
- knife tools- to cut new vertexes into the mesh for finer deletion
- separate into new mesh object - p
You can also delete entire mesh objects, removing them from the model.
When deleting your mesh it is worth also checking the armature to see if any bones were for mesh you just deleted and removing them too as they are now useless.
Also important is that deletion is a destructive process and you should save iterative copies (ctrl-shift-s) so any mistakes can be easily fixed down the line. Trust me on this one.
Decimation
Decimation is another destructive process that forcefully reduces vertex count by automatically merging polygons together. Most useful on suspiciously high polygon clothes, attachments and hair.
To decimate, first separate out the mesh you want to have the polygon count reduced on by selecting it and pressing p.
Then in object mode with the mesh selected, go to the modifiers tab (blue spanner) and find the decimate modifier. Use the modifier to reduce the polygon count. if you get a non zero shapekey error, open the dropdown on the mesh object and delete shapekey data. This will delete all the shapekeys on that mesh.
Decimation is a pretty brutal process but it can be very effective, but keep in mind the following guidance:
- Don't decimate the face. it has so many goddamn shapekeys and is what most people will be looking at the most
- Hands, fingers and other joints get super fugged by decimation so avoid those areas.
- It is often a good policy to not decimate the base skin mesh at all. the topology on that is often efficient already, and you'll likely cause yourself some problems.
Separating Mesh Objects
This is for when you have multiple meshes, such as outfits, but you don't need them on at the same time, such as two different hats, even two entire different outfits.
Why this is optimisation and not just a weird opposite of the earlier message to join meshes is your priority with a unity avatar is to reduce the amount of meshes rendering at the same time. Any deactivated game object is not taking up draw calls or other resources, so to have multiple outfits, the optimal play is to have only one rendering at once, one mesh, one outfit; for a total of 3 meshes rendering: face, body, outfit. And with many outfits on the same avatar that can be switched to but only using 3 draw calls at once.
Of course the most optimised is to have just one outfit, merge the body and outfit, delete unwanted mesh [>>todo: example use cases] and not change outfit. But to change outfit you would need to change avatar, and that would make everyone else in the instance reload the entire avatar just to have you put a hat on. The balance between fun and performance should always be a factor in how hard you optimise.
Anyway, the blender part of separating meshes is pretty simple:
- Select the mesh you want separate
- Press p to make it into a separate mesh object
- Give it a name
- Make a toggle for it in unity sometime later
Some people will make outfit toggles that are shapekeys which hide the alternate outfit inside their body or something. These people are indeed wrong, but they are saving a draw call or two. However, they are also adding two more performance hogs: the hidden mesh is still rendering and will take frames even though it is hidden inside the avatars. The shapekeys are still requiring calculation on the higher poly mesh of both outfits at once. This usually outweighs the benefits of saving a drawcall. I think- if anyone wants to test how many polygons a drawcall is worth that would be good to quantify.
Retopologising
This is an advanced technique that is for when you are making a from scratch model and want to clean the topology. it will murder your texturing, though. best done before texturing.