Advertisement Jump to content
Sign in to follow this  
rubicondev

Perlin Noise

This topic is 2904 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm just getting around to making a proper fist of a perlin noise class. I aim to do 1D, 2D and 3D versions and I already have code to do the necessary interpolations.

For all 3x classes, I aim to provide a larger function to return a finished value with given number of octaves etc and also a smaller function that gives just the bare result so you get to dick around with the values as you "zoom in".

One thing I've always struggled with though is what the inital sample coordinates actually mean. For example in the 2D case would it be better to map the inputs such that a range of 0-1 spans the number of samples in each dimension? A value of 2.5 would then get wrapped to 0.5 before looking up halfway through the samples. Leaving things to just wrap by casting to int and masking off with the number of samples just seems a bit arbitrary to me.

What would the panel consider the ideal behaviour for a class like this? All comments welcome.

Share this post


Link to post
Share on other sites
Advertisement

I'm just getting around to making a proper fist of a perlin noise class. I aim to do 1D, 2D and 3D versions and I already have code to do the necessary interpolations.

For all 3x classes, I aim to provide a larger function to return a finished value with given number of octaves etc and also a smaller function that gives just the bare result so you get to dick around with the values as you "zoom in".

One thing I've always struggled with though is what the inital sample coordinates actually mean. For example in the 2D case would it be better to map the inputs such that a range of 0-1 spans the number of samples in each dimension? A value of 2.5 would then get wrapped to 0.5 before looking up halfway through the samples. Leaving things to just wrap by casting to int and masking off with the number of samples just seems a bit arbitrary to me.

What would the panel consider the ideal behaviour for a class like this? All comments welcome.


I let my functions extend to infinity, then I have an adapter class which performs the mapping of a given region of the function to a buffer. I specify the region I want to map, as well as whether or not I want the noise to tile seamlessly, in the adapter class, which then will make the necessary transformations before calling the function.


Here are my mapping functions, that map multi-dimensional noise functions to a 2D buffer. Noise functions are implemented to have 2,3,4,and 6-dimensional varieties which are called as needed. As according to this blog entry of mine, I do a form of seamless mapping that uses "circles" traced through multi-dimensional noise functions, rather than blending adjacent regions of 2D noise as is typical, to do the seamless mapping, in order to avoid distortion caused by blending that muddies the image in the middle and can create very noticeable tiling artifacts.

Non-seamless case (mapx0,mapy0)-(mapx1,mapy) specify the region to map. Requires a 2D noise function:
[spoiler]


for(int x=0; x<mw; ++x)
{
for(int y=0; y<mh; ++y)
{
double s=(double)x/(double)mw;
double t=(double)y/(double)mh;

double nx=m_mapx0 + s*(m_mapx1-m_mapx0);
double ny=m_mapy0 + t*(m_mapy1-m_mapy0);
array2d->set(x,y,m_source->get(nx,ny));
}
}

[/spoiler]

Seamless in the X-Axis only: (mapx0,mapy0)-(mapx1,mapy1) specify the region to map, (loopx0,loopx1) specifies what part of the region loops. Setting loopx to mapx is usually the desired behavior. Requires a 4D noise function:
[spoiler]


for(int x=0; x<mw; ++x)
{
for(int y=0; y<mh; ++y)
{
double s=(double)x/(double)mw;
double t=(double)y/(double)mh;

s=s* ((m_mapx1-m_mapx0)/(m_loopx1-m_loopx0));

double dx=m_loopx1-m_loopx0;
double dy=m_mapy1-m_mapy0;

static double pi2=3.141592*2.0;

double nx=m_loopx0+cos(s*pi2) * dx/pi2;
double ny=m_loopx0+sin(s*pi2) * dx/pi2;
double nz=m_mapy0 + t*dy;

array2d->set(x,y,m_source->get(nx,ny,nz,m_zdepth));
}
}

[/spoiler]

Seamless in the Y-Axis only. Requires a 4D noise function:
[spoiler]


for(int x=0; x<mw; ++x)
{
for(int y=0; y<mh; ++y)
{
double s=(double)x/(double)mw;
double t=(double)y/(double)mh;

t=t* ((m_mapy1-m_mapy0)/(m_loopy1-m_loopy0));

double dx=m_mapx1-m_mapx0;
double dy=m_loopy1-m_loopy0;

static double pi2=3.141592*2.0;
double nx=m_mapx0+s*dx;
double ny=m_loopy0+cos(t*pi2) * dy/pi2;
double nz=m_loopy0+sin(t*pi2) * dy/pi2;

array2d->set(x,y,m_source->get(nx,ny,nz,m_zdepth));
}
}

