4D perlin noise bug, help needed!

I’m trying to make clouds with 4D perlin noise but I’ve encountered a bug. When testing it after a long time the parts just created sloped columns of parts instead of noise. Heres my clouds:


vs how i want it:


Here’s my module that makes perlin noise: `local PerlinNoiseAPI = {}

function PerlinNoiseAPI.new(coords,amplitude,octaves,persistence)
coords = coords or {}
octaves = octaves or 1
persistence = persistence or 0.5
if #coords > 4 then
error(“The Perlin Noise API doesn’t support more than 4 dimensions!”)
else
if octaves < 1 then
error(“Octaves have to be 1 or higher!”)
else
local X = coords[1] or 0
local Y = coords[2] or 0
local Z = coords[3] or 0
local W = coords[4] or 0

		amplitude = amplitude or 10
		octaves = octaves-1
		if W == 0 then
			local perlinvalue = (math.noise(X/amplitude,Y/amplitude,Z/amplitude))
			if octaves ~= 0 then
				for i = 1,octaves do
					perlinvalue = perlinvalue+(math.noise(X/(amplitude*(persistence^i)),Y/(amplitude*(persistence^i)),Z/(amplitude*(persistence^i)))/(2^i))
				end
			end
			return perlinvalue
		else
			local AB = math.noise(X/amplitude,Y/amplitude)
			local AC = math.noise(X/amplitude,Z/amplitude)
			local AD = math.noise(X/amplitude,W/amplitude)
			local BC = math.noise(Y/amplitude,Z/amplitude)
			local BD = math.noise(Y/amplitude,W/amplitude)
			local CD = math.noise(Z/amplitude,W/amplitude)
				
			local BA = math.noise(Y/amplitude,X/amplitude)
			local CA = math.noise(Z/amplitude,X/amplitude)
			local DA = math.noise(W/amplitude,X/amplitude)
			local CB = math.noise(Z/amplitude,Y/amplitude)
			local DB = math.noise(W/amplitude,Y/amplitude)
			local DC = math.noise(W/amplitude,Z/amplitude)
			
			local ABCD = AB+AC+AD+BC+BD+CD+BA+CA+DA+CB+DB+DC
			
			local perlinvalue = ABCD/12
			
			if octaves ~= 0 then
				for i = 1,octaves do
					local AB = math.noise(X/(amplitude*(persistence^i)),Y/(amplitude*(persistence^i)))
					local AC = math.noise(X/(amplitude*(persistence^i)),Z/(amplitude*(persistence^i)))
					local AD = math.noise(X/(amplitude*(persistence^i)),W/(amplitude*(persistence^i)))
					local BC = math.noise(Y/(amplitude*(persistence^i)),Z/(amplitude*(persistence^i)))
					local BD = math.noise(Y/(amplitude*(persistence^i)),W/(amplitude*(persistence^i)))
					local CD = math.noise(Z/(amplitude*(persistence^i)),W/(amplitude*(persistence^i)))
					
					local BA = math.noise(Y/(amplitude*(persistence^i)),X/(amplitude*(persistence^i)))
					local CA = math.noise(Z/(amplitude*(persistence^i)),X/(amplitude*(persistence^i)))
					local DA = math.noise(W/(amplitude*(persistence^i)),X/(amplitude*(persistence^i)))
					local CB = math.noise(Z/(amplitude*(persistence^i)),Y/(amplitude*(persistence^i)))
					local DB = math.noise(W/(amplitude*(persistence^i)),Y/(amplitude*(persistence^i)))
					local DC = math.noise(W/(amplitude*(persistence^i)),Z/(amplitude*(persistence^i)))
					
					local ABCD = AB+AC+AD+BC+BD+CD+BA+CA+DA+CB+DB+DC
					
					perlinvalue = perlinvalue+((ABCD/12)/(2^i))
				end
			end
			return perlinvalue
		end
	end
end

end

return PerlinNoiseAPI
And finally, this is my server script that activates the perlin noise module:local module = require(script.PerlinNoise)

local cloudFolder = Instance.new(“Folder”)
cloudFolder.Name = “Clouds”
cloudFolder.Parent = workspace

local BOUNDARY = 32
local Y_MIN = 0
local Y_MAX = 8
local FADE_RANGE = 6
local WIND = Vector3.new(0.2, 0, 0.1)
local SPAWN_INTERVAL = 5

local function getEdgeTransparency(x, z)
local distX = math.min(x + BOUNDARY, BOUNDARY - x)
local distZ = math.min(z + BOUNDARY, BOUNDARY - z)
local edgeDist = math.min(distX, distZ)
return math.clamp(1 - (edgeDist / FADE_RANGE), 0, 1)
end

local function createPart(x, y, z)
local g = Instance.new(“Part”)
g.Parent = cloudFolder
g.Size = Vector3.new(1.5, 1.5, 1.5)
g.Position = Vector3.new(x, y, z)
g.Anchored = true
g.Material = Enum.Material.SmoothPlastic
g.Color = Color3.new(1, 1, 1)
g.CanCollide = false
g.CastShadow = false
g.Transparency = getEdgeTransparency(x, z)
return g
end

– Generate initial cloud field once
local cloudParts = {}
for x = -BOUNDARY, BOUNDARY do
for y = Y_MIN, Y_MAX do
for z = -BOUNDARY, BOUNDARY do
local noise = module.new({x, y, z, 0}, 12, 4, 0.5)
if noise > .2 then
table.insert(cloudParts, createPart(x, y, z))
end
end
end
end

local windOffsetX = 0
local windOffsetZ = 0
local frame = 0

while true do
frame += 1
windOffsetX += WIND.X
windOffsetZ += WIND.Z

for i = #cloudParts, 1, -1 do
	local part = cloudParts[i]
	if not part or not part.Parent then
		table.remove(cloudParts, i)
	else
		local newPos = part.Position + WIND
		if newPos.X > BOUNDARY or newPos.X < -BOUNDARY or newPos.Z > BOUNDARY or newPos.Z < -BOUNDARY then
			part:Destroy()
			table.remove(cloudParts, i)
		else
			part.Position = newPos
			part.Transparency = getEdgeTransparency(newPos.X, newPos.Z)
		end
	end
end


if frame % SPAWN_INTERVAL == 0 then

	for y = Y_MIN, Y_MAX do
		for z = -BOUNDARY, BOUNDARY do
			local noise = module.new({-BOUNDARY + windOffsetX, y, z + windOffsetZ, 0}, 12, 4, 0.5)
			if noise > .2 then
				table.insert(cloudParts, createPart(-BOUNDARY, y, z))
			end
		end
	end
	-- Entry at -Z edge (skip corner already covered by -X)
	for y = Y_MIN, Y_MAX do
		for x = -BOUNDARY + 1, BOUNDARY do
			local noise = module.new({x + windOffsetX, y, -BOUNDARY + windOffsetZ, 0}, 12, 4, 0.5)
			if noise > .2 then
				table.insert(cloudParts, createPart(x, y, -BOUNDARY))
			end
		end
	end
end

task.wait(0.1)

end`

