Value Noise Module

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 = (1-math.cos(a1*math.pi))/2
	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
2 Likes

really cool resource! i quickly coded it into my 2D Perlin Noise Visualizer and got some cool results!
(rather low resolution because i’m doing this on my old laptop)



1 Like