Material Expression Add Issue

Hi everyone,

I’ve been playing with materials, shaders, render targets and what-could-be-done, and I’m facing something I can’t quite revolve my head around. To reduce the scope of what’s happening with my project I extraced the culprit, to no avail as the issue was still there. So I’m turning to you to find some help.
So as a minimal issue, here’s the deal:

I have two render targets, both RGBA16f, that’s my two render buffers. Call them Current and Temp.
I then have two materials, one is adding a constant float parameter to the red channel of the input texture, the other one does a simple copy. Both use the Unlit shading model and send data to emissive, with “allow negative emissive values” checked.
This is because both materials are then used by an actor in my world, which is responsible for calling boths shaders to render on the two render targets. As you would imagine, these are interleaved, so on each tick we :

  • Take Temp as input, add X to Temp.r, store this value in Current
  • Take Current as input and copy to Temp
  • reiterate…

So effectively, with initially black render targets, I get increasing red value in both.
This works for a moment, but the issue begins to show after some time depending on the amount I choose to add on each update.
eg: If I set X (the amount to add each update) to 1.0f; my red channel stops at 2048 and never gets past it. As it is fully dynamic I can increase X to 2 while simulating, red will slowly rise to 4096 and stop there. So there’s at least one hint, it seems to be linear.

But why on earth would it stop at such values ? That’s a simple add, and clearly we are not overflowing (maxValue = 65535).

Here’s the file if you want to test by yourself, or have a better look :

Just open the attached map, and you can run the simulation and change the added amount on BP_ShaderCycle actor. It will show as a sphere, vertically displaced by the amount of red read in our rendered texture. There is also a parameter on the MaterialInstance associated with said mesh, HeightThreshold, which is useful to manually read the computed red value, turning the sphere from red to green when it goes past it.
By default there, with Amount set to 0.5f, the red sphere should rise up to 1024 units and stop (and turn green with the threshold set).

What am I missing guys ? I hope you can help !

Okay so I dismissed precision issues too quick. Digging deeper into float16 representation, it’s clearly the encoding itself which can’t handle increments < 1 for numbers >= 1024.0, as well as increments < 2 for numbers >= 2048.0 and so on.

In the end really this file is a perfect representation of floating point precision issues.

So this is only partially solved: For this specific test case I could go to float32 representation and wouldn’t hit any walls until much higher values alright. But what I’m really doing aside this minimal excerpt test-case would require larger render targets, a little more than two of these, and clearly going from 16 to 32bits is no small increase in memory footprint… For the sake of trying I did so though, but with the real-case values it appears I run into these precision issues as well even with full precision.

Now that I know where (at least part of) this is coming from, I need to test more things to see if I can come up with something. If I’m not mistaken, this issue arise from the matissa-exponent relationship, so even normalizing everything would not help, anyone can confirm ?

Correct. Integer part can be accurately represented in range -2048 to 2048. 2048 to 4096 is rounded to even number and so on. In range between 32768 and 65519 you can only hit each 32nd number.

Reconsider, if you mechanics can fit into 0 to 1 range instead.

Thank you. This is what I’m currently trying yes, though I’m dealing with two very different scales which are combined and compared to produce output. I’ll see if I can treat these two branches optimally regarding their respective “domain filling” (I’m not sure how it’s called).

I’ll report here as I make it through. Hopefully in the end I’ll be able to show what it’s all about, a (prototype…) script for water dynamics on a landscape on the gpu, which could also be used as an erosion system (though not at runtime for landscapes, it could be for another custom representation). So this should get brigther !
Again thank you.