Improved 3D (Time-z) Perlin Noise

Hi everyone,

I’ve been experimenting with generating 3D Perlin noise in Unity, and I think I’ve come up with an interesting approach that might be useful for others. I could not find a 3D Perlin function that looked like a 2D Perlin, without artifacts or repeats, and without arbitrary property adjustments. That said, I’m honestly not sure if this is entirely novel or if I’m just reinventing the wheel, so I’d love to get some feedback from the community.

The method I’m using involves oscillating two 2D Perlin noise layers as a function of a time parameter. This approach results in a smooth, evolving 3D noise effect without noticeable artifacts, which I found tends to be an issue especially with easy implementations that average 6 Perlin noise calls. The generated noise evolves deterministically over time, without needing to translate coordinates or tweak other parameters arbitrarily.

Key Features:

  • Only two Perlin noise calls are required, which keeps it efficient.
  • No typical artifacts or repeating patterns—just a consistent 3D effect.
  • The third dimension is treated as a time variable, which makes it suitable for procedural content that evolves over time without changing the properties of the 2D perlin.
  • Note that with the z layer being a time dimension, you will have to independently scale that dimension to match your purposes.

Here’s an example of the method signature:

public static float Noise3D(float x, float y, float time, float scale, float viewport, int steps, float low, float high, float target)

I’ve put the code on GitHub under the MIT License, so anyone is free to use it. I’d really appreciate it if some of you could take a look and let me know what you think. I’m not an expert, so I’m genuinely curious if there are limitations or edge cases I haven’t considered.

You can find the full implementation here: git repo

Thanks so much in advance for any feedback or thoughts!

using UnityEngine;

public static class Perlin
{
    /// <summary>
    /// Generates a blended Perlin noise value that evolves over time to create a 3D Perlin effect.
    /// </summary>
    /// <param name="x">The x-coordinate for the noise calculation.</param>
    /// <param name="y">The y-coordinate for the noise calculation.</param>
    /// <param name="time">The time variable, representing the third dimension.</param>
    /// <param name="scale">The scale factor to control the frequency of the noise.</param>
    /// <param name="viewport">The size of the observable field used for offset calculation.</param>
    /// <param name="steps">The number of steps for quantizing the blended noise value.</param>
    /// <param name="low">The minimum value of the noise output range.</param>
    /// <param name="high">The maximum value of the noise output range.</param>
    /// <param name="target">The target value used for proximity calculations.</param>
    /// <returns>A clamped, stepped Perlin noise value in the range [0, 1].</returns>
    public static float Noise3D(float x, float y, float time, float scale, float viewport, int steps, float low, float high, float target)
    {
        // Determine cycles for peak and valley updates based on time progression
        int cycleCount = Mathf.FloorToInt((time + Mathf.PI / 2) / Mathf.PI); // Adjust cycle count to align with peaks and valleys

        // Static offsets that change deterministically based on the cycle count
        float startOffset = 1000f;
        float offset2 = startOffset + (cycleCount / 2) * viewport; // Offset1 changes at each peak (even cycles)
        float offset1 = (viewport + startOffset) + ((cycleCount + 1) / 2) * viewport; // Offset2 changes at each valley (odd cycles)

        // Calculate Perlin noise values with offsets
        float delta = high - low;
        float noise1 = Mathf.PerlinNoise((x + offset1) * scale, y * scale) * delta + low;
        float noise2 = Mathf.PerlinNoise(x * scale, (y + offset2) * scale) * delta + low;

        // Use a sine function to oscillate between the two Perlin noise values
        float period = Mathf.PI; // Period of the wave
        float t = Mathf.PingPong((time + period / 2) / period, 1.0f);

        // Blend the two Perlin noise values based on the oscillation value
        float blendedNoise = Mathf.Lerp(noise1, noise2, t);

        // Calculate proximity to the target value and adjust the blended noise accordingly
        float miss = Mathf.Abs(target - blendedNoise);
        float prox = (1 - miss) * 2f;
        float stepped = Mathf.Floor(prox * steps) / steps;

        // Clamp the final output to the range [0, 1]
        return Mathf.Clamp(stepped, 0.0f, 1.0f);
    }    
}

1 Like

i dont how then !!

The original Perlin noise reference implementation is 3D and still available as Java code on Ken Perlin’s blog. The Mathf Unity version essentially simplified that code with z fixed to 0. The new Mathematics library adds classic Perlin noise for 2D, 3D, and 4D as well as simplex noise for 2D, 3D and 4D.