glsl – Raymarching distance function resulting in surface holes – step size required?

I have studied the functions of raymarching and signed distance, and I have implemented a raymarching fragment shader in Unity. It works very well for geometric shapes such as cubes and spheres, but when I started trying to create terrain with it, I started to encounter problems.

I think I have limited the problem to my signed distance function and not to the implementation of Raymarching. To simplify things, I've reproduced the problem using a sine wave, which seems to give the same result I see with the noise functions.

The first problem is that I get strange holes on the surface – at least I think they are holes. Repeatedly, rendering is done in the volume rather than the surface:
Dark / black holes

Here is a modified color output in which I color each pixel based on the value of the signed distance.

color = 0 - signedDistance

  • Red: unaffected area
  • Black: on the surface or very near
  • White: inside the volume

White areas indicating that the point being rendered is within the volume

I would expect not to see any white at all in this picture. The results must be either black (surface found) or red (surface not found, ray scrolling stopped).

The other problem, which is closely related to the first, is that I get imprecise normal surface values ‚Äč‚Äčover relatively large distances, which gives a wavy appearance. However, I am pretty sure that this is due to the normal calculation using the same signed distance function that comes back into the volume (which would provide incorrect, possibly inverted, distance results). So I focus first on this issue.

I have reproduced the problem that I have on Shadertoy. Although it is not exactly the same code, the basic algorithm is the same for the GLSL Shadertoy sample and for my Unity HLSL shader.

First, here is the most basic signed distance function I've used successfully. This makes just a flat plane at y = 0:

float map (position float3) {
float sdist = position.y;
return sdist;
}

Now, I add a sine wave over this flat plane:

float map (position float3) {
float sdist = position.y + sin (x);
return sdist;
}

What I'm waiting for is to see the plane going up and down like a sine wave along the x-axis. I see this, but I also see these strange holes (in the pictures above) across the surface. It is as if the scrolling part of the rays crossed the surface and then stopped inside the volume.

I can somewhat mitigate this by intervening in steps much smaller than the computed calculated distance. Here is an example with the step size at 0.05:

The size of the step is 0.05

But this decreases the performance since each ray / step step now crosses much smaller distances.

Is a distance shorter than the calculated distance function the way to solve this problem?

Just to be complete, here is a simplified version of the scrolling radius function:

// multiplier before march
float stepSize = 1.0;
// maximum number of steps forward
float maxRayCasts = 128;
// The minimum distance considered as the surface
float precis = 0.01;

vec3 castRay (in vec3 ro, in vec3 rd) {
float tmin = 0.01;
float t max = 600.0;

vec2 res;

float t = tmin;
float m = -1.0;
for (int i = 0; i <maxRayCasts; i ++) {
res = map (ro + rd * t);
if (res.xtmax) pause;
t + = res.x * stepSize;
m = res.y;
}

if (t> tmax) m = -1.0;
returns vec3 (t, m, res.x);
}