iOS - is the subtract, divide or distance shader math broken?

hey folks!

I created a little material function to add a spherical falloff to my materials, but for some reason the result is completely off on iOS builds.

I’m using the SphericalMask node (i also tried with my own function that does the same thing, but also did not work). This is what my material function looks like -

On PC in all the preview modes I’ve tried it works great. Here’s what it should look like (noticed the cloudy overlay on the floor’s material) -


When I send this to iOS though, the falloff is completely broken and covers the whole world when the value for the Radius input goes over ~260. Here’s what I get -


With values between zero and ~250 the look is the same on both. Anything higher on iOS quickly covers the entire world. So between ~260 and ~280 the gradient goes from what you see in the first result (working) to what you see in the second result (not working).

On all other builds and previews I’ve tried I can increase the Radius value to as high as I want and it will slowly cover the entire map as intended.

This is how I’m using the material function btw -

It really feels something is going off with the division in the SphericalMask node. Has anyone else run into a similar issue? Any help would be great.


would it be possible to change change this question to a bug? I’ve tried so many different solutions, but it really seems to be an issue with the distance node in the materials not functioning properly on iOS.

It’s hard to say without looking at your entire material. But keep in mind that on most mobile devices/chipsets, you have less floating point depth than on desktop. So for example, on desktop, you will typically have 32 bits of floating point depth when doing shader math. On mobile, you typically have 16 bits, which is a lot less. If you have a large chain of math operations, especially ones where you subtract two large numbers and end up with a small number, you will run out of floating point precision and underflow or become unexpectedly quantized, which will give you unexpected results.

I’m guessing this is what is happening to you here. You can try restructuring your material to avoid precision loss, or use a switch to select a simplified version on mobile.

Most of what you see there in the first image is the whole material function. the only part that’s missing is the texture information with some simple UV modifier… Writing that down, I just realized that my UV are on the pixel shader so I’ll have to fix that and output to a customizedUV. But I doubt that’s the issue.

As for the precision issue, that was my thought also, but I’m not convinced. The value you see there, where the bug starts, is very small. Less that 300 pixels. IF the precision is that small then there’s definitely a problem with the renderer. The SNES has higher precision than that. :slight_smile:

Also - I’ve done similar effects in unity without any issue, this is why I really feel like this is an error in the distance math.

SphereMask is a nontrivial function involving several operations. You might want to try omitting it for something more simple to see if you see different results in your material.

I’d also like to point out that you the SNES thing is not a real comparison – the SNES isn’t doing multiple divisions, subtractions and multiplications across several coordinate spaces for a rasterized fragment. I realize you are not being serious, but it’s important for me to point this out because you don’t understand where the precision loss is coming from. It’s coming from the math operations, not the numbers themselves.

Thanks dude. It was defintely a joke. I know exactly what precision loss is. I’ve been making AAA games for 15 years. But that’s fine, I’ll save the jokes for another place.

If the SphereMask node is made the way I imagine it is, it’s should already be incredibly simple. result = clamp (abs (distance(v1,v2)) - offset / falloff). That’s why I’m really curious what’s going on in that Distance operation. But judging from the result, if feels like there’s a very high exponent somewhere in there because the result is not linear.

I changed to this node after trying roughly four different formulas, all with the exact same result, and all with one thing in common - the distance node. The only thing I have not tried yet was calculate the distance manually. So yes, you may be right about precision loss, but I suspect it’s coming from the distance node itself.

And as I wrote, the bigger concern for me is that the ES2 preview does not match the iOS result. I plan on updating the project to 4.11.1 preview tonight to see if that makes a difference because there’s a lot of new Metal integration that may change a lot for previewing.

Epic has written in many places in the Unreal docs that the as long as the nodes are supported the results should be the same in the editor and in a build or at least very close. I 100% agree with this statement but in my case it’s not true, so this post is to bring up the potential bug. If it is a case of precision loss then Unreal should fake the same result in the editor when using the appropriate preview.

Thanks for the replies though, they’ve made me think of a few other things to try out.

Yeah, it’s not always the same. And usually the result comes from the different floating point results.

First of all, ‘absolute world position’ is already a big loss if you’re in low-bit-depth, because it’s expressed in world coordinates. You end up doing stuff like, world → fragment → world → some math → fragment → some math → write to buffer.

If you want to see what SphereMask is doing, you can look at MaterialExpressions.cpp, line 8269. There are several multiplications, divisions, and subtractions, because it has a hardness/softness thing going on. It’s more complicated than the expression you described.

If you’re wondering why distance is causing you a problem, it’s because you have to take the dot product and then the square root, which is a precision loss, especially if you’re in world space coordinates (depending on the coordinates scale of your world).

You can try using distance-squared instead of distance in your case, which will save you both performance and precision loss.

The results for the materials will be accurate if you are doing typical material things – blending masks, adjusting UV coordinates, etc. typically in UV, fragment or tangent space. But if you start doing fancy stuff, the onus is on the programmer to understand what will happen at a lower level on different devices.

very interesting. Strange that they’re using all those mults and divs for the falloff hardness. The div in my formula example does exactly that. But I’ll try out your solution with the distance-squared.

I have another solution to try, but I would rather avoid that idea for now because it requires a depth test and will cost one overdraw. I’m trying as much as possible to have it built into the main material so it’s a fixed, known cost.

I’m going to try replacing that WorldPos node first. I was suspicious of that as well, because there’s no way that I can find of setting the max world size for it. I maybe be able to replace that by feeding the scale of a dummy sphere that’s not rendered and just calculating the distance by using the radius. Not sure what will happen on pixel level, but we’ll see.

Thanks for the ideas, I’ll give this stuff a go and write back here with results.

I’m at a loss. I don’t know know what else to do. There’s clearly a precision problem coming from somewhere, I just don’t know where exactly.

I tried replacing all the nodes I suspected to be an issue and I just can’t get this to work. What I’m trying to do here is so basic, I refuse to believe that this a limitation of iOS or mobile in general. I’m sure there’s a problem with how the shaders are being built for iOS.

What did you replace it with?

basically anywhere I could avoid using AWP node I would removed and tried various other attempts. Nothing worked though because I really need AWP for this. Next I’m going to try and feed that into the custom UVs to see what happens.

looks like there was indeed a bug that’s been logged - UE-25532.

Also having issues with AWP node on iOS only, in case it’s related: