How can I address aliasing on UI when working in VR?

I’m working in VR for Quest2 and later and we’ve been having aliasing issues on pretty much all of our UI for some time now. Being VR, our UI is displayed in world space so the resulting images are guaranteed to be at an imperfect and unpredictable resolution relative to their import. The UI itself doesn’t move, but the user’s head will always move some amount.

My image sprites are all simple greyscale shapes and lines.

I’m looking for options to address aliasing, preferably globally, for UI in this context. I’ll try explaining my efforts and barriers. I’d be happy just to gather a list of options to continue testing within my capabilities.


  • Rendering Path = Forward Rendering
  • Quality Settings - I have a single Quality level toggled for both PC and Android
  • Quality Settings - Global Mipmap Limit = 0 : Full Resolution
  • Quality Settings - Anisotropic Textures = Forced On
  • URP Asset - Anti Aliasing (MSAA) = 4x
  • URP Asset - Render Scale = 1
  • URP Asset - HDR + Depth Texture = off
  • Player Settings - Graphics API = OpenGL ES3

Ineffective Editor Testing
One major obstacle to my testing is that the aliasing level I see when running VR in editor is not the same as what I see when running in a build. Since the output resolution is the same, I assume there’s a difference in graphics APIs between the two scenarios that either affects the mipmap level selection or changes the feature set available between them. I wouldn’t mind understanding why they’re different and whether I can synchronize them better. Building is tedious and my eyes can’t compare artifacts on a moving screen twenty minutes apart, especially after squinting under the headset during development all day. I’ve “fixed” the aliasing in editor, only to find it very present in build.


Import Size
I originally tried to solve the problem by setting my sprites’ import scales and locking disabling mipmapping. By choosing something close to their target display size, I could kind of reduce aliasing on them, but it’s overly hard coded and doesn’t always help.


Mipmaps
Enabling mipmaps unfortunately causes aggressive blurring at the size icons are typically used. The selection process for mipmap levels is a black box (handled by the GPU from what I understand), and there doesn’t appear to be anything like a global LOD bias in either the Quality Settings or URP Asset. Blurring is just as much an artifact as aliasing, and almost all of my icons become impractically blurry.


Mipmap Biases
I’ve stumbled my way through a dozen pages on mipmap biases, mostly populated by blogus’s explanations [1] [2]. Those are frequently posted as a way to choose between blurry and aliased artifacting, and with the help of an old post I was able to make an editor tool that shifts the mipmap bias up or down, as well as a script that adjusts the cell size of a grid layout so I can preview different icon sizes during runtime.

Unfortunately I neglected to spot the exception listed for mipmap biases on Unity’s documentation page which says mipmap biases don’t work on OpenGL ES. That happens to be what we’re using. After carefully curating my icons to an acceptable balance of blur and alias, I loaded them up in a build to find all the mipmaps were blurry again.

I also found no global setting for mipmap bias which is generally what I was looking for. Most icons will behave the same if I prepare them correctly, so adjusting them one at a time using a hidden property gets tedious.


Anisotropic Filtering
My use case is for panels that are typically facing the camera almost straight on. Their scaling is close to symmetrical across the axes, so this doesn’t apply as far as I know.


Canvas Scaler
Does nothing whether I set it to 1 or 1000. Probably not intended to work with World Space. The docs claim it does, then

Canvas Scale x100, x1000
I tried increasing the size of the canvas but that inevitably results in having to apply an inverse scale to its elements. I work at a canvas scale of 0.1 to get natural canvas sizes (in the hundreds), and because layouts give padding in integers which is unusable if I set the canvas scale to 1 since the padding then needs to drop well below 1. I tested a set of canvases at different scales with complicated icons on them and can’t see any difference in aliasing.

Scaling Text to Increase Font Size
I assume this must have been a fix for the older Text component because it has no affect on my TextMeshPro components. Scaling the text’s rect up or down by x1000, then changing the font size by the inverse did nothing. My text is mostly fine, but is blurrier than I’d like compared with what Meta pulls off in their menus. Scaling anything other than 1 also ruins the way rects interact with layouts which makes this a definite dead end for me.


Sprite Line Weights
Outside of adjusting settings, I can do some amount of cleanup on the sprites themselves by using consistent line weight and coverage of their available resolution. This doesn’t solve my aliasing issues as a whole, but it helps ensure all of my UI fails in the same ways.


Again, looking for options to control aliasing in VR. The Quest’s native UI is relatively crisp and suffers no aliasing issues that I can see, so it’s definitely possible. My text is somewhat blurry as well, but what I need most is to clean up my UI sprites.

Have you tried using a custom shader for your UI (in order to bypass the OpenGL ES mipmapbias limitation), like this:

I think we ended up using a similar solution to what’s they’re talking about in that post. If I recall we applied it via a script alongside the Image component, giving us a bypass for the mipmapbias. Since I had prefabbed out a lot of our UI, I was able to relatively quickly apply that. The result is still very micro-managed, and is lacks the crispness I get in the Quest menus, but it unblocks that feature and lets me correct for some of the worst offenders in my icon lineup.

I also did a fair amount of work cleaning up the line weights and image densities of some of the icons themselves, though those issues were always obvious and not really the aliasing I had been struggling with.

Here’s a screenshot from before I cleaned up the icons to show how I was testing it. The slider dynamically changed the size of all the icons so I could test faster in builds. I had the icons displayed as toggles, so I could click the problematic ones to highlight them and take a screenshot when I was done in the build. I’d bring that over to my desktop and use it as reference to make corrections.