GPU Improved Perlin Noise

Hello,
I am sure many people have done this before with jMonkey, but I have not seen any implementations. This is simply perlin noise running in a pixel shader using precomputed look up tables. These tables are created using the ImageRaster class. I just thought this would be helpful to some people as it would have been helpful to me, enjoy.

Java:

[java]

ackage t;

import java.nio.ByteBuffer;
import java.util.Random;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapAxis;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ImageRaster;
import com.jme3.util.BufferUtils;

public class gpuNoise extends SimpleApplication{

static int[] permutation;
private Material mat;
static float[][] grads = new float[][]{
{1,1,0},
{-1,1,0},
{1,-1,0},
{-1,-1,0},
{1,0,1},
{-1,0,1},
{1,0,-1},
{-1,0,-1},
{0,1,1},
{0,-1,1},
{0,1,-1},
{0,-1,-1},
{1,1,0},
{0,-1,1},
{-1,1,0},
{0,-1,-1}
};
public static void main(String[] args){
gpuNoise app = new gpuNoise();
app.setDisplayStatView(false);
app.setDisplayFps(false);
app.start();
}

public void simpleInitApp(){
	ByteBuffer data = BufferUtils.createByteBuffer( (int)Math.ceil(Format.RGBA16F.getBitsPerPixel() / 8.0) * 256 * 256);
	Image permImage = new Image();
	permImage.setWidth(256);
	permImage.setHeight(256);
	permImage.setFormat(Format.RGBA16F);
	permImage.setData(data);
	
	ByteBuffer data2 = BufferUtils.createByteBuffer( (int)Math.ceil(Format.RGBA16F.getBitsPerPixel() / 8.0) * 256 * 1);
	Image gradImage = new Image();
	gradImage.setWidth(256);
	gradImage.setHeight(1);
	gradImage.setFormat(Format.RGBA16F);
	gradImage.setData(data2);
	
long seed = new Random().nextLong();

	Random r = new Random(seed);

// Compute permutation table

	 permutation = new int[256];
	for(int i = 0; i<permutation.length; i++){
		permutation[i] = -1;
	}

// Random Numbers 0-255
for(int i = 0; i< permutation.length; i++){
while(true){
int per = Math.abs(r.nextInt()) % permutation.length;
if(permutation[per] == -1){
permutation[per] = i;
break;
}
}
}
// fills permutation texture…generates 4 hash values here to save GPU processing power, originally this was a 1D table
ImageRaster rP = ImageRaster.create(permImage);
for(int x = 0; x < 256; x++){
for(int y = 0; y < 256; y++){
int A = perm2d(x) + y;
int AA = perm2d(A);
int AB = perm2d(A + 1);
int B = perm2d(x + 1) + y;
int BA = perm2d(B);
int BB = perm2d(B + 1);
ColorRGBA c = new ColorRGBA((float)AA/255f, (float)AB/255f, (float)BA/255f, (float)BB/255f);
rP.setPixel(x, y, c);

		}
	} 

//Creates table of gradients
//The gradients are rearranged based on the permutation table here instead of on the GPU
ImageRaster rG = ImageRaster.create(gradImage);
for(int x = 0; x<256; x++){
for(int y = 0; y < 1; y++){

			ColorRGBA c = new ColorRGBA(grads[permutation[x]%16][0], 
			grads[permutation[x] % 16][1], 
			grads[permutation[x] % 16][2], 1);
			rG.setPixel(x, y, c);
		}
	} 
	
	Texture2D permutationTable = new Texture2D(permImage);
	permutationTable.setWrap(WrapMode.Repeat);
	permutationTable.setMagFilter(MagFilter.Nearest);
	permutationTable.setMinFilter(MinFilter.NearestNoMipMaps);
	
	Texture2D gradientTable = new Texture2D(gradImage);
	gradientTable.setWrap(WrapAxis.S, WrapMode.Repeat);
	gradientTable.setWrap(WrapAxis.T, WrapMode.Clamp);
	gradientTable.setMagFilter(MagFilter.Nearest);
	gradientTable.setMinFilter(MinFilter.NearestNoMipMaps);
	
	Quad q = new Quad(2,2);
	cam.resize(256, 256, true);
	mat = new Material(assetManager, "noiseMaterial.j3md"); 
	Geometry ge = new Geometry("Mesh",q);
	ge.setMaterial(mat);
	mat.setTexture("permutationTexture", permutationTable);
	mat.setTexture("gradientTexture", gradientTable);
	rootNode.attachChild(ge);
}

public int perm2d(int i){
	return permutation[i % 256];
}		

}

[/java]

Vertex Shader:
[java]
attribute vec4 inPosition;

varying vec4 pos;
void main(void){

pos = vec4(inPosition);
pos.x += -1.0;
pos.y += -1.0;

gl_Position = pos;
}
[/java]

Pixel Shader:

[java]

uniform sampler2D m_permutationTexture;
uniform sampler2D m_gradientTexture;

varying vec4 pos;

vec3 fade(vec3 t)
{
return (t * t * t * (t * (t * 6.0 - 15.0) + 10.0));
}
vec4 permute(vec2 p)
{
return texture2D(m_permutationTexture, p);
}
float permuteGrad(float x, vec3 p)
{
return dot(vec3(texture2D(m_gradientTexture, vec2(x,0.0)).xyz), p);
}

float noise(vec3 p)
{
vec3 P = mod(floor§, 256.0);
p -= floor§;
vec3 f = fade§;

P = P / 256.0;
const float one = 1.0 / 256.0;
  
vec4 AA = permute(P.xy) + P.z;

return mix( mix( mix( permuteGrad(AA.x, p ),  
                         permuteGrad(AA.z, p + vec3(-1.0, 0.0, 0.0) ), f.x),
                   mix( permuteGrad(AA.y, p + vec3(0.0, -1.0, 0.0) ),
                         permuteGrad(AA.w, p + vec3(-1.0, -1.0, 0.0) ), f.x), f.y),
                         
             mix( mix( permuteGrad(AA.x+one, p + vec3(0.0, 0.0, -1.0) ),
                         permuteGrad(AA.z+one, p + vec3(-1.0, 0.0, -1.0) ), f.x),
                   mix( permuteGrad(AA.y+one, p + vec3(0.0, -1.0, -1.0) ),
                         permuteGrad(AA.w+one, p + vec3(-1.0, -1.0, -1.0) ), f.x), f.y), f.z);

}

float fbm(in vec3 v){

	  int octaves = 12;
	  float frequency = 0.45;
	  float persistence = 0.6;
      float lacunarity = 2.0;
      float sum = 0.0;
      float noise = 0.0;
      float pers = 1.0;
      v *= frequency;
      

      for (int i = 0; i&lt;octaves; i++)
      {
         noise = noise(v);
         sum += noise * pers;
         v *= lacunarity;
         pers *= persistence;
	}
	return  sum;

}

void main(void){
float n = (fbm(vec3(pos.xyz * 4.0))+1.0) * 0.5;
gl_FragColor = vec4(n,n,n,1.0);
}
[/java]

For reference here is the article this was based on: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter26.html

6 Likes

EDIT: Using gl_FragCoord.xyz/(texture or viewport size) as input rather than the position vector eliminates grid-like artifacts that are a natural result of gradient noise.