GLSL - Using a 2D texture for 3D Perlin noise instead of procedural 3D noise

5.5k Views Asked by At

I implemented a shader for the sun surface which uses simplex noise from ashima/webgl-noise. But it costs too much GPU time, especially if I'm going to use it on mobile devices. I need to do the same effect but using a noise texture. My fragment shader is below:

#ifdef GL_ES
precision highp float;
#endif
precision mediump float;

varying vec2 v_texCoord;
varying vec3 v_normal;

uniform sampler2D u_planetDay;
uniform sampler2D u_noise; //noise texture (not used yet)
uniform float u_time;

#include simplex_noise_source from Ashima

float noise(vec3 position, int octaves, float frequency, float persistence)   {
float total = 0.0; // Total value so far
float maxAmplitude = 0.0; // Accumulates highest theoretical amplitude
float amplitude = 1.0;
for (int i = 0; i < octaves; i++) {
    // Get the noise sample
    total += ((1.0 - abs(snoise(position * frequency))) * 2.0 - 1.0) * amplitude;
    //I USE LINE BELOW FOR 2D NOISE
    total += ((1.0 - abs(snoise(position.xy * frequency))) * 2.0 - 1.0) * amplitude;
    // Make the wavelength twice as small
    frequency *= 2.0;
    // Add to our maximum possible amplitude
    maxAmplitude += amplitude;
    // Reduce amplitude according to persistence for the next octave
    amplitude *= persistence;
}
// Scale the result by the maximum amplitude
return total / maxAmplitude;
}

void main()
{   
    vec3 position = v_normal *2.5+ vec3(u_time, u_time, u_time);   
    float n1 = noise(position.xyz, 2, 7.7, 0.75) * 0.001;

    vec3 ground = texture2D(u_planetDay, v_texCoord+n1).rgb;
    gl_FragColor = vec4 (color, 1.0);
 }

How can I correct this shader to work with a noise texture and what should the texture look like?

As far as I know, OpenGL ES 2.0 doesn't support 3D textures. Moreover, I don't know how to create 3D texture.

2

There are 2 best solutions below

0
On

Trying to evaluate noise at run-time is often a bad practice unless you want to do some research work or to quickly check / debug your noise function (or see what your noise parameters visually look like).

It will always consume too much processing budget (not worth it at all), so just forget about evaluating noise at run-time.

If you store your noise results off-line, you will reduce the charge (by say over 95 %) to a simple access to memory.

I suggest to reduce all this to a texture look-up over a pre-baked 2D noise image. You are so far only impacting the fragment pipeline so a 2D noise texture is definitely the way to go (you can also use this 2D lookup for vertex positions deformation).

In order to map it on a sphere without any continuity issue, you may generate a loopable 2D image with a 4D noise, feeding the function with the coordinates of two 2D circles.

As for animating it, there are various hackish tricks either by deforming your lookup results with the time semantic in the fragment pipeline, or baking an image sequence in case you really need noise "animated with noise".

3D textures are just stacks of 2D textures, so they are too heavy to manipulate (even without animation) for what you want to do, and since you apparently need only a decent sun surface, it would be overkill.

8
On

I wrote this 3D noise from a 2D texture function. It still uses hardware interpolation for x/y directions and then manually interpolates for z. To get noise along the z direction I've sampled the same texture at different offsets. This will probably lead to some repetition, but I haven't noticed any in my application and my guess is using primes helps.

The thing that had me stumped for a while on shadertoy.com was that texture mipmapping was enabled which caused seams at the change in value of the floor() function. A quick solution was passing a -999 bias to texture2D.

This was hard coded for a 256x256 noise texture, so adjust accordingly.

float noise3D(vec3 p)
{
    p.z = fract(p.z)*256.0;
    float iz = floor(p.z);
    float fz = fract(p.z);
    vec2 a_off = vec2(23.0, 29.0)*(iz)/256.0;
    vec2 b_off = vec2(23.0, 29.0)*(iz+1.0)/256.0;
    float a = texture2D(iChannel0, p.xy + a_off, -999.0).r;
    float b = texture2D(iChannel0, p.xy + b_off, -999.0).r;
    return mix(a, b, fz);
}

Update: To extend to perlin noise, sum samples at different frequencies:

float perlinNoise3D(vec3 p)
{
    float x = 0.0;
    for (float i = 0.0; i < 6.0; i += 1.0)
        x += noise3D(p * pow(2.0, i)) * pow(0.5, i);
    return x;
}