Unity Terrain performances on Oculus Quest 2

You know, reading this thread, if texture fetches of the normal array are pushing the quest beyond it’s memory bandwidth limits, I could try generating the normal from the height map, which is already packed in with the diffuse. It’s hard to say if the ALU cost of doing this would be worse than the memory access or not, but it should look pretty decent and not require any normal maps…

So I did some experiments with this; basically I use the screen space derivatives and the height sample to generate a normal map. This is pretty cheap, and on Unity terrains I can do some shortcuts to swizzle the resulting normal into the correct space without doing any matrix multiplies. Results:

Normal Mapping disabled:

Using normal maps:

Using auto-generated normals:

With good height map data, it looks surprisingly good. I also generate an AO map from the normal as well, since it’s just a multiply. I suspect this will be drastically faster than looking up normal map textures on platforms like the quest, which seems to be particularly memory bound.

Note there is an issue with this technique, which is that the derivatives don’t produce the best normals up close- they tend to look “stair step” like:

This tends to be noticeable when you get close to the textures, or when they have subtle gradient content (like this moss texture does).

If you get extremely close, in some situations you can see the pixel grid breaking down and moire patterns appearing:

These could be fixed, but it would require sampling the height maps multiple times and doing a sobel filter, but that would defeat the reason for doing this in the first place by driving the sample count higher. As a cheap, no-texture sample technique, I think this works surprisingly well and is applicable for many games.

jbooth, release it as an addon! I can guinea pig this!

Nah, made it as part of core…

Uhhh, what would be the best way for a peasant such as myself to enable this uhhh properly

Wait till my next patch is released.

This was released this morning.

Tested it out with URP. Everything is magenta now. I suppose the autonormal feature needs to be adapted for URP as well?

I wouldn’t expect it to need that, but I can check…

Nope, works fine in URP for me.

Strangely enough after installing the updated microsplat, textures do not appear at all in android builds, and the height normals does not work in urp (with the oculus quest anyways)

The most recent microsplat has the following error (2019.4.14f, URP, clean project):

ArgumentException: Could not find MaterialProperty: '_AutoNormalHeightScale', Num properties: 9
UnityEditor.ShaderGUI.FindProperty (System.String propertyName, UnityEditor.MaterialProperty[] properties, System.Boolean propertyIsMandatory) (at <68af50843d0a45398c92799647332ec2>:0)
UnityEditor.ShaderGUI.FindProperty (System.String propertyName, UnityEditor.MaterialProperty[] properties) (at <68af50843d0a45398c92799647332ec2>:0)
MicroSplatShaderGUI.FindProp (System.String name, UnityEditor.MaterialProperty[] props) (at Assets/MicroSplat/Core/Scripts/Editor/MicroSplatShaderGUI.cs:20)

More importantly, textures no longer appear at all in my Android builds (although they do appear in the editor). I am going to have to revert to an earlier microsplat unfortunately

Works fine for me, in URP, HDRP, and Standard… Did you try toggling an option on the material’s features to regenerate the shader? Or is this after setting it to auto-normals?

Ok it seems reinstalling microsplat and urp in order seemed to have rescued the android builds issue. The magenta autonormals is triggering a different error (even after exiting bulk shader edit mode)

Shader error in 'Hidden/MicroSplat/MicroSplat_Base909050615': cannot map expression to ps_4_0 instruction set at line 1657 (on d3d11)

Leading to this code

    float3 HeightToNormal(float height, float3 worldPos)
      {
         float3 dx = ddx_fine(worldPos);
         float3 dy = ddy_fine(worldPos);
         float3 crossX = cross(float3(0,1,0), dx);
         float3 crossY = cross(float3(0,1,0), dy);
         float3 d = abs(dot(crossY, dx));
         float3 n = ((((height + ddx(height)) - height) * crossY) + (((height + ddy(height)) - height) * crossX)) * sign(d);
         n.y *= -1.0;
         return normalize((d * float3(0,1,0)) - n).xzy;
      }

I’m using URP 7.5.2 if that helps…

try changing that from ddx_fine to ddx and ddy_fine to ddy

changing them to ddx and ddy respectively gets rid of the magenta effect, although the autonormals do not seem to work (I don’t see any normal mapping after making this change).

Recompiling the shader will undo any manual changes to HeightToNormal it seems.

Yeah, you’d have the change it in the source fragment files in core/scripts/editor/fragments/microsplat_terrain_shared.txt

There’s a strength setting on the effect on the material, you likely just need to turn it up…

Yep that did the trick! It works now!

Edit: Slight issue. I noticed the effect is very muted if I set the uv scale of the texture properties to less than 1. Is this intentional? I tend to scale the uvs down for artistic purposes.

This is a total hack technique, so it has some funny things about it - since it’s based off derivatives, the strength of the effect is related to the texture scale, so you have to increase the strength to counter balance that.

Ok, it may be necessary to create my own large scale textures to properly take advantage of this new technique!