Uploaded image for project: 'Minecraft: Java Edition'
  1. Minecraft: Java Edition
  2. MC-219762

More performant noise blending algorithm in BlendedNoise

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • 1.17 Pre-release 1
    • 21w10a
    • None
    • Plausible
    • Performance
    • Normal

      Currently, the noise blending algorithm in the overworld samples the main noise to linearly interpolate the min and max limit noises to create the terrain for the overworld. However, due to the number of octaves used in the main noise, the noise is regularly below 0 or above 1. due to this, the clamped lerp renders either the min or max limit noise completely unused. By only calculating the limit noises when they can affect the output (i.e., when the main noise is between 0 and 1), we can eliminate 16 octaves of calculation in the majority of instances. When the main noise is below 0, only the min limit noise is calculated, and when it's above 1, just the max limit noise is calculated. Here's the code that fixes it and makes it more performant:

      public double sampleAndClampNoise(int x, int y, int z, double horizontalScale, double verticalScale, double horizontalStretch, double verticalStretch) {
          double mainNoiseSum = 0.0;
          double amplitude = 1.0;
          // First calculate the main noise to select which limit noise to use
      
          for (int i = 0; i < 8; i++) {
              ImprovedNoise mainNoise = this.mainNoise.getOctaveNoise(i);
              if (mainNoise != null) {
                  mainNoiseSum += mainNoise.noise(PerlinNoise.wrap(x * horizontalStretch * amplitude), PerlinNoise.wrap(y * verticalStretch * amplitude), PerlinNoise.wrap(z * horizontalStretch * amplitude), verticalStretch * amplitude, y * verticalStretch * amplitude) / amplitude;
              }
      
              amplitude /= 2.0;
          }
      
          // Scale the main noise for use in interpolation
          double noiseLerp = (mainNoiseSum / 10.0 + 1.0) / 2.0;
      
          // Reset amplitude for calculation
          amplitude = 1.0;
      
          // If the interpolation is below 0, calculate and use the min limit noise
          if (noiseLerp <= 0) {
              double minNoiseSum = 0.0;
              for (int i = 0; i < 16; i++) {
                  double scaledX = PerlinNoise.wrap(x * horizontalScale * amplitude);
                  double scaledY = PerlinNoise.wrap(y * verticalScale * amplitude);
                  double scaledZ = PerlinNoise.wrap(z * horizontalScale * amplitude);
                  double scaledAmplitude = verticalScale * amplitude;
      
                  ImprovedNoise minNoise = this.minLimitNoise.getOctaveNoise(i);
                  if (minNoise != null) {
                      minNoiseSum += minNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
                  }
      
                  amplitude /= 2.0;
              }
      
              // Return the scaled min noise
              return minNoiseSum / 512.0;
          } else if (noiseLerp >= 1) { // If the interpolation is above 1, calculate and use the max limit noise
              double maxNoiseSum = 0.0;
              for (int i = 0; i < 16; i++) {
                  double scaledX = PerlinNoise.wrap(x * horizontalScale * amplitude);
                  double scaledY = PerlinNoise.wrap(y * verticalScale * amplitude);
                  double scaledZ = PerlinNoise.wrap(z * horizontalScale * amplitude);
                  double scaledAmplitude = verticalScale * amplitude;
      
                  ImprovedNoise maxNoise = this.maxLimitNoise.getOctaveNoise(i);
                  if (maxNoise != null) {
                      maxNoiseSum += maxNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
                  }
      
                  amplitude /= 2.0;
              }
      
              // Return the scaled max noise
              return maxNoiseSum / 512.0;
          } else { // If the interpolation is between 0 and 1, calculate both noises and blend it
              double minNoiseSum = 0.0;
              double maxNoiseSum = 0.0;
              for (int i = 0; i < 16; i++) {
                  double scaledX = PerlinNoise.wrap(x * horizontalScale * amplitude);
                  double scaledY = PerlinNoise.wrap(y * verticalScale * amplitude);
                  double scaledZ = PerlinNoise.wrap(z * horizontalScale * amplitude);
                  double scaledAmplitude = verticalScale * amplitude;
      
                  ImprovedNoise minNoise = this.minLimitNoise.getOctaveNoise(i);
                  if (minNoise != null) {
                      minNoiseSum += minNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
                  }
      
                  ImprovedNoise maxNoise = this.maxLimitNoise.getOctaveNoise(i);
                  if (maxNoise != null) {
                      maxNoiseSum += maxNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
                  }
      
                  amplitude /= 2.0;
              }
      
              // Return the blended noise
              return Mth.lerp(noiseLerp, minNoiseSum / 512.0, maxNoiseSum / 512.0);
          }
      }
      

      This change will retain the overworld's exact shape as it only changes how the interpolation works internally. This code is licensed under CC0. On a test where I generated chunks and measured with a profiler, these changes cut the amount of time this method took by half, a significant improvement as noise calculation is one of the most intensive tasks while generating chunks.

            Unassigned Unassigned
            SuperCoder79 Jasmine Karthikeyan
            Votes:
            16 Vote for this issue
            Watchers:
            11 Start watching this issue

              Created:
              Updated:
              Resolved:
              CHK: