Strawie
Strawie

Reputation: 143

Perlin Noise getting wrong values in Y axis (C++)

Issue

I'm trying to implement the Perlin Noise algorithm in 2D with a single octave with a size of 16x16. I'm using this as heightmap data for a terrain, however it only seems to work in one axis. Whenever the sample point moves to a new Y section in the Perlin Noise grid, the gradient is very different from what I expect (for example, it often flips from 0.98 to -0.97, which is a very sudden change). enter image description here

This image shows the staggered terrain in the z direction (which is the y axis in the 2D Perlin Noise grid)

Code

I've put the code that calculates which sample point to use at the end since it's quite long and I believe it's not where the issue is, but essentially I scale down the terrain to match the Perlin Noise grid (16x16) and then sample through all the points.

Gradient At Point

So the code that calculates out the gradient at a sample point is the following:

// Find the gradient at a certain sample point
float PerlinNoise::gradientAt(Vector2 point)
{
    // Decimal part of float
    float relativeX = point.x - (int)point.x;
    float relativeY = point.y - (int)point.y;
    Vector2 relativePoint = Vector2(relativeX, relativeY);

    vector<float> weights(4);

    // Find the weights of the 4 surrounding points
    weights = surroundingWeights(point);


    float fadeX = fadeFunction(relativePoint.x);
    float fadeY = fadeFunction(relativePoint.y);


    float lerpA = MathUtils::lerp(weights[0], weights[1], fadeX);
    float lerpB = MathUtils::lerp(weights[2], weights[3], fadeX);
    float lerpC = MathUtils::lerp(lerpA, lerpB, fadeY);


    return lerpC;
}

Surrounding Weights of Point

I believe the issue is somewhere here, in the function that calculates the weights for the 4 surrounding points of a sample point, but I can't seem to figure out what is wrong since all the values seem sensible in the function when stepping through it.

// Find the surrounding weight of a point
vector<float> PerlinNoise::surroundingWeights(Vector2 point){

    // Produces correct values
    vector<Vector2> surroundingPoints = surroundingPointsOf(point);

    vector<float> weights;

    for (unsigned i = 0; i < surroundingPoints.size(); ++i) {
        // The corner to the sample point
        Vector2 cornerToPoint = surroundingPoints[i].toVector(point);

        // Getting the seeded vector from the grid
        float x = surroundingPoints[i].x;
        float y = surroundingPoints[i].y;
        Vector2 seededVector = baseGrid[x][y];

        // Dot product between the seededVector and corner to the sample point vector
        float dotProduct = cornerToPoint.dot(seededVector);

        weights.push_back(dotProduct);
    }

    return weights;

}

OpenGL Setup and Sample Point

Setting up the heightmap and getting the sample point. Variables 'wrongA' and 'wrongA' is an example of when the gradient flips and changes suddenly.

void HeightMap::GenerateRandomTerrain() {

int perlinGridSize = 16;

PerlinNoise perlin_noise = PerlinNoise(perlinGridSize, perlinGridSize);

numVertices = RAW_WIDTH * RAW_HEIGHT;
numIndices = (RAW_WIDTH - 1) * (RAW_HEIGHT - 1) * 6;

vertices = new Vector3[numVertices];
textureCoords = new Vector2[numVertices];
indices = new GLuint[numIndices];

float perlinScale = RAW_HEIGHT/ (float) (perlinGridSize -1);

float height = 50;

float wrongA = perlin_noise.gradientAt(Vector2(0, 68.0f / perlinScale));
float wrongB = perlin_noise.gradientAt(Vector2(0, 69.0f / perlinScale));

for (int x = 0; x < RAW_WIDTH; ++x) {
    for (int z = 0; z < RAW_HEIGHT; ++z) {
        int offset = (x* RAW_WIDTH) + z;

        float xVal = (float)x / perlinScale;
        float yVal = (float)z / perlinScale;

        float noise = perlin_noise.gradientAt(Vector2( xVal , yVal));

        vertices[offset]        = Vector3(x * HEIGHTMAP_X,      noise * height,  z * HEIGHTMAP_Z);
        textureCoords[offset]   = Vector2(x * HEIGHTMAP_TEX_X,  z * HEIGHTMAP_TEX_Z);

    }
}
numIndices = 0;
for (int x = 0; x < RAW_WIDTH - 1; ++x) {
    for (int z = 0; z < RAW_HEIGHT - 1; ++z) {
        int a = (x      * (RAW_WIDTH)) + z;
        int b = ((x + 1)* (RAW_WIDTH)) + z;
        int c = ((x + 1)* (RAW_WIDTH)) + (z + 1);
        int d = (x      * (RAW_WIDTH)) + (z + 1);

        indices[numIndices++] = c;
        indices[numIndices++] = b;
        indices[numIndices++] = a;

        indices[numIndices++] = a;
        indices[numIndices++] = d;
        indices[numIndices++] = c;
    }
}
BufferData();

}

Upvotes: 2

Views: 260

Answers (1)

Strawie
Strawie

Reputation: 143

Turned out the issue was in the interpolation stage:

 float lerpA = MathUtils::lerp(weights[0], weights[1], fadeX);
 float lerpB = MathUtils::lerp(weights[2], weights[3], fadeX);
 float lerpC = MathUtils::lerp(lerpA, lerpB, fadeY);

I had the interpolation in the y axis the wrong way around, so it should have been:

lerp(lerpB, lerpA, fadeY)

Instead of:

lerp(lerpA, lerpB, fadeY)

Upvotes: 3

Related Questions