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<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