Ground from height-map texture

I think it might be convenient if there was a way to create grounds from height-map textures (texture objects) out of the box as for now it is only possible to load them via url leaving behinds procedural textures. Also it can be even useful in cases when one needs to create procedural levels.
I managed to come up with such sort of code, but it’s look a little bit clumsy because vertexData.applyToMesh receives as input only unsigned RGBA buffer.

BABYLON.Mesh.CreateGroundFromHeightTexture = (name, texture, width, height, subdivisions, minHeight, maxHeight, scene, updatable, onReady, alphaFilter) => {
    let options = {
        width: width,
        height: height,
        subdivisions: subdivisions,
        minHeight: minHeight,
        maxHeight: maxHeight,
        updatable: updatable,
        onReady: onReady,
        alphaFilter: alphaFilter
    };

    return BABYLON.GroundBuilder.CreateGroundFromHeightTexture(name, texture, options, scene);
};

BABYLON.GroundBuilder.CreateGroundFromHeightTexture = (name, texture, options, scene) => {
    var width = options.width || 10.0;
    var height = options.height || 10.0;
    var subdivisions = options.subdivisions || 1 | 0;
    var minHeight = options.minHeight || 0.0;
    var maxHeight = options.maxHeight || 1.0;
    var filter = options.colorFilter || new BABYLON.Color3(0.3, 0.59, 0.11);
    var alphaFilter = options.alphaFilter || 0.0;
    var updatable = options.updatable;
    var onReady = options.onReady;

    scene = scene || BABYLON.EngineStore.LastCreatedScene;

    var ground = new BABYLON.GroundMesh(name, scene);
    ground._setReady(false);
    ground._subdivisionsX = subdivisions;
    ground._subdivisionsY = subdivisions;
    ground._width = width;
    ground._height = height;
    ground._maxX = ground._width / 2.0;
    ground._maxZ = ground._height / 2.0;
    ground._minX = -ground._maxX;
    ground._minZ = -ground._maxZ;

    if (texture.textureFormat !== BABYLON.Engine.TEXTUREFORMAT_RGBA) {
        throw "GroundBuilder: texture must correspond RGBA format";
    }

    var buffer = texture.readPixels(0, 0, buffer);
    var byteBuffer = buffer;

    if (texture.textureType !== BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT) {
         var length = buffer.length;
         byteBuffer = Uint8Array(length);
         for (i = 0; i < length; i += 4) {
             byteBuffer[i] = buffer[i] * 255.0;
             byteBuffer[i + 1] = buffer[i] * 255.0;
             byteBuffer[i + 2] = buffer[i] * 255.0;
             byteBuffer[i + 3] = buffer[i] * 255.0;
         }
    }

    var vertexData = BABYLON.VertexData.CreateGroundFromHeightMap({
        width: width, height: height,
        subdivisions: subdivisions,
        minHeight: minHeight, maxHeight: maxHeight, colorFilter: filter,
        buffer: byteBuffer, bufferWidth: texture.getSize().width, bufferHeight: texture.getSize().height,
        alphaFilter: alphaFilter
    });
    vertexData.applyToMesh(ground, updatable);

    if (onReady) {
        onReady(ground);
    }

    ground._setReady(true);

    return ground;
};

PS I don’t know js well and am new to Babylon JS so I presume I’m missing something.

Hello and welcome!!

You can create “virtual URL” from arraybuffer content with URL.createObjectUrl():

            var blob = new Blob([data], { type: "octet/stream" });
            var url = URL.createObjectURL(blob);

and then use that URL for the CreateGround :slight_smile:

2 Likes

Not to make this topic so wasteful, I actually thought Height-Maps can be effectively reimplemented using computer shaders and storage buffers when WebGL 2 stabilizes. It should be good performance-wise as well as provide higher color precision.

1 Like

Hi @AlexandrKedrov,

I am also new to Babylon but I thought I’d take a crack at your question. I was able to make a ground from a height-map texture (taken from google) out of the box using the Node Material Editor.

Check out the playground here to see the final result: https://playground.babylonjs.com/#507KU5#2

Right now the material is loading via url just to show you the end product. To recreate my work, just:

  1. Comment out lines 26-28
  2. Uncomment lines 31-32
  3. Click the play button to reset the scene
  4. Open the Inspector (Gear Button > Inspector)
  5. Expand “Materials”
  6. Click “nodeMaterial”
  7. Scroll to the bottom of the Properties Box (right box) and click “Node Material Editor”
  8. You can mess with the material here as you wish or you can load mine in clicking “Load” on the right side. You can find mine on github
  9. The changes you make should be reflected in the Playground.

Hope this fits your use case and helped a bit!

Hi @belfortk,

It looks nice, but I am scared about performance. I haven’t dug into materials yet, but if they use vertex shader for displacement then it might be overwhelming for this kind of task.

Anyway, I think the best solution is to use custom wrapper for vertexData.applyToMesh or create “virtual URL” (perhaps the quickest way) as @Deltakosh suggested.

1 Like