1
\$\begingroup\$

I'm trying to use simplex Perlin-noise to create topography of a planet (procedurally generated). The basic sphere consists of 6 sided cube with normalized vertices.

Before adding noise the first problem was the Unity did not calculate the normals correctly at the edges of the sides so seams were visible.

According to this solution (Calculate mesh normals with added noise): the gradients are calculated for each vertex and normals are calculated from these gradients. It works with one layer of noise: enter image description here

I'm trying to implement some features like in this video ([Unity] Procedural Planets (E03: layered noise)):

  • multiple layers of noise
  • shift the elevation value from (-1,+1) to (0,1)
  • add strength
  • add minimum value

Multiple layers of noise

Here is the base of my noise and normal generation method:

private float GetElevationAndNormal(Vector3 point, 
                                    out Vector3 normal, 
                                    float baseFrequency, 
                                    float baseAmplitude, 
                                    float roughness, 
                                    float persistance, 
                                    int layers)
{
    float frequency = baseFrequency;
    float amplitude = baseAmplitude;

    float noiseValue = 0;
    Vector3 gradients = Vector3.zero;

    for (int i = 0; i < layers; i++)
    {
        // Sample the noise value and gradient at our position.
        NoiseEvaluation evaluation = _noise.Evaluate(point * frequency);
        noiseValue += evaluation.Value * amplitude;
        gradients += evaluation.Gradient;

        if (i < layers - 1)
        {
            amplitude *= persistance;
            frequency *= roughness;
        }
    }

    // Zero out the component of the gradient perpendicular to the sphere.
    var surfaceGradient = gradients - point * Vector3.Dot(gradients, point);

    // Compute an updated normal - leaning away from the direction of increase.
    normal = (point - 25f * amplitude * frequency * surfaceGradient).normalized;

    // Convert our noise sample to an elevation.
    return 1.0f + noiseValue;
}

And the result is not correct:

enter image description here

I know the normal calculation is the following: \$\vec{n} = \vec{x} − s \cdot \vec{h}\$, where \$\vec{h}\$ is the surfaceGradient variable. (Surface normal to point on displaced sphere) So maybe the problem is with the value of \$s\$. I tried the following things:

  • use the sum of amplitude and frequency of each layers
  • use the last value of amplitude and frequency
  • use other value than 25f

Update #1: I tried to sum the scaled gradients but it seems there is a little misalignment yet. At 2 layers of noise it is almost OK but the shading on the left is a little bit lighter. At 5 layers of noise there is a lot of differences.

2 layers of noise: enter image description here

5 layers of noise: enter image description here

Here is the modified code:

float frequency = baseFrequency;
float amplitude = baseAmplitude;

float noiseValue = 0;
Vector3 gradients = Vector3.zero;

for (int i = 0; i < layers; i++)
{
    // Sample the noise value and gradient at our position.
    NoiseEvaluation evaluation = _noise.Evaluate(point * frequency);
    noiseValue += evaluation.Value * amplitude;
    gradients += evaluation.Gradient * 25f * amplitude * frequency;

    if (i < layers - 1)
    {
        amplitude *= persistance;
        frequency *= roughness;
    }
}

// Zero out the component of the gradient perpendicular to the sphere.
var surfaceGradient = gradients - point * Vector3.Dot(gradients, point);

// Compute an updated normal - leaning away from the direction of increase.
normal = (point - surfaceGradient).normalized;

Question #1: how should I change the normal calculation in case of multiple noise layers? What is the relation between layer numbers and normal calculations? Do I modify amplitude and frequency wrong in the for loop?

Shift elevation

This works perfect I had to modify the noise value and also the gradient value:

NoiseEvaluation evaluation = _noise.Evaluate(point * frequency);
noiseValue += (evaluation.Value + 1) * 0.5f * amplitude;
...
gradients *= 0.5f;

Strength

I wanted to use this parameter to scale the noise, also works perfect, just had to multiply also the sum of noise values and the sum of gradient with the strength value:

noiseValue *= strength;
gradients *= strength;

Minimum value

It has an issue: when a noise value is below the minimum value than I use the minimum value as elevation. In this case the normal vector should be the vector pointing from the center point to the actual vertex point. But the gradient returned from the Evaluate function is calculated based on the original elevation not the minimum. The result: enter image description here

I modified the algorithm in the following way: when an elevation is a minimum value then use the vector point from center point to the actual vertex point. This solved the issue but not perfectly because normals are not correct at the edge of "plains" (I get better result when use the Unity RecalculateNormals method):

enter image description here

Question #2: Is there any way to fix these few wrong normals at the edge of "plains"?

\$\endgroup\$
1
  • \$\begingroup\$ The 5-layer example might be closer to correct than the comparison makes it seem. Remember that RecalculateNormals works by averaging-together adjacent face normals, so you get less resolution that way, smoothing out small bumps or missing them entirely when they land between two vertices. The gradient method samples the exact wiggles of the noise field per vertex, so it can capture more details about how the surface is bending, leading to denser patterns on high-frequency octaves. I'd say the results you got there look plausible, and you might only need to tune that 25f fudge factor by eye. \$\endgroup\$
    – DMGregory
    Jan 7, 2021 at 15:05

1 Answer 1

0
\$\begingroup\$

I accept DMGregory's comment that this little difference does not seem to be an algorithm issue and I use this method in the future instead of the Unity built-in normal recalculations.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .