Random Alpha Map generation for terrain

Hi

I want to randomly generate Alpha map for terrain texturing.

@jayfella in your fast noise library for JME
https://github.com/jayfella/jme-fastnoise

should it be possible using GradientPerturb with fractal for generating texture? Do you have probably an example code for it?

Yeah I don’t see why not. What do you want it for? I just need to see it in my head first.

Something like this:

https://camo.githubusercontent.com/911deaa128a82b3f7d743cd73f5ef0b7e8adb08f/687474703a2f2f692e696d6775722e636f6d2f674f6a633175312e706e67

for random texture splatting on terrain material.

Yes. And I actually duplicated that GUI, too, so I could “see” if the results were the same. Let me see where I put it and I’ll publish it so you can do the same.

1 Like

Thanks. :slightly_smiling_face:

Ok. So there was a bit more thought involved in this than I anticipated. I got a result, but it comes with an explanation:

The algorithm in that application normalizes the whole image - it gets the lowest value and the highest value and uses it as a scale for min-max (so if the colors only ever get to 0.8f - that will be the new 1.0 - and so everything becomes “lighter”). In my image you see a lot of darker areas, and in your reference image it’s a lot lighter - and it’s because of that. But actually you don’t want this with procedural generated worlds because it won’t join seamlessly. If my co-ordinates were 20 pixels to the right, the whole image might be slightly lighter or darker. The max brightness of a color might only be 0.7 now or even 0.9 and so the whole image will “shift” a bit lighter or darker.

You can toy with each layers scale, frequency, etc… to play around with which texture will be displayed more or less frequently.

I also had to update the fastnoise library, so you’ll need to reference version 1.0.2 now (its already on jcenter).

Anyway, here’s my result.

package com.jayfella.jme.fastnoise.debug;

import com.jayfella.fastnoise.FastNoise;
import com.jayfella.fastnoise.GradientPerturb;
import com.jayfella.fastnoise.LayeredNoise;
import com.jayfella.fastnoise.NoiseLayer;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.material.Materials;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ImageRaster;
import com.jme3.util.BufferUtils;

public class Main extends SimpleApplication {

    private LayeredNoise layeredNoise;

    private int sizeX, sizeY;
    private ImageRaster textureRaster;

    public static void main(String... args) {

        Main main = new Main();

        AppSettings appSettings = new AppSettings(true);
        appSettings.setFrameRate(120);
        appSettings.setResolution(1280, 720);

        main.setSettings(appSettings);
        main.start();
    }

    @Override
    public void simpleInitApp() {

        sizeX = cam.getWidth();
        sizeY = cam.getHeight();

        flyCam.setMoveSpeed(100);
        flyCam.setDragToRotate(true);

        Image image = new Image();
        image.setWidth(sizeX);
        image.setHeight(sizeY);
        image.setFormat(Image.Format.BGRA8);
        image.setData(BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4));
        textureRaster = ImageRaster.create(image);

        Texture2D texture = new Texture2D(image);

        Geometry textureGeom = new Geometry("Noise", new Quad(sizeX, sizeY));
        textureGeom.setMaterial(new Material(assetManager, Materials.UNSHADED));
        textureGeom.getMaterial().setTexture("ColorMap", texture);


        initNoise();
        generateTexture();
        guiNode.attachChild(textureGeom);
    }

    private NoiseLayer createLayer() {
        NoiseLayer layer = new NoiseLayer("Layer");
        layer.setFrequency(0.02f);
        layer.setInterp(FastNoise.Interp.Quintic);
        layer.setFractalOctaves(5);
        layer.setFractalLacunarity(1.7f);
        layer.setFractalGain(0.6f);
        layer.setSeed(FastMath.nextRandomInt());
        layer.setNoiseType(FastNoise.NoiseType.PerlinFractal);
        //layer.setGradientPerturb(GradientPerturb.Fractal);
        layer.setFractalType(FastNoise.FractalType.FBM);
        layer.setGradientPerturbAmp(20);

        return layer;
    }

    private void initNoise() {
        layeredNoise = new LayeredNoise();

        NoiseLayer red = createLayer();
        layeredNoise.addLayer(red);

        NoiseLayer green = createLayer();
        layeredNoise.addLayer(green);

        NoiseLayer blue = createLayer();
        layeredNoise.addLayer(blue);

    }

    float noiseScale = 0.75f;

    private void generateTexture() {

        for (int x = 0; x < sizeX; x++) {
            for (int y = 0; y < sizeY; y++) {

                Vector3f f = new Vector3f(x * noiseScale, y * noiseScale, 0.5f);

                f.x -= x;
                f.y -= y;
                f.z -= 0.5f;

                layeredNoise.getLayers().get(0).getPrimaryNoise().GradientPerturbFractal(f);
                layeredNoise.getLayers().get(1).getPrimaryNoise().GradientPerturbFractal(f);
                layeredNoise.getLayers().get(2).getPrimaryNoise().GradientPerturbFractal(f);

                // toy with this to increase the brightness.
                float scale = 1.0f;

                float r = Math.max(0, Math.min(1, layeredNoise.getLayers().get(0).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale));
                float g = Math.max(0, Math.min(1, layeredNoise.getLayers().get(1).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale));
                float b = Math.max(0, Math.min(1, layeredNoise.getLayers().get(2).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale));

                ColorRGBA color = new ColorRGBA(r, g, b, 1.0f);
                textureRaster.setPixel(x, y, color);

            }
        }

    }

}

