user3541787
user3541787

Reputation: 53

Converting 2D Noise to 3D

I've recently started experimenting with noise (simple perlin noise), and have run into a slight problem with animating it. So far come I've across an awesome looking 3d noise (https://github.com/ashima/webgl-noise) that I could use in my project but that I understood nothing of, and a bunch of tutorials that explain how to create simple 2d noise.

For the 2d noise, I originally used the following fragment shader:

uniform sampler2D al_tex;
varying vec4 varying_pos; //Actual coords
varying vec2 varying_texcoord; //Normalized coords

uniform float time;

float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); }

float ease(float p) { return 3*p*p - 2*p*p*p; }

float cnoise(vec2 p, int wavelength)
{
    int ix1 = (int(varying_pos.x) / wavelength) * wavelength;
    int iy1 = (int(varying_pos.y) / wavelength) * wavelength;
    int ix2 = (int(varying_pos.x) / wavelength) * wavelength + wavelength;
    int iy2 = (int(varying_pos.y) / wavelength) * wavelength + wavelength;

    float x1 = ix1 / 1280.0f;
    float y1 = iy1 / 720.0f;
    float x2 = ix2 / 1280.0f;
    float y2 = iy2 / 720.0f;

    float xOffset = (varying_pos.x - ix1) / wavelength;
    float yOffset = (varying_pos.y - iy1) / wavelength;

    xOffset = ease(xOffset);
    yOffset = ease(yOffset);

    float t1 = rand(vec2(x1, y1));
    float t2 = rand(vec2(x2, y1));
    float t3 = rand(vec2(x2, y2));
    float t4 = rand(vec2(x1, y2));

    float tt1 = mix(t1, t2, xOffset);
    float tt2 = mix(t4, t3, xOffset);

    return mix(tt1, tt2, yOffset);
}

void main()
{
    float t = 0;

    int minFreq = 0;
    int noIterations = 8;

    for (int i = 0; i < noIterations; i++)
        t += cnoise(varying_texcoord, int(pow(2, i + minFreq))) / pow(2, noIterations - i);

    gl_FragColor = vec4(vec3(t), 1);
}

The result that I got was this: Sample result of shader

Now, I want to animate it with time. My first thought was to change the rand function to take a vec3 instead of vec2, and then change my cnoise function accordingly, to interpolate values in the z direction too. With that goal in mind, I made this:

sampler2D al_tex;
varying vec4 varying_pos;
varying vec2 varying_texcoord;

uniform float time;

float rand(vec3 co) { return fract(sin(dot(co, vec3(12.9898, 78.2332, 58.5065))) * 43758.5453); }

float ease(float p) { return 3*p*p - 2*p*p*p; }

float cnoise(vec3 pos, int wavelength)
{
    ivec3 iPos1 = (ivec3(pos) / wavelength) * wavelength; //The first value that I'll sample to interpolate
    ivec3 iPos2 = iPos1 + wavelength; //The second value

    vec3 transPercent = (pos - iPos1) / wavelength; //Transition percent - A float in [0-1) indicating how much of each of the above values will contribute to final result
    transPercent.x = ease(transPercent.x);
    transPercent.y = ease(transPercent.y);
    transPercent.z = ease(transPercent.z);

    float t1 = rand(vec3(iPos1.x, iPos1.y, iPos1.z));
    float t2 = rand(vec3(iPos2.x, iPos1.y, iPos1.z));
    float t3 = rand(vec3(iPos2.x, iPos2.y, iPos1.z));
    float t4 = rand(vec3(iPos1.x, iPos2.y, iPos1.z));
    float t5 = rand(vec3(iPos1.x, iPos1.y, iPos2.z));
    float t6 = rand(vec3(iPos2.x, iPos1.y, iPos2.z));
    float t7 = rand(vec3(iPos2.x, iPos2.y, iPos2.z));
    float t8 = rand(vec3(iPos1.x, iPos2.y, iPos2.z));

    float tt1 = mix(t1, t2, transPercent.x);
    float tt2 = mix(t4, t3, transPercent.x);
    float tt3 = mix(t5, t6, transPercent.x);
    float tt4 = mix(t8, t7, transPercent.x);

    float tt5 = mix(tt1, tt2, transPercent.y);
    float tt6 = mix(tt3, tt4, transPercent.y);

    return mix(tt5, tt6, transPercent.z);
}

float fbm(vec3 p)
{
    float t = 0;

    int noIterations = 8;

    for (int i = 0; i < noIterations; i++)
        t += cnoise(p, int(pow(2, i))) / pow(2, noIterations - i);

    return t;
}

void main()
{
    vec3 p = vec3(varying_pos.xy, time);

    float t = fbm(p);

    gl_FragColor = vec4(vec3(t), 1);
}

However, on doing this, the animation feels... strange. It's as though I'm watching a slideshow of perlin noise slides, with the individual slides fading in. All other perlin noise examples that I have tried (like https://github.com/ashima/webgl-noise) are actually animated with time - you can actually see it being animated, and don't just feel like the images are fading in, and not being actually animated. I know that I could just use the webgl-noise shader, but I want to make one for myself, and for some reason, I'm failing miserably. Could anyone tell me where I am going wrong, or suggest me on how I can actually animate it properly with time?

Upvotes: 2

Views: 1463

Answers (2)

bandybabboon
bandybabboon

Reputation: 2346

check this code. it's a simple version of 3d noise:

// Here are some easy to understand noise gens... the D line in cubic interpolation (rounding) 


function rndng ( n: float ): float
{//random proportion -1, 1   ... many people use Sin to take 
 //linearity out of a pseudo random, exp n*n is faster on central processor.
    var e = ( n *321.9234)%1;
    return  (e*e*111.07546)%2-1;
}

function lerps(o:float, v:float, alpha:float):float
{
    o += ( v - o ) * alpha;
    return o;
}

 //3d ----------------
function lnz ( vtx: Vector3 ): float //3d perlin noise code fast
{  
    vtx= Vector3 ( Mathf.Abs(vtx.x) , Mathf.Abs(vtx.y) , Mathf.Abs(vtx.z) ) ;
    var I = Vector3 (Mathf.Floor(vtx.x),Mathf.Floor(vtx.y),Mathf.Floor(vtx.z));
    var D = Vector3(vtx.x%1,vtx.y%1,vtx.z%1);
    D = Vector3(D.x*D.x*(3.0-2.0*D.x),D.y*D.y*(3.0-2.0*D.y),D.z*D.z*(3.0-2.0*D.z));
    var W = I.x + I.y*71.0 + 125.0*I.z;

    return lerps(          
                lerps( lerps(rndng(W+0.0),rndng(W+1.0),D.x) , lerps(rndng(W+71.0),rndng(W+72.0),D.x) , D.y)
                ,
                lerps( lerps(rndng(W+125.0),rndng(W+126.0),D.x) , lerps(rndng(W+153.0),rndng(W+154.0),D.x) , D.y)
                ,
                D.z
                );
}

 //1d ----------------
function lnzo ( vtx: Vector3 ): float //perlin noise, same as unityfunction version
{
    var total = 0.0;  
    for (var i:int = 1; i < 5; i ++)
    {
        total+= lnz2(Vector3 (vtx.x*(i*i),0.0,vtx.z*(i*i)))/(i*i);
    }

    return total*5;

}

 //2d 3 axis  honeycombe noise ----------------
function lnzh ( vtx: Vector3 ): float // perlin noise, 2d, with 3 axes at 60'instead of 2 x y axes
{  
    vtx= Vector3 ( Mathf.Abs(vtx.z) , Mathf.Abs(vtx.z*.5-vtx.x*.866) , Mathf.Abs(vtx.z*.5+vtx.x*.866) ) ;
    var I = Vector3 (Mathf.Floor(vtx.x),Mathf.Floor(vtx.y),Mathf.Floor(vtx.z));
    var D = Vector3(vtx.x%1,vtx.y%1,vtx.z%1);
   //D = Vector3(D.x*D.x*(3.0-2.0*D.x),D.y*D.y*(3.0-2.0*D.y),D.z*D.z*(3.0-2.0*D.z));
    var W = I.x + I.y*71.0 + 125.0*I.z;

    return lerps(          
                lerps( lerps(rndng(W+0.0),rndng(W+1.0),D.x) , lerps(rndng(W+71.0),rndng(W+72.0),D.x) , D.y)
                ,
                lerps( lerps(rndng(W+125.0),rndng(W+126.0),D.x) , lerps(rndng(W+153.0),rndng(W+154.0),D.x) , D.y)
                ,
                D.z
                );
}

 //2d ----------------
function lnz2 ( vtx: Vector3 ): float // i think this is 2d perlin noise
{  
    vtx= Vector3 ( Mathf.Abs(vtx.x) , Mathf.Abs(vtx.y) , Mathf.Abs(vtx.z) ) ;
    var I = Vector3 (Mathf.Floor(vtx.x),Mathf.Floor(vtx.y),Mathf.Floor(vtx.z));
    var D = Vector3(vtx.x%1,vtx.y%1,vtx.z%1);
    D = Vector3(D.x*D.x*(3.0-2.0*D.x),D.y*D.y*(3.0-2.0*D.y),D.z*D.z*(3.0-2.0*D.z));
    var W = I.x + I.y*71.0 + 125.0*I.z;

    return lerps(      
                lerps( lerps(rndng(W+0.0),rndng(W+1.0),D.x) , lerps(rndng(W+71.0),rndng(W+72.0),D.x) , D.z)
                ,
                lerps( rndng(W+125.0), rndng(W+126.0),D.x)
                ,
                D.z
                );                  
}

Upvotes: 0

bofjas
bofjas

Reputation: 1206

You should proably include z in the sin function:

float rand(vec3 co) { return fract(sin(dot(co.xy ,vec2(12.9898,78.233)) + co.z) * 43758.5453); }

Apparently the somewhat random numbers are prime numbers. This is to avoid patterns in the noise. I found another prime number, 94418953, and included that in the sin/dot function. Try this:

float rand(vec3 co) { return fract(sin(dot(co.xyz ,vec3(12.9898,78.233, 9441.8953))) * 43758.5453); }

EDIT: You don't take into account wavelength on the z axis. This means that all your iterations will have the same interpolation distance. In other words, you will get the fade effect you're describing. Try calculating z the same way you calculate x and y:

int iz1 = (int(p.z) / wavelength) * wavelength;
int iz2 = (int(p.z) / wavelength) * wavelength + wavelength;

float z1 = iz1 / 720.0f;
float z2 = iz2 / 720.0f;

float zOffset = (varying_pos.z - iz1) / wavelength;

This means however that the z value will variate the same rate that y will. So if you want it to scale from 0 to 1 then you should proably multiply z with 720 before passing it into the noise function.

Upvotes: 1

Related Questions