This is the exact same problem that I've been trying to tackle. The problem is that we run out of FP32 precision on the GPU. We could probably squeeze more precision if we start with a larger number on the first LOD level, but then that means the noise frequency will be too dense, or at least for me with the Simplex noise algorithm. So it's not a solution.
There's one method that has been suggested before, which is emulating FP64 on the shader, and it should be adequate to just pass emulated FP64 number to the noise function as an input and keep the rest of the fractal calculations as FP32 in order to get 3-4 more LOD levels without artifacts. I tried this myself with no luck, I probably did something wrong on the way. I will probably try this again soon.
I've also thought of another method. Why is the noise frequency so dense with large numbers? Could we maybe change the noise algorithm so that the noise has less frequency for larger numbers? Because then we could probably squeeze out more precision. Because we're only utilizing part of the FP32 range.
There's an interesting thread on the Outerra forums where cameni is talking about using integer noise function to get more precision. He's sounds surprised why no one is doing this.
I came up with the code listed above only after everybody presented the same direct function for computing the noise and complained about the precision. So I wondered why people aren't using an integer version that can use more bits for computation. However, my version uses 2D coordinates as the input, I can see it would be simpler for people to use 3D there. Well, it should work as you have described.
The code he's talking about:
Suppose you have unsigned integer coordinates ranging from 0 to 2³²-1, used to address the location on the face.
The following code is for 1D, using a 1D pregenerated noise texture, but can be easily extended to more dimensions.
float interpolated_noise( uint x, uint level )
uint xm = x & (0xffffffffU >> level); //mask out the bits that would cause repeat in tex lookup
float c = ldexp(float(xm), level-32); //convert to 0..1 floating point value
//use c in noise tex lookup
float v = texture(noise1D, c).x;
//could be scaled here as well
return ldexp(v, -level);
Then you just sum up the values for how many levels you need to get the result.
The guy who asked the question replied back asking if this implementation is correct:
would you call that function on each axis of your face coordinate?
when you talk about face coordinates i'm getting the impression you mean a 2D grid coordinate. Is that not the case?
is this how you see it being used?
vec3f unitVector; // in range of -1.0 to +1.0
vec3ui intVector = convertFloatToUINT(unitVector);
// intVector now in range 0 to 0xFFFFFFFF
float n = 0.0;
for (int o=0; o n += interpolated_noise(intVector.x, o);
n += interpolated_noise(intVector.y, o);
n += interpolated_noise(intVector.z, o);
And to make long story short, he says this implementation should pretty much work.
Now, this code is really confusing for me, especially the float to uint part, I have no clue how that works. But I hope my reply gives you some ideas on what you can try out.
I would love to hear from someone who managed to solve this problem!
I would also like to add that I solved the vertex precision error (where the mesh is bouncing and jittering around) by having 64 bit precision on the CPU and then when I upload the vertices to the GPU I subtract the camera position from the mesh, so that you get maximum precision for everything that is close to the camera. So basically the camera is always at origin (0, 0, 0) and I just shift everything around the camera for maximum precision.