Edit: I noticed a bug. I’ve updated the code here.

3 Likes

I updated the code to fix a bug. Looks nicer, I think.

1 Like

Thank you so much. Will try this soon. :slightly_smiling_face:

1 Like

I occurred to me that you could just clamp those rgb noise values to -1 and 1 and get the abs value to eliminate a lot of the black areas. It might yield a more similar look.

1 Like

As I suspected, it does get rid of the black, but it looks more like an art teachers overcoat than a random texture map.

float r = FastMath.clamp(layeredNoise.getLayers().get(0).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);
float g = FastMath.clamp(layeredNoise.getLayers().get(1).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);
float b = FastMath.clamp(layeredNoise.getLayers().get(2).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);

r = FastMath.abs(r);
g = FastMath.abs(g);
b = FastMath.abs(b);

For “biomes” and stuff like that you usually use this kind of nose. Stretching one along the X axis and another along the Y axis and either adding or multiplying them works really well.

Anyway. I’ve let my brain wander for far too long.

2 Likes

Toying with different values I was able to make some nice looking alpha maps,

I have not tested them yet on actual terrain to see how the result will look like, will do it soon. I hope it looks nice on the terrain. :slightly_smiling_face:

Mostly this is going to be used for blending grass + dirt on ground.

2 Likes

This is wrong. It should be

f.x -= x * noiseScale;
f.y -= y * noiseScale;

Not sure understanding this, but this will make the resulting vector always (0,0,0).

Vector3f f = new Vector3f(x * noiseScale, y * noiseScale, 0.5f);

f.x -= x * noiseScale;
f.y -= y * noiseScale;
f.z -= 0.5f;

or you mean like this?

 Vector3f f = new Vector3f(x , y , 0);

 f.x -= x * noiseScale;
 f.y -= y * noiseScale;

Edit:

What is this minus for ???

Also tried to switch FastMath.clam with FastMath.normalize and I got different results.

Normalize in [-1,1] :

            float r = FastMath.normalize(layeredNoise.getLayers().get(0).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);
            float g = FastMath.normalize(layeredNoise.getLayers().get(1).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);
            float b = FastMath.normalize(layeredNoise.getLayers().get(2).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);

            r = FastMath.abs(r);
            g = FastMath.abs(g);
            b = FastMath.abs(b);

Normalize in [0, 1]:

                float r = FastMath.normalize(layeredNoise.getLayers().get(0).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, 0, 1);
                float g = FastMath.normalize(layeredNoise.getLayers().get(1).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, 0, 1);
                float b = FastMath.normalize(layeredNoise.getLayers().get(2).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, 0, 1);

No, the vector gets “moved around” in these lines before.

layeredNoise.getLayers().get(0).getPrimaryNoise().GradientPerturbFractal(f);
layeredNoise.getLayers().get(1).getPrimaryNoise().GradientPerturbFractal(f);
layeredNoise.getLayers().get(2).getPrimaryNoise().GradientPerturbFractal(f);

I just tried it and it looks much more like the original image you posted. For clarity I posted the code that creates this image.

package com.jayfella.jme.fastnoise.debug;

import com.jayfella.fastnoise.FastNoise;
import com.jayfella.fastnoise.GradientPerturb;
import com.jayfella.fastnoise.LayeredNoise;
import com.jayfella.fastnoise.NoiseLayer;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.material.Materials;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ImageRaster;
import com.jme3.util.BufferUtils;

public class Main extends SimpleApplication {

    private LayeredNoise layeredNoise;

    private int sizeX, sizeY;
    private ImageRaster textureRaster;

    public static void main(String... args) {

        Main main = new Main();

        AppSettings appSettings = new AppSettings(true);
        appSettings.setFrameRate(120);
        appSettings.setResolution(1280, 720);

        main.setSettings(appSettings);
        main.start();
    }

