Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support disabling rescaling in Clamp to avoid seams with repeating Perlin/Simplex terrains #26

Open
campbellgoe opened this issue Jul 29, 2019 · 17 comments

Comments

@campbellgoe
Copy link

Feature request:

It would be great to be able to set an x and y offset for perlin / simplex noise, and potentially other noise functions.

The usage in my case would be to create tiles of THREE.Terrain, each positioned next to each-other, with the same simplex noise seed, but with its x, y axes offset so they match the position, and the edges of the tiles match up. Then you will be able to create many tiles of terrain, loading nearby tiles and unloaded distance tiles.

I did try to implement this by just adding an offsetX and offsetY in the simplex function, but the results were not as expected and I wasn't sure how to do this.

Thanks.

@IceCreamYou
Copy link
Owner

The Perlin generator is defined here:

THREE.Terrain.Perlin = function(g, options) {
noise.seed(Math.random());
var range = (options.maxHeight - options.minHeight) * 0.5,
divisor = (Math.min(options.xSegments, options.ySegments) + 1) / options.frequency;
for (var i = 0, xl = options.xSegments + 1; i < xl; i++) {
for (var j = 0, yl = options.ySegments + 1; j < yl; j++) {
g[j * xl + i].z += noise.perlin(i / divisor, j / divisor) * range;
}
}
};

The Simplex generator is defined similarly.

You can define your own generator which passes different values to noise.perlin() or noise.simplex().

In the provided generators, the X/Y values passed to those functions are i / divisor, j / divisor. i/j are the X/Y indices, respectively, of the vertex whose height is being calculated. divisor essentially defines the scale of the features in the generated noise. For your use case you would probably just want to pass different values of i/j depending on the location of the affected tile relative to your origin tile.

The other thing to pay attention to is that every call to THREE.Terrain.Perlin() or THREE.Terrain.Simplex() currently re-seeds noise. If you were to write your own generator method, you would want to only seed noise once rather than on each call.

@campbellgoe
Copy link
Author

campbellgoe commented Jul 29, 2019

Thanks. I figured that was the correct bit of code to add the offset, and that the random needed to remain the same for each tile.

Although somehow still have a seam, as seen here:

sand seam

But that's probably a problem with my implementation.

@campbellgoe
Copy link
Author

campbellgoe commented Jul 29, 2019

For example

Terrain.simplex(
          (i + ox * (options.xSegments + 1)) / divisor,
          (j + oy * (options.ySegments + 1)) / divisor
        ) * range;

Where ox, oy are offsets such as (-1,-1), (0,-1), (1, -1), (-1, 0)...
i and j go from 0 through options.xSegments + 1, so I am assuming perfect offsets will be multiples of options.xSegments + 1, however as seen in the image above, there are very small seams.
If the logic above is correct (which I'm not sure about) then it likely means a bit of variation may be arising due to a random seed being improperly set/not set.

It could be due to do with the height range, if one tile/chunk has a different natural height range, could it be being distorted on the y axis relative to other tiles/chunks?

Sorry for asking questions here, if there's a better place, I'd be happy to ask there instead.

@IceCreamYou
Copy link
Owner

IceCreamYou commented Jul 29, 2019 via email

@campbellgoe
Copy link
Author

campbellgoe commented Aug 1, 2019

I've done as much as I could to ensure the x,y inputs for the noise function were correct and each chunk edge had the same correct corresponding x,y coords. However, I was still unable to get the chunks perfectly connecting.

Here is a 2x2 grid of 1024x1024 terrains with 15x15 segments, and this gives me these results:

4x4
with seam
seam prominent

I find this very odd, as surely the noise function should output the same values for the same x,y coordinates.

I think a solution so I can move on would be to just snap these edges together, and that shouldn't be noticeable.

@IceCreamYou
Copy link
Owner

IceCreamYou commented Aug 1, 2019

I think a solution so I can move on would be to just snap these edges together, and that shouldn't be noticeable.

That's what I would do. That said, I'd still be willing to bet that the arguments you're passing to noise.simplex are very slightly different for vertices that should be at the same X/Y coordinates due to floating point precision issues. The only way to know would be to log the those values.

(Or is it possible there's some other generator or something else other than the simplex generator that's affecting the points on your terrain plane?)

@campbellgoe
Copy link
Author

campbellgoe commented Aug 1, 2019

After setting each seams / edge verticies Z all to 0, I get this:

0 z seam

Whereas setting the edge seams Z to 100 removes the seam.

100 z seam

Seam begins to appear somewhere between 92 and 93 z. Setting the edge verticies to less than this amount makes the seam appear.

@campbellgoe
Copy link
Author

Could it be due to Clamp ?

 * Rescale the heightmap of a terrain to keep it within the maximum range.
 ...
function Clamp(g, options) {
  ...
  for (i = 0; i < l; i++) {
    g[i].z = options.easing((g[i].z - min) / actualRange) * range + optMin;
  }
}```

@campbellgoe
Copy link
Author

Yes, disabling Clamp in the Normalize function fixed it. The seams are no more!

@IceCreamYou
Copy link
Owner

Good catch! Clamp does indeed rescale / stretch the terrain vertically, so if your max or min Z values are different on the different terrains, it would make the edges misaligned. Without Clamp, you don't get easing, and depending on the generation function used you could end up with values outside of the desired max/min Z-values. However there should probably be a way to disable it for use cases like this!

@IceCreamYou IceCreamYou reopened this Aug 2, 2019
@IceCreamYou IceCreamYou changed the title Give perlin / simplex x, y offsets. Support disabling rescaling in Clamp to avoid seams with repeating Perlin/Simplex terrains Aug 2, 2019
@campbellgoe
Copy link
Author

A related issue I am getting is with the shading/shadows as seen below in the sand version:

SAND SHADOW SEAM
same image with UV_Grid

@IceCreamYou
Copy link
Owner

In addition to what I mentioned in #27, there's probably an additional factor here around how the shadow interpolation works. Honestly I don't really understand the details and haven't spent much time looking at it since 2014, but I think that the shadows are vertex based and so the calculations won't come out the same for edge vertices with significantly different slopes on either side. Fixing this probably requires using different shadow fragments which might require using a different base texture.

@campbellgoe
Copy link
Author

I found a solution to shadow seams thanks to thrax.

The idea is to generate terrain with the outer segments overlapping the other tiles, then computeVertexNormals, and finally to trim the excess segments so the tiles no longer overlap.

In other words:

  1. Generate each tile with +2 x/y segments, making sure the size of the tile grows from its center, so that the extreme segments overlap perfectly with the other tiles.
  2. computeVertexNormals
  3. Remove the extra segments made in step 1, so that the terrain is back to the normal tile size, without overlapping.

This ensures the shadows take into account neighbouring vertexes at tile extremes.

@IceCreamYou
Copy link
Owner

Good idea!

@c4b4d4
Copy link

c4b4d4 commented Nov 25, 2022

Terrain.simplex(
          (i + ox * (options.xSegments + 1)) / divisor,
          (j + oy * (options.ySegments + 1)) / divisor
        ) * range;

Where ox, oy are offsets such as (-1,-1), (0,-1), (1, -1), (-1, 0)... i and j go from 0 through options.xSegments + 1, so I am assuming perfect offsets will be multiples of options.xSegments + 1, however as seen in the image above, there are very small seams. If the logic above is correct (which I'm not sure about) then it likely means a bit of variation may be arising due to a random seed being improperly set/not set.

Did you ever finished your project? I'm struggling at the moment with making it seamless:

I tried with your noise modification:

g[j * xl + i] += noise.simplex((i + options.offsetX * (options.xSegments + 1)) / divisor, (j + options.offsetY * (options.ySegments + 1)) / divisor) * range;

But no luck at all...

Screenshot 2022-11-25 at 19 52 09

THREE.Terrain.Simplex = function(g, options) {
    noise.seed(options.seed);
    var range = (options.maxHeight - options.minHeight) * 0.5,
        divisor = (Math.min(options.xSegments, options.ySegments) + 1) * 2 / options.frequency;
    for (var i = 0, xl = options.xSegments + 1; i < xl; i++) {
        for (var j = 0, yl = options.ySegments + 1; j < yl; j++) {
            g[j * xl + i] += noise.simplex((i + options.offsetX * (options.xSegments + 1)) / divisor, (j + options.offsetY * (options.ySegments + 1)) / divisor) * range;
        }
    }
};

I also removed the Clamp function, like you commented on here and I'm using the following options:

const options = {
easing: THREE.Terrain.Linear,
       heightmap:THREE.Terrain.Simplex,
      steps: 2,
      xSegments: 127,
      ySegments: 127,
      maxHeight: 200,
      stretch:false,
      offsetX:tileX,
      offsetY:tileY,
      minHeight: -100,
      seed:0.7281564856921896,
        xSize: 1024,
        ySize: 1024,
    }

Edit: I think it has to do with the steps.

@c4b4d4
Copy link

c4b4d4 commented Nov 25, 2022

Yes, the problem is at stepping.

These two have exactly the same settings, except step, I'm variating it from 1 to 2.

Any ideas on how to make it seamless? And using something like step, which creates more desired results.

I think the problem lays here:

g[i] = buckets[j].avg;

smooth

step

@c4b4d4
Copy link

c4b4d4 commented Nov 26, 2022

So, I replaced the Step function with one of my own that does not require neighborhood checking, cause that's what messes it up when you divide it in chunks; you don't know who your neighborhood is unless you load it, but that doesn't work cause it's an infinite cycle.

Made it really rough, will post it when I have it cleaned up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants