I've developed a new Perlin-style noise. Can you improve it?

This is a simplified version of the OpenSimplex noise (and all its versions).

seed = 0x120367e73b381c44
#Modified version of random number generator by Bernard Widynski. https://arxiv.org/abs/2004.06278

get_random_number(pos,seed) = get_random_number(convert(UInt64,pos),convert(UInt64,seed))
get_random_noise(pos,seed) = get_random_noise(convert(UInt64,pos),convert(UInt64,seed))

function get_random_number(pos::UInt64,seed::UInt64)
    y = x = pos*seed; z = y+seed
    x = x*x+y; x = (x>>32)|(x<<32)
    x = x*x+z; x = (x>>32)|(x<<32)
    x = x*x+y; x = (x>>32)|(x<<32)
    return ((x*x+z)>>32)%UInt32

function get_random_noise(pos::UInt64,seed::UInt64)
    y = x = pos*seed; z = y+seed
    x = x*x+y; x = (x>>32)|(x<<32)
    x = x*x+z; x = (x>>32)|(x<<32)
    x = x*x+y; x = (x>>32)|(x<<32)
    return reinterpret(Float32,(((x*x+z)>>41)%UInt32)|reinterpret(UInt32,Float32(1.0)))-Float32(1.0)

function smooth_transition(x)
    x = x*(1-x)
    x2 = x*x
    return 16*x2

function smoother_step_7_th_order(x)
    x_sq = x*x
    return x_sq*x_sq*(((-20*x+70)*x-84)*x+35)

using Plots
using Random
using LoopVectorization
x = range(0, 1, length=100)
y = smoother_step_7_th_order.(x)

const main_seed::UInt64 = 0xb6e06d07c7dd3a5f 
#seeder = Xoshiro(main_seed)

@inline function noise2d(x_in,y_in,xx_seed,xy_seed,xz_seed,yx_seed,yy_seed,yz_seed,zx_seed,zy_seed,zz_seed)
    #x = 0.66666667*y_in

    eps = 1e-6
    unit = one(UInt64)
    y = 0.57735027*x_in -0.33333333*y_in
    z = -0.57735027*x_in -0.33333333*y_in
    x = -y-z
    #(X,x) = (reinterpret(UInt64,floor(Int64,x)),x%1)
    X = reinterpret(UInt64,floor(Int64,x))
    x = x-floor(x)
    Y = reinterpret(UInt64,floor(Int64,y))
    y = y-floor(y)
    Z = reinterpret(UInt64,floor(Int64,z))
    z = z-floor(z)
    #(Y,y) = (reinterpret(UInt64,floor(Int64,y)),y%1)
    #(Z,z) = (reinterpret(UInt64,floor(Int64,z)),z%1)
    next_X = X+unit
    next_Y = Y+unit
    next_Z = Z+unit

    x_seed = xx_seed⊻get_random_number(Y,xy_seed)⊻get_random_number(Z,xz_seed)

    current_x_value = get_random_noise(X,x_seed)
    next_x_value = get_random_noise(next_X,x_seed)
    true_x_value = (smoother_step_7_th_order(x)*(next_x_value-current_x_value) + current_x_value-0.5)*2
    true_x_value *= smooth_transition(y)*smooth_transition(z)
    y_seed = yy_seed⊻get_random_number(X,yx_seed)⊻get_random_number(Z,yz_seed)
    current_y_value = get_random_noise(Y,y_seed)
    next_y_value = get_random_noise(next_Y,y_seed)
    true_y_value = (smoother_step_7_th_order(y)*(next_y_value-current_y_value) + current_y_value-0.5)*2

    true_y_value *= smooth_transition(x)*smooth_transition(z)

    z_seed = zz_seed⊻get_random_number(X,zx_seed)⊻get_random_number(Y,zy_seed)
    current_z_value = get_random_noise(Z,z_seed)
    next_z_value = get_random_noise(next_Z,z_seed)
    true_z_value = (smoother_step_7_th_order(z)*(next_z_value-current_z_value) + current_z_value-0.5)*2

    true_z_value *= smooth_transition(x)*smooth_transition(y)

    return (true_x_value+true_y_value+true_z_value)/3

function main(seed)
    seeder = Xoshiro(seed)

    #Seed can be generated in any random way.
    xx_seed = rand(seeder,UInt64)
    xy_seed = rand(seeder,UInt64)
    xz_seed = rand(seeder,UInt64)
    yx_seed = rand(seeder,UInt64)
    yy_seed = rand(seeder,UInt64)
    yz_seed = rand(seeder,UInt64)
    zx_seed = rand(seeder,UInt64)
    zy_seed = rand(seeder,UInt64)
    zz_seed = rand(seeder,UInt64)
    A = Array{Float64,2}(undef,(1024,1024))
    for y in 1:1024, x in 1:1024
        @inbounds @fastmath A[x,y] = noise2d(x/64,y/64,xx_seed,xy_seed,xz_seed,yx_seed,yy_seed,yz_seed,zx_seed,zy_seed,zz_seed)
    return A


@Siddharth_Bhatia, @Kyjor
@cormullion you might be interested.

1 Like

So can you describe a bit more what you changed? What properties this has? How it compares to other implementations?

Also what do you have in mind with “can you improve it”? Are there allocation you think are unnecessary? Do you think it could be faster (and have some benchmarks and reasoning why you think that is)? Do you want stylistic suggestions?

Please put in a bit more effort in your post if you want people to work on/with your code.


Nice. You might want to compare against the Julia state of the art: Michael Fiano’s CoherentNoise.jl.


My implementation:

  1. sidestep the need to determine which simplex we’re in by overparameterizing the coordinate by 1 dimension.
  2. sidestep the requirement for the coordinate of the vertices by interpolating between the (N-1)-dimensional lines inside two adjacent simplices. The 2d noise inside a triangle is made of three sets of two triangles averaged together, with the triangle itself forming the pair with each adjacent triangle.
  3. Is completely branchless.
  4. Use a new square-based pseudorandom number generator instead of lookup-based hash.


  1. Currently, this doesn’t work with loopvectorization. I think it has to do with bit operations not being supported. Can this be made to work with SIMD/GPU/etc?
  2. How can the noise artifacts be reduced?