Perlin Noise routine
Blitz3D Forums/Blitz3D Programming/Perlin Noise routine
| ||
I have been working on this which is based on a code snippet from somewhere but I can't find the relevant source through searching. I'm pretty sure it might have been Krischan because it's similar to his planet textuiring. Anyway, it's not working right at all. Const PerlinSamples=16 Const PerlinMapSize=64 Const PerlinSharpness#=0.5 Const SamplingLevel%=4 Dim SampleArray#(PerlinSamples-1,2) Dim NoiseArray#(PerlinMapSize-1,PerlinMapSize-1,PerlinMapSize-1) Function InitialisePerlin(Seed=0,Sharpness#=PerlinSharpness) If Not(Seed) Seed=MilliSecs() End If SeedRnd Seed Local X Local Y Local Z Local f# For X = 1 To PerlinMapSize For Y = 1 To PerlinMapSize For Z = 1 To PerlinMapSize f#=Rnd(0.000,1.000) NoiseArray#(X-1,Y-1,Z-1) = f Next Next Next Local Iterate Local SubIter ; prepare octave Data For Iterate = 0 To PerlinSamples-1 SampleArray#(Iterate,0) = 2 ^ Iterate SampleArray#(Iterate,1) = Sharpness# ^ Iterate SampleArray#(Iterate,2) = 0.0 For SubIter = 0 To Iterate SampleArray#(Iterate,2)=SampleArray#(Iterate,2)+SampleArray#(SubIter,1) Next SampleArray#(Iterate,2) = 1.0 / SampleArray#(Iterate,2) Next Return Seed End Function Function Integer%(f#) If (f < 0 ) Return Int(Ceil(f)) End If Return Int(Floor(f)) End Function Function PerlinNoisePoint#(X#,Y#,Z#,Resolution#=0.5) Local Baseline# = 0 Local Octaves%=(Resolution*PerlinSamples)-1 If Octaves < 0 Then Octaves = 0 If Octaves => PerlinSamples Then Octaves = PerlinSamples-1 Local Amplitude# Local Frequency# Local XStepsize Local YStepsize Local ZStepsize Local XBetween# Local YBetween# Local ZBetween# Local XInvBetween# Local YInvBetween# Local ZInvBetween# Local Iter% For Iter = 0 To Octaves% Frequency# = SampleArray#(Iter,0) Amplitude# = SampleArray#(Iter,1) XStepsize = Integer(X * Frequency#) YStepsize = Integer(Y * Frequency#) ZStepsize = Integer(Z * Frequency#) ; get the inbetween co-ords XBetween# = EaseCurve((X * Frequency#) - XStepsize) YBetween# = EaseCurve((Y * Frequency#) - YStepsize) ZBetween# = EaseCurve((Z * Frequency#) - ZStepsize) XInvBetween# = 1 - XBetween# YInvBetween = 1 - YBetween# ZInvBetween# = 1 - ZBetween# Baseline#=Baseline#+PerlinVector(XStepsize,YStepsize,ZStepsize) * XInvBetween# * YInvBetween# * ZInvBetween# Baseline#=Baseline#+PerlinVector(XStepsize+1,YStepsize,ZStepsize) * XBetween# * YInvBetween# * ZInvBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize) * XInvBetween# * YBetween# * ZInvBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize,ZStepsize+1) * XInvBetween# * YInvBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize,ZStepsize+1) * XBetween# * YInvBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize) * XBetween# * YBetween# * ZInvBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize+1) * XBetween# * YBetween# * ZBetween# Stop Baseline=Baseline*Amplitude Next Baseline# = Baseline# * SampleArray#(Octaves,2) Return Baseline# End Function Function PerlinVector#(X,Y,Z) If X < 0 Then X = X - (Integer((X / PerlinMapSize) - 1) * PerlinMapSize) Else X = X - (Integer(X/PerlinMapSize) * PerlinMapSize) If Y < 0 Then Y = Y - (Integer((Y / PerlinMapSize) - 1) * PerlinMapSize) Else Y = Y - (Integer(Y/PerlinMapSize) * PerlinMapSize) If Z < 0 Then Z = Z - (Integer((Z / PerlinMapSize) - 1) * PerlinMapSize) Else Z = Z - (Integer(Z/PerlinMapSize) * PerlinMapSize) Return NoiseArray#(X,Y,Z) End Function Function EaseCurve#(Vector#) Return(1-(Cos(Vector#)* 180.0)) * 0.5 End Function Function GenerateNoisemap(Size=512, Sharpness#=PerlinSharpness#) Local MinimumSample =0 Local xSize# = Size Local ySize# = Size*0.5 Local xScale# = 360.0 / xSize Local yScale# = 180.0 / ySize Local pxs% = xSize-1 Local pys% = ySize-1 Local xof% = 0 Local yof% = 0 Local Image=CreateImage(xSize,ySize) Local Buffer=ImageBuffer(Image) Local posx Local posy Local xPolar# Local yPolar# Local zPolar# Local Component Local ComponentRatio# Local RGB Local ComponentMax=0 Local ComponentMin=255 Local X# Local Y# Local Z# LockBuffer Buffer ;First Pass to obtain maxima and minima For posx% = 0 To pxs Step 1 For posy% = 0 To pys Step 1 ;PolarX xPolar# = Cos((posx + xof + 0.5) * xScale#) zPolar# = Sin((posx + xof + 0.5) * xScale#) ;PolarY yPolar# = Sin(((posy + yof + 0.5) * yScale#)) * 2 ;Determine Coordinates X# = (xPolar# * yPolar#) + 5 Y# = (Cos(((posy + yof + 0.5) * yScale#)) * 2) + 5 Z# = (zPolar# * yPolar#) + 5 Component = PerlinNoisePoint(X#,Y#,Z#,Sharpness#)* 255.0 Stop ; cap the value If Component => 255 Then Component = 255 If Component<= 0 Then Component = 0 If Component<ComponentMin ComponentMin=Component End If If Component>ComponentMax ComponentMax=Component End If RGB=RGBa(Component,Component,Component) WritePixelFast posx,posy,RGB,Buffer Next Next ;Second Pass to Normalise values For posx = 0 To pxs Step 1 For posy = 0 To pys Step 1 RGB=ReadPixelFast(posx,posy,Buffer) ComponentRatio#=Float((RGB And 255)/255.0) Component=Normalise(ComponentRatio,ComponentMin,ComponentMax)*255 RGB=RGBa(Component,Component,Component) WritePixelFast posx,posy,RGB,Buffer Next Next UnlockBuffer Buffer Return Image End Function Function Normalise#(f#,Min#=0.0,Max#=1.0) Local Range#=(Max-Min) Local Normal#=(f-Min)/Range Return Normal End Function Function RGBa(R,G,B,a=255) Return (R And 255)+((G And 255) Shl 8)+((B And 255) Shl 16)+((a And 255) Shl 24) End Function Function IntToFloat#(n%) Local f#=n*1.0 Return f# End Function I think the issue is with the Baseline value from the PerlinNoisePoint function. The value returned ought to be I think in a range of 0 - 1 but seems to be increased way out of bounds. If anyone who has a better idea of the Perlin Noise concept or even otherwise that can help me out, I'd be extremely grateful! |
| ||
Too much to wade through, but this looks suspicious...; get the inbetween co-ords XBetween# = EaseCurve((X * Frequency#) - XStepsize) YBetween# = EaseCurve((Y * Frequency#) - YStepsize) ZBetween# = EaseCurve((Z * Frequency#) - ZStepsize) XInvBetween# = 1 - XBetween# YInvBetween = 1 - YBetween# ZInvBetween# = 1 - ZBetween# That gives the impression Between and InvBetween are supposed to be complementary. They should be in the range 0 to 1 and their sum should be 1. But then we have Function EaseCurve#(Vector#) Return(1-(Cos(Vector#)* 180.0)) * 0.5 End Function Note that as the angle goes from 0 to 180 degrees the Cos() will go 1 to -1. That means 1-Cos() goes 0 to 2, so that ( 1 - Cos() ) * 0.5 goes 0 to 1. That strongly suggests that Vector# is meant to be in the range 0 to 1 and the returned value should be Return( 1 - Cos( Vector# * 180.0 ) ) * 0.5 |
| ||
You're right in sod far as the complimentarity and ranges for those variables. I changed the EaseCurve function as suggested - thank you for catching that! - it definitely has helped and all seems to be within boundaries now, however there's still a problem - Baseline is now way too small, typically resulting around 0.0025 which when scaled up as a pixel colour component is only just about capable of rounding up to 1. I don't believe this is an aspect of the randomnisity of the perlin routine, because every value is too low. I think I shall get some sleep and try again in the morning - thanks for your help though! |
| ||
I had another quick look to see if anything felt wrong, despite not really understanding what is being done.Baseline#=Baseline#+PerlinVector(XStepsize,YStepsize,ZStepsize) * XInvBetween# * YInvBetween# * ZInvBetween# Baseline#=Baseline#+PerlinVector(XStepsize+1,YStepsize,ZStepsize) * XBetween# * YInvBetween# * ZInvBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize) * XInvBetween# * YBetween# * ZInvBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize,ZStepsize+1) * XInvBetween# * YInvBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize,ZStepsize+1) * XBetween# * YInvBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize) * XBetween# * YBetween# * ZInvBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween# Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize+1) * XBetween# * YBetween# * ZBetween# It's clear from the variations on Stepsize+1 that this is meant to operate on the eight corners of a unit cube. But there are nine lines of code. Lines 7 and 8 are identical. This is supposed to be a weighted average of eight values. Here's how such averages work. Consider the 1D case, two values. You have weights A,B and values v1,v2. The weights must be non-negative and add up to 1. The weighted average is A*v1 + B*v2. If both weights are 0.5 then you have an ordinary average of v1 and v2. But in any case you get a value between v1 and v2. In 2D there are four weights, call them A,B,C,D. Again they are non-negative and the sum is 1. The weighted average has the form A*v1 + B*v2 + C*v3 + D*v4. 3D has eight weights and eight values. You can guess how it works. This sort of weighted average always has non-negative weights which sum to 1. It is called a convex combination. The result is always something between the largest and smallest of the values being averaged. Your eight weights will have this property if the three X,Y,Z values are in the range 0 to 1 and the "Inv" values are the complements, i.e. XInv = 1 - X etc., which means they are also 0 to 1. |
| ||
Sorry to have wasted your time, Floyd - you really have been very helpful, but I just couldn't get it to work correctly. The code just seems too complicated in itself, despite the theory should be fairly straightforward. As such, I decided to scrap it altogether and just write my own, where at least I can follow what's going on! http://www.blitzbasic.com/codearcs/codearcs.php?code=3221 |
| ||
Hello, I've written a perlin noise routine a while back. I just put it in the code archive, it can maybe be useful : http://www.blitzbasic.com/codearcs/codearcs.php?code=3223 |