    @Override
    public void simpleInitApp() {

        sizeX = cam.getWidth();
        sizeY = cam.getHeight();

        flyCam.setMoveSpeed(100);
        flyCam.setDragToRotate(true);

        Image image = new Image();
        image.setWidth(sizeX);
        image.setHeight(sizeY);
        image.setFormat(Image.Format.BGRA8);
        image.setData(BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4));
        textureRaster = ImageRaster.create(image);

        Texture2D texture = new Texture2D(image);

        Geometry textureGeom = new Geometry("Noise", new Quad(sizeX, sizeY));
        textureGeom.setMaterial(new Material(assetManager, Materials.UNSHADED));
        textureGeom.getMaterial().setTexture("ColorMap", texture);


        initNoise();
        generateTexture();
        guiNode.attachChild(textureGeom);
    }

    private NoiseLayer createLayer() {
        NoiseLayer layer = new NoiseLayer("Layer");
        layer.setFrequency(0.02f);
        layer.setInterp(FastNoise.Interp.Quintic);
        layer.setFractalOctaves(5);
        layer.setFractalLacunarity(1.7f);
        layer.setFractalGain(0.6f);
        layer.setSeed(FastMath.nextRandomInt());
        //layer.setNoiseType(FastNoise.NoiseType.PerlinFractal);
        layer.setGradientPerturb(GradientPerturb.Fractal);
        layer.setFractalType(FastNoise.FractalType.FBM);
        layer.setGradientPerturbAmp(30);

        return layer;
    }

    private void initNoise() {
        layeredNoise = new LayeredNoise();

        NoiseLayer red = createLayer();
        layeredNoise.addLayer(red);

        NoiseLayer green = createLayer();
        layeredNoise.addLayer(green);

        NoiseLayer blue = createLayer();
        layeredNoise.addLayer(blue);

    }

    float noiseScale = 0.25f;

    private void generateTexture() {

        for (int x = 0; x < sizeX; x++) {
            for (int y = 0; y < sizeY; y++) {

                Vector3f f = new Vector3f(x * noiseScale, y * noiseScale, 0.5f);

                layeredNoise.getLayers().get(0).getPrimaryNoise().GradientPerturbFractal(f);
                layeredNoise.getLayers().get(1).getPrimaryNoise().GradientPerturbFractal(f);
                layeredNoise.getLayers().get(2).getPrimaryNoise().GradientPerturbFractal(f);

                float scale = 1.0f;

                f.x -= x * noiseScale;
                f.y -= y * noiseScale;
                f.z -= 0.5f;

                float r = FastMath.clamp(layeredNoise.getLayers().get(0).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);
                float g = FastMath.clamp(layeredNoise.getLayers().get(1).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);
                float b = FastMath.clamp(layeredNoise.getLayers().get(2).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, -1, 1);

                r = FastMath.abs(r);
                g = FastMath.abs(g);
                b = FastMath.abs(b);

                ColorRGBA color = new ColorRGBA(r, g, b, 1.0f);
                textureRaster.setPixel(x, y, color);

            }
        }

    }

}

Regarding normalizing, the original algorithm iterates over the entire image and keeps track of the minimum and maximum value and then uses that as the 0-1 range, so the whole image color range gets “stretched”. Its not the same as just normalizing each value.

1 Like

Oops! Sorry :man_facepalming:
I was setting it before calling to

            layeredNoise.getLayers().get(0).getPrimaryNoise().GradientPerturbFractal(f);
            layeredNoise.getLayers().get(1).getPrimaryNoise().GradientPerturbFractal(f);
            layeredNoise.getLayers().get(2).getPrimaryNoise().GradientPerturbFractal(f);

Updated it based on the new code and it works just fine now, thanks.

Yes, aware of it.
I just wanted to see how it will look like. :slightly_smiling_face:

for example this image is generated with

layer.setFractalLacunarity(1.2f);

and

            float r = FastMath.normalize(layeredNoise.getLayers().get(0).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, 0, 1);
            float g = FastMath.normalize(layeredNoise.getLayers().get(1).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, 0, 1);
            float b = FastMath.normalize(layeredNoise.getLayers().get(2).getPrimaryNoise().GetNoise(f.x, f.y, f.z) * scale, 0, 1);

https://i.imgur.com/XnKDh4r.png

have not tried it on a terrain yet. Will do it soon.

Glad I found this thread! I am using fastnoise in terranova to ‘add’ noise to generated heightmaps or alphamaps. In the terranova editor alpha map can be generated using actual terrain heights, slopes, and last step is to blend in some noise to make the alpha map more varied. I was looking for some different examples using LayeredNoise since I need to decide how to make it all configurable in the UI. This thread is very helpful!

Below is an example with and without noise added to an alphamap generated from terrain heights :grinning:

2 Likes