Random Alpha Map generation for terrain


#1

Hi

I want to randomly generate Alpha map for terrain texturing.

@jayfella in your fast noise library for JME

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


#2

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


#3

Something like this:

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

for random texture splatting on terrain material.


#4

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.


#5

Thanks. :slightly_smiling_face:


#6

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.


#7

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


#8

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


#9

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.


#10

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.


#11

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.


#12

This is wrong. It should be

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

#13

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


#14

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

#15

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.


#16

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

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