[/spoiler]

Seamless in 2-dimensions, good for making, for example, textures that tile with themselves: (mapx,mapy) and (loopx,loopy) specify the ranges, again most often loop==map is the desired behavior. Requires a 4D noise function:
[spoiler]


for(int x=0; x<mw; ++x)
{
for(int y=0; y<mh; ++y)
{
double s=(double)x/(double)mw;
double t=(double)y/(double)mh;
s=s* ((m_mapx1-m_mapx0)/(m_loopx1-m_loopx0));
t=t*((m_mapy1-m_mapy0)/(m_loopy1-m_loopy0));

double dx=m_loopx1-m_loopx0;
double dy=m_loopy1-m_loopy0;

static double pi2=3.141592*2.0;
double nx=m_loopx0+cos(s*pi2) * dx/pi2;
double nz=m_loopx0+sin(s*pi2) * dx/pi2;

double ny=m_loopy0+cos(t*pi2) * dy/pi2;
double nw=m_loopy0+sin(t*pi2) * dy/pi2;

m->set(x,y,m_source->get(nx,ny,nz,nw));
}
}

[/spoiler]

Seamless in 3 dimensions (ie, can be used to generate a series of images, each of which tiles with itself, and the whole set of which forms a looping "animated" sequence; specifying (mapx,mapy,mapz) and (loopx,loopy,loopz), of course, determine the ranges from which to map and the ranges which will loop, while specifying the parameter zdepth will determine which "image" in the looping Z sequence to generate). Requires a 6D noise function:
[spoiler]


for(int x=0; x<mw; ++x)
{
for(int y=0; y<mh; ++y)
{
double s=(double)x/(double)mw;
double t=(double)y/(double)mh;
s=s* ((m_mapx1-m_mapx0)/(m_loopx1-m_loopx0));
t=t*((m_mapy1-m_mapy0)/(m_loopy1-m_loopy0));

double dx=m_loopx1-m_loopx0;
double dy=m_loopy1-m_loopy0;
double dz=m_loopz1-m_loopz0;

static double pi2=3.141592*2.0;
double nx=m_loopx0+cos(s*pi2) * dx/pi2;
double nz=m_loopx0+sin(s*pi2) * dx/pi2;

double ny=m_loopy0+cos(t*pi2) * dy/pi2;
double nw=m_loopy0+sin(t*pi2) * dy/pi2;

double nu=m_loopz0+cos(m_zdepth*pi2) * dz/pi2;
double nv=m_loopz0+sin(m_zdepth*pi2) * dz/pi2;

array2d->set(x,y,m_source->get(nx,ny,nz,nw,nu,nv));
}
}


[/spoiler]

As you can see, my approach to noise mapping is quite a bit more complicated than most approaches you will see, and of course I don't run these on the GPU. They're mostly used for non-realtime tasks; noise functions meant to run as part of the rendering process need to be far more streamlined and lightweight. In the blog post I linked above, though, I justify my reason for using these more complicated mappings. (6-Dimensional noise, oh my!) In effect, this method allows me to tile any arbitrarily complex chain of noise functions without worrying about blending altering the distribution of output values toward the center of any images I create. I can chain pattern functions, fractal noise functions, turbulence functions, etc... and as long as the functions are well-defined in all necessary dimensions, the seamless mapping tends to be quite crisp. (I have had some troubles with 6-D simplex noise; despite its popularity, simplex doesn't seem to behave all that well above 4-dimensions).

By doing noise mapping like this, I give myself the power to map the noise however I see fit. If I set the (mapx,mapy) ranges to be a sub-set of (loopx,loopy) and call the Seamless2D mapping, for example, then I can sample a number of images which when composited together in order will form the larger tiling image. In effect, this lets me "zoom-in" to the image. In the Non-Seamless variety, I just set (mapx,mapy) to whatever area of the function I want to look at; smaller ranges will "zoom in".

Share this post


Link to post
Share on other sites
Wow, that's quite a bit deeper than I was intending to go but I do see the value in it. Seeing as you were good enough to share the important bits I might stick this in as well and have a play around, thanks. The tiling stuff will definitely be handy.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!