How can I make the material edges smoother on a PBR terrain?

I’m using HillHeightMap to generate a heightmap. I found a way to generate an alphamap based on a heightmap.

This is how I create the material

/** Add texture into the red layer  */
            val red: Texture = assetManager.loadTexture(redTexturePath)
            red.setWrap(WrapMode.Repeat)
            material.setTexture("AlbedoMap_0", red)
            material.setTexture(
                "NormalMap_0",
                assetManager.loadTexture(redTextureNormalPath)
            )
            material.setFloat("AlbedoMap_0_scale", 64f)

            /** Add texture into the green layer */
            val green: Texture = assetManager.loadTexture(greenTexturePath)
            green.setWrap(WrapMode.Repeat)
            material.setTexture("AlbedoMap_1", green)
            material.setTexture(
                "NormalMap_1",
                assetManager.loadTexture(greenTextureNormalPath)
            )
            material.setFloat("AlbedoMap_1_scale", 32f)

            /** Add texture into the blue layer */
            val blue: Texture = assetManager.loadTexture(blueTexturePath)
            blue.setWrap(WrapMode.Repeat)
            material.setTexture("AlbedoMap_2", blue)
            material.setTexture(
                "NormalMap_2",
                assetManager.loadTexture(blueTextureNormalPath)
            )
            material.setFloat("AlbedoMap_2_scale", 128f)
            HillHeightMap.NORMALIZE_RANGE = 100f

This is how I generate the alphamap

    private var alphamap: WeakReference<Image>? = null
    private var rendered: Boolean = false

    private val tex1Height = 0f
    private val tex2Height = 75f
    private val tex3Height = 80f
    private val maxHeight = 300f

if (rendered && alphamap != null && alphamap?.get() != null) return alphamap!!.get()!!

        val height = heightmap.size
        val width = heightmap.size

        val data = BufferUtils.createByteBuffer(width * height * 4)

        for (x in 0 until width) {
            for (z in 0 until height) {
                var alpha = 255
                var red = 0
                var green = 0
                var blue = 0

                val pointHeight = heightmap.getScaledHeightAtPoint(z, width-(x + 1))

                if (pointHeight < maxHeight) {
                    if (pointHeight > tex3Height) blue = 255
                    else if (pointHeight > tex2Height) green = 255
                    else if (pointHeight > tex1Height) red = 255
                    else if (pointHeight < tex1Height) alpha = 0
                } else alpha = 0
                data
                    .put(red.toByte())
                    .put(green.toByte())
                    .put(blue.toByte())
                    .put(alpha.toByte())
            }
        }

        val image = Image(Image.Format.RGBA8, width, height, data)
        alphamap = WeakReference(image)
        rendered = true
        return image

How can I make the transition between textures smoother/less noticeable?

1 Like

To get smoother blending, you usually just need to make the AlphaMap textures larger (or the terrain smaller). Larger alpha maps will have smoother edges (as a result of alotting more alphaMap pixels per world unit) but will take more memory. Usually I have my alpha map size set as the same as my terrain size, sometimes 2x that amount. So in my game, my 512 size terrains have a 512x512 alpha map texture and the blending is smooth enough.

I can’t see the exact sizes of your alpha map and terrain to know for sure. It looks like you are using the size of your original heighMap image to generate an equal size alpha map, so I suspect the terrain is likely much larger.

2 Likes

Thanks good to know. I am setting the terrain size to 513. And using the alpha map used that so it should be the same size. I’ll play around and see if changing things has an effect. I’m creating the byte buffer to a size 4x the terrain size but only looping width*height. I’ll play around with this code to see if I can change the result.

val height = heightmap.size
val width = heightmap.size

val data = BufferUtils.createByteBuffer(width * height * 4)

for (x in 0 until width) {
    for (z in 0 until height) {

I tried different widths, heights and byte buffer sizes and I still got the same result.

Maybe post a picture of your alpha map in case something pops up.

1 Like

If you are certain that your heightMap and alphaMap are both the same size as the terrain, then it might be something else causing the issue.

I have never generated an alpha map this way with code, but you might need to use the getInterpolatedHeight() method (HeightMap (jMonkeyEngine3)), rather than getScaledHeightAtPoint() (this would be especially true if your alphaMap is bigger than your heightMap)

I think your issue could be caused due to fetching a height from a location that is between 2 terrain points, in which case I think you need to get that interpolated height value.

I would also try changing this:

to instead be based on the size of the terrain (this would be a better way to do things, especially in case the heightMap isn’t the same size as the terrain):

    val height = trerrain.getTerrainSize();
    val width = trerrain.getTerrainSize();

And you could also queuey the height from the Terrain object with this method: Terrain (jMonkeyEngine3) (which also would be easier in cases where the original heightMap is not equal size to the terrain)