the module script is a child of the server script

1 Like

you need to take Y into account, so that becomes
part.Transparency = getEdgeTransparency(newPos.X, newPos.Z) * newPos.Y / Y_MAX

also please wrap your code around
```lua
```

next time

1 Like

sorry. It’s just whenever i try to show my code it doesn’t format correctly

I think the issue might actually be with how you’re sampling the noise when spawning the new edges.

You’re physically moving all of the existing cloud parts by WIND, but then when generating the replacement parts you’re doing:

-Boundary + windOffsetX
z + windOffsetZ

That samples farther with the movement. If you’re trying to keep one continuous noise field that’s being moved by the wind, you should be sampling from the opposite direction, something more like:

local noise = module.new({
	-BOUNDARY - windOffsetX,
	y,
	z - windOffsetZ,
	0
}, 12, 4, 0.5)

and same idea for the Z edge.

Otherwise the new slices you’re spawning don’t really continue the same noise field as the parts that were already moved, which could explain why it slowly starts looking like sloped/diagonal columns.

Also, one other thing I noticed: you’re technically never using the 4D part of your module right now. Every call passes 0 as W, and your module does:

if W == 0 then

which sends it through the normal 3D math.noise(X, Y, Z) path.

So I’d fix the wind offset sampling

Nvm. Forget what i said about the lag I just wanna know to to get the effect I’ve shown in video

Heres what it looks like now btw

I have noticed several issues in your code.

First one: You are not using 4th dimension correctly.
You are doing:

if W == 0 then
    local perlinvalue = math.noise(X/amplitude,Y/amplitude,Z/amplitude)

as if W = 0 is not a valid 4D coordinate.

Afterwards, on server you do:

local noise = module.new({x, y, z, 0}, 12, 4, 0.5)

and

local noise = module.new({x + windOffsetX, y, -BOUNDARY + windOffsetZ, 0}, 12, 4, 0.5)

which means your 4th dimension is always 0. I don’t know if it is intentional for debugging purposes or not, but it is something to keep in mind. (otherwise you should use time, to have it animated)

Second one: You seem to use wind offset incorrectly
@GreenTreeGamingYT has pointed it out and proposed the fix in this message:

Third one: Your wind offset is unbounded
You always do:

windOffsetX += WIND.X
windOffsetZ += WIND.Z

Which means those 2 variables always growing/getting smaller. Roblox noise repeats every 256 units. It might be the reason for some artifacts so it is sensible to wrap the wind offset like this:

windOffsetX = (windOffsetX + WIND.X) % 256
windOffsetZ = (windOffsetZ + WIND.Z) % 256

Try those changes independently, and see if anything improves. Make sure to update us on whether it helped :slight_smile:

Right. But i’ve already fixed this issue all I need is just to get the effects just like what i’ve shown in the video at the top of this conversation

Try adding multiple noise layers together then updating their directions and update speed independently.