Resource:
Hello. I made a modulescript that can be used as an alternative to perlin noise. This type of noise is called “Value Noise” (Similar, but Not the same as perlin noise). Here is the code for it:
local noise = {}
noise.__index = noise
function noise.new(seed)
local self = {}
setmetatable(self, noise)
self.seed = seed or Random.new():NextNumber(-256, 256)
return self
end
local randomConstant = 2^32
local function rand(worldSeed, x, y, z)
local random = Random.new(worldSeed)
random = Random.new(bit32.bxor(x, random:NextInteger(-randomConstant, randomConstant)))
random = Random.new(bit32.bxor(y, random:NextInteger(-randomConstant, randomConstant)))
random = Random.new(bit32.bxor(z, random:NextInteger(-randomConstant, randomConstant)))
return random:NextNumber(-1, 1)
end
local function lerp(x, y, a1)
local a2 = -2*a1^3+3*a1^2 -- https://www.desmos.com/calculator/jq1v8r4jwk
return x*(1-a2)+y*a2
end
local function bilinear(x, y, j, k, a, b)
return lerp(lerp(x, y, a), lerp(j, k, a), b)
end
local function trilinear(x, y, j, k, l, m, n, o, a, b, c)
return lerp(bilinear(x, y, j, k, a, b), bilinear(l, m, n, o, a, b), c)
end
function noise:normalSample(x, y, z)
local
bx,
ax = math.floor(x), math.ceil(x)
local
by,
ay = math.floor(y), math.ceil(y)
local
bz,
az = math.floor(z), math.ceil(z)
local tx, ty, tz = x - bx, y - by, z - bz
local a,
b,
c,
d,
e,
f,
g,
h = rand(self.seed, bx, by, bz), rand(self.seed, ax, by, bz), rand(self.seed, bx, ay, bz), rand(self.seed, ax, ay, bz), rand(self.seed, bx, by, az), rand(self.seed, ax, by, az), rand(self.seed, bx, ay, az), rand(self.seed, ax, ay, az)
local noiseValue = trilinear(a, b, c, d, e, f, g, h, tx, ty, tz)
return noiseValue
end
return noise
What does this do?
This resource can be used as an alternative to perlin noise. It generates a 3D grid of random numbers, and then it uses cosine interpolation to figure out what the random numbers in between of the grid coordinates should be, and then returns this number.
Why / Why not?
There is no particular reason why anyone would ever want to use this except for if they just like the results of value noise more than they do perlin noise. I have not benchmarked this module yet, so I don’t know if there is any performance benefits to using this over perlin noise. If you ever want to use this for terrain generation, just keep in mind that terrain generated with perlin noise looks more natural, and less artificial than terrain that is generated with value noise (this module). There are however many different uses for some of the code that makes this module. This modulescript generates a random value for each coordinate in the rand(worldSeed, x, y, z)
function, and then it uses something called “Tri-Cosine Interpolation” to calculate / approximate what the random number should be for all of the possible coordinates in between, so what you could do is modify this code, so it could do other things that have nothing to do with procedural generation / random number generation (ie downsampling some data). Overall, this module is somewhat useful (in my opinion).
How to use it?
Here is a script that generates 3D Value noise:
local noise = require(script.Noise)
local noiseClass = noise.new()
for x = 1, 64 do
for y = 1, 64 do
for z = 1, 64 do
local noiseValue = noiseClass:normalSample(x / 16, y / 16, z / 16) - ((y - 32)/24)
if noiseValue >= 0 then
local p = Instance.new("Part")
p.Parent = workspace
p.CFrame = CFrame.new(x, y, z)
p.Size = Vector3.one
p.Anchored = true
end
end
end
task.wait()
end
Here is also a script that generates 2D Value noise:
local noise = require(script.Noise)
local noiseClass = noise.new()
for x = 1, 256 do
for z = 1, 256 do
local noiseValue = ((noiseClass:normalSample(x / 32, 0, z / 32) + 1) / 2) * 64
local p = Instance.new("Part")
p.Parent = workspace
p.CFrame = CFrame.new(x, noiseValue / 2, z)
p.Size = Vector3.new(1, noiseValue, 1)
p.Anchored = true
end
task.wait()
end
Extras:
Side effects
One thing you might notice is that terrain generated with this looks like it’s been generated on some type of grid:
I did a horrible job of drawing those grid lines, but you might notice that some of the terrain looks “squared” / “cubed”. This isn’t a glitch, but rather an artifact / side effect of using Value Noise.
This issue can be mitigated by adding some type of random offset to the coordinates going into the :normalSample
function.
2D Value noise (With Offset):
local noise = require(script.Noise)
local noiseClass = noise.new()
local offsetNoiseClassX = noise.new()
local offsetNoiseClassZ = noise.new()
for x = 1, 256 do
for z = 1, 256 do
local xOffset = offsetNoiseClassX:normalSample(x / 32, z / 32, 0)
local zOffset = offsetNoiseClassZ:normalSample(x / 32, z / 32, 0)
local noiseValue = ((noiseClass:normalSample(x / 32 + xOffset, 0, z / 32 + zOffset) + 1) / 2) * 64
local p = Instance.new("Part")
p.Parent = workspace
p.CFrame = CFrame.new(x, noiseValue / 2, z)
p.Size = Vector3.new(1, noiseValue, 1)
p.Anchored = true
p.Color = Color3.new(noiseValue / 64, noiseValue / 64, noiseValue / 64)
end
task.wait()
end
Picture:
As you can see, doing this will generate some pretty interesting results that look similar to Perlin noise (But, not quite the same), and I was not able to draw that grid, that I drew in the other screenshot. This can be done with 3D Value noise too, but when you want to do that, you also need to add an offset to the third coordinate (the y coordinate).
3D Value noise (With Offset):
local noise = require(script.Noise)
local noiseClass = noise.new()
local offsetXNoiseClass = noise.new()
local offsetYNoiseClass = noise.new()
local offsetZNoiseClass = noise.new()
for x = 1, 64 do
for y = 1, 64 do
for z = 1, 64 do
local noiseOffsetX = offsetXNoiseClass:normalSample(x / 16, y / 16, z / 16)
local noiseOffsetY = offsetYNoiseClass:normalSample(x / 16, y / 16, z / 16)
local noiseOffsetZ = offsetZNoiseClass:normalSample(x / 16, y / 16, z / 16)
local noiseValue = noiseClass:normalSample(x / 16 + noiseOffsetX, y / 16 + noiseOffsetY, z / 16 + noiseOffsetZ) - ((y - 32)/24)
if noiseValue >= 0 then
local p = Instance.new("Part")
p.Parent = workspace
p.CFrame = CFrame.new(x, y, z)
p.Size = Vector3.one
p.Anchored = true
end
end
end
task.wait()
end
You can also make this linear instead of cosine by replacing the lerp(x, y, a1)
function (that is inside of the value noise module) with:
local function lerp(x, y, a1)
return x*(1-a1)+y*a1
end