Noise Generator
  • Project Type:-
    Self
  • Engine Used:-
    NA
  • Languages Used:-
    C++
  • Target Platform:-
    PC

A Noise Generation library written in C++ using the stb_image library for rendering to an image. The noise maps utlise procedural random number generators for generating random numbers. These are mapped to an 2D Array of pixel data which can be converted into a .png file.

Various noise maps which can be generated using the library:-

Rendering random noise to a 2D Image.

Generated a 2D array of pixels with each pixel having a value b/w 0.0 to 1.0. The value of 0.0 will imply black color and 1.0 will imply white color.

Code sample for the random noise generation:-

// Gets a Random Noise Map
float *get_noisemap(int rows, int columns)
{
    float *noiseMap = new float[rows * columns];
    for (int y = 0; y < rows; y++)
    {
        for (int x = 0; x < columns; x++)
        {
            noiseMap[(y * columns) + x] = get_random_noise();
        }
    }
    return noiseMap;
}

Rendering Perlin Noise to a 2D Map

The values of each pixel in a perlin noise map will be dependent on the X and Y coordinates of that pixel. Nearby pixels will have similar values and the pixel values will lie b/w 0.0 to 1.0.

Code sample for the perlin noise generation:-

// Gets a Perlin Noise Map
float *get_noisemap(int rows, int columns, float scale)
{
    ...
    PerlinNoiseGenerator pn(seed);
    for (int y = 0; y < rows; y++)
    {
        for (int x = 0; x < columns; x++)
        {
            float sampleX = (x / scale);
            float sampleY = (y / scale);
            float noiseVal = get_perlin_noise(sampleX, sampleY, &pn);
            noiseMap[(y * columns) + x] = noiseVal;
        }
    }
    return noiseMap;
}

Rendering Octave Noise to a 2D Map

Multiple layers of perlin noise can be layered upon one another to create octave noise. Each layer will have smaller amplitude and higher frequency. These layers are scaled in the range of [-1, 1] and added to each other. The final octave noise will then be obtained and scaled in the range of [0, 1].

Code sample for the octave noise generation:-

...
for (int y = 0; y < rows; y++)
{
    for (int x = 0; x < columns; x++)
    {
        float amplitude = 1;
        float frequency = 1;
        float noiseVal = 0;
        for (int i = 0; i < octaves; i++)
        {
            float sampleX = (((x - halfX) / scale) * frequency) + octaveOffsets[(i * 2)];
            float sampleY = (((y - halfY) / scale) * frequency) + octaveOffsets[(i * 2) + 1];
            float noise = (get_perlin_noise(sampleX, sampleY, &pn) * 2) - 1;
            noiseVal += noise * amplitude;
            amplitude *= persistence;
            frequency *= lacunarity;
        }
        maxNoise = max(maxNoise, noiseVal);
        minNoise = min(minNoise, noiseVal);
        noiseMap[(y * columns) + x] = noiseVal;
    }
}
...

Generating Color Maps from Octave noise

A color map of the octave noise can be made by defining region thresholds for the noise values. Each region will have a color value and a minimum height. Based on the noise value of a given pixel, we find the region and assign a color value to that pixel.

Code sample for the color map:-

// The Default Map Sections for a Color Map
const MapSection mapSections[MAP_REGIONS] = {
    MapSection(DARK_BLUE),
    MapSection(OCEAN_BLUE, 0.05f),
    MapSection(DARK_YELLOW, 0.37f),
    MapSection(LAND_GREEN, 0.4f),
    MapSection(DARK_GREEN, 0.6f),
    MapSection(LIGHT_BROWN, 0.725f),
    MapSection(DARK_BROWN, 0.85f),
    MapSection(LIGHT_BLUE, 0.925f)};

...

// Gets the Section from the pixel height
Colorf get_color_from_sections(float height)
{
    int n = 0;
    for (int i = 0; i < MAP_REGIONS; i++)
    {
        if (mapSections[i].height > height)
        {
            break;
        }
        n = i;
    }
    return mapSections[n].col;
}

Apply Falloff to noise maps

A falloff map can be generated by using a maximum check on the pixel coordinates. This can be further altered by passing through a function as:- [x ^ a / (x ^ a + {b * (1 - x)} ^ a)]

Code Sample for falloff map generation:-

// Gets a FallOff Map
...
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < columns; j++)
        {
            float y = ((float)i / rows) * 2 - 1;
            float x = ((float)j / columns) * 2 - 1;
            float val = max(absolute(x), absolute(y));
            falloffmap[(i * columns) + j] = clamp(eval_falloff_value(val, curve, shift));
        }
    }
...

...
// Function transformation for Falloff map
float eval_falloff_value(float val, float curve, float shift)
{
    float a = curve;
    float b = shift;
    val = pow(val, a) / (pow(val, a) + pow(b - (b * val), a));
    return val;
}

The falloff map can be subtracted from the octave noise map to get a closed noise map.

This noise map can be used to create a color map. The color maps will be covered from all sides to get a more usable map.

Color Blending in Color Maps

The color value for a pixel in a color map is region-dependent. If the value is 0.30 or 0.32, the color value will be same if they lie in the same region. A better solution would be if the color value gets interpolated b/w the color values of adjacent regions.

Code Sample for the color blending in color map:-

...
    float height = texData->tex[texIndex];
    int index = get_index_from_height(height);
    Colorf col = mapSections[index].col;

#if BLEND_REGIONS
    int indexB = index + 1;
    float h2 = 1.0f;
    Colorf col2(1.0f);
    if (indexB < MAP_REGIONS)
    {
        h2 = mapSections[indexB].height;
        col2 = mapSections[indexB].col;
    }

    float off = inverse_lerp(height, mapSections[index].height, h2);
    if (off > BLEND_THRESHOLD)
    {
        Colorf mixCol = col_lerp(col, col2, off);
        col = col_lerp(col, mixCol, BLEND_FACTOR);
    }
#endif

    int imgIndex = texData->get_index(newI, newJ);
    img[imgIndex] = get_color(col);
...

Comparision with color maps with no blending(left) and one with blending(right):-

Level of Detail (LOD) for image generation

For the output image, we can define Level of Detail (LOD) such that closer pixels have the same values. This leads to smaller files sizes and fewer number of calculations to be done.

Code sample for defining LODs for output image:-

// Without LODs
//--------------------
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < columns; j++)
    {
        ...
        int imgIndex = texData->get_index(i, j);
        img[imgIndex] = get_color(col);
    }
}

// With LODs
//--------------------
for (int i = 0; i < rows; i += 1 + imgLOD)
{
    for (int j = 0; j < columns; j += 1 + imgLOD)
    {
        int texIndex = texData->get_index(i, j);
        int iEnd = clampi(i + imgLOD, 0, rows - 1);
        int jEnd = clamp(j + imgLOD, 0, columns - 1);
        for (int newI = i; newI <= iEnd; newI++)
        {
            for (int newJ = j; newJ <= jEnd; newJ++)
            {
                ...
                int imgIndex = texData->get_index(newI, newJ);
                img[imgIndex] = get_color(col);
            }
        }
    }
}