So I have this plane generated with two different Perlin noises. It has mainly 3 regions, divided by color. I want to smooth out the lines, to create a "gradient" so there are no abrupt color changes:

enter image description here

Each zone corresponds to a biome which is decided by a PerlinNoise called Humidity and another one called Temperature, so each vertex receives a biome according to the following chart with its corresponding humidity and temperature vertices' height:

enter image description here

So this is my code (I commented almost every step):

        //CREATES the terrain material and geometry + adds the color attribute
        let TerrainMaterial = new THREE.MeshPhongMaterial({side:THREE.DoubleSide, flatShading: true,vertexColors: true});
        let TerrainGeometry = new THREE.PlaneGeometry(step, step, 16,16);
        TerrainGeometry.setAttribute( 'color', new THREE.BufferAttribute( new Float32Array(  TerrainGeometry.attributes.position.count * 3), 3 ) )
        const colors = TerrainGeometry.attributes.color;

        //The position like this is to create a grid so every tile is next to eachother
        let terrain = new THREE.Mesh(TerrainGeometry,TerrainMaterial)
        terrain.position.set(x, 0, z).multiplyScalar(step)

        // Create some variables
        const TemperatureVertices = TemperatureTerrain.attributes.position;
        const HumidityVertices = HumidityTerrain.attributes.position;
        let pos = TerrainGeometry.attributes.position;

        const lerp = (a, b, amount) => (1 - amount) * a + amount * b;

         //iteration through each vertex
        for(let i = 0; i < pos.count; i++){

            //gets temperature and humidity maps vertex height
           let temperature = TemperatureVertices.getY(i) * 2;
           let humidity = HumidityVertices.getY(i)* 2;

           //this function returns the biome according with the chart in the post by giving it temperature and humidity variables
           let BIOME = this.Biomes.determineBiome(temperature,humidity,new THREE.Vector2(x, z),i);
           //This retrieves the fractional part of the numer, ex: frac(-2.8) = 0.8 or frac(10.09) = 0.9
           var a = Math.abs(temperature) - Math.floor(Math.abs(temperature))
           var b = Math.abs(humidity) - Math.floor(Math.abs(humidity))

           //This variables determine the "blending zone", wich you will understand later
           let AMPLIFIER = 0.1;
           let AMPLIFIER1 = 0.9;

           //Instantiates the temperature and humidity variables
           let temperature_blending = temperature;
           let temperature_weight = 1;

           let humidity_blending = humidity;
           let humidity_weight = 1;

           //This is executed if the temperature is close to the next biome chart row/column
            if(a < AMPLIFIER){
                //This algorithm gives me like a percentage of how much the vertex is influenced by the other biome.
                let x = Math.abs(temperature);
                temperature_weight = (x - Math.floor(x)) / AMPLIFIER

                //here based on the temperature, I can detect what is the closer column-
                if(temperature > 0){
                    temperature_blending -= 0.5
                    temperature_blending += 0.5
                //here I repeat the process but backwards as my function has to work with negative numbers when the closest biome column is further away than the original one.
            }else if(a > AMPLIFIER1){
                let x = -Math.abs(temperature);
                temperature_weight = (x - Math.floor(x)) / AMPLIFIER

                if(temperature < 0){
                    temperature_blending -= 0.5
                    temperature_blending += 0.5
            //here I repeat all the process but for humidity
             if(b < AMPLIFIER){
                 let x = Math.abs(humidity);
                 humidity_weight = (x - Math.floor(x)) / AMPLIFIER
                 if(humidity > 0){
                     humidity_blending -= 0.5
                     humidity_blending += 0.5
             }else if(b > AMPLIFIER1){
                 let x = -Math.abs(humidity);
                 humidity_weight = (x - Math.floor(x)) / AMPLIFIER
                 if(humidity < 0){
                     humidity_blending -= 0.5
                     humidity_blending += 0.5

            //Once we get here, We have the original vertex biome and the closest biome to it (if it is in the blending zone: very close to another biome implying that it has to start blending with it)
           let BIOME1 = this.Biomes.determineBiome(temperature_blending,humidity_blending,new THREE.Vector2(x, z),i);

                //I am very lost from here, I also think that the way to calculate the weights is incorrect.

           //this calculate the average between both climatic factors 
           let average = (humidity_weight+temperature_weight)/2

           //Here I try blending the colors between them using the average 
           colors.setXYZ( i, lerp(BIOME.color.r,BIOME1.color.r,average), lerp(BIOME.color.g,BIOME1.color.g,average), lerp(BIOME.color.b,BIOME1.color.b,average));

The result which doesn't look well: enter image description here

So if you know how I can correct it, or if an algorithm already exists please tell me. Thanks a lot!


I got to make it work thanks to enter link description here steps. (note that the heights are rounded numbers by preset): enter image description here


1 Answer 1


So you basically have a function that takes a coordinate, runs it through some noise, and outputs a biome, if I understand correctly? Biome blending is trickier than it might seem at surface value, but there's an approach I've been using that works well

  • Scatter a bunch of points around your world, similar to how you would in Voronoi noise.
    • You can also use blue noise point distributions, e.g. Poisson if your world is finite and small.
  • Evaluate the biome at each of those points.
  • Figure out a circle size that will always contain points, use a falloff function to assign weights to nearby biomes, and perform a weighted average.

I wrote about it in more detail here: https://noiseposti.ng/posts/2021-03-13-Fast-Biome-Blending-Without-Squareness.html

Also I wouldn't suggest using actual Perlin for noise, at least not without something in place to fix its issues. A lot of sources talk about it, but it has strong cardinal axis bias. Noise should remind us of nature, not of the coordinate-space math under the hood. Better as a general rule to use Simplex or Simplex-type noise, or specifically 3D Perlin which has been rotated so that its cardinal planes are out of alignment. This library supports both: https://www.npmjs.com/package/fastnoise-lite/v/0.0.1

  • \$\begingroup\$ This approach looks very good, I think it's just what I need. But I don`t understand what you mean in the las step with "certain points". \$\endgroup\$
    – alon
    Jan 13, 2022 at 18:04
  • \$\begingroup\$ You mean when choosing the circle size, right? Basically if you start with a grid and randomly offset each of the points, the blending circle radius needs to be no less than (max dist to any point on the unjittered grid + max jitter displacement amount) so that there will always be at least one point in range of a sampling circle. \$\endgroup\$
    – KdotJPG
    Jan 17, 2022 at 1:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .