Skip to content

Instantly share code, notes, and snippets.

@stormrook
Created November 4, 2021 21:52
Show Gist options
  • Select an option

  • Save stormrook/7ed1019332718879bec2722bf970405a to your computer and use it in GitHub Desktop.

Select an option

Save stormrook/7ed1019332718879bec2722bf970405a to your computer and use it in GitHub Desktop.
#define USE_MULTIPOINT
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using UnityEngine;
public static class UberNoise2DSampler
{
public static float3 Sample(float2 pos,
int octaves,
float persistence,
float lacunarity,
float perturbFeatures,
float sharpness,
float sharpnessEnhance,
float amplification,
float altitudeErosion,
float ridgeErosion,
float slopeErosion)
{
float amplitude = 1.0f;
float frequency = 1.0f;
float3 accumulator = new float3(0.0f);
float max = 0;
float3 fistOctave = new float3(0.0f);
float3 lastOctave = new float3(0.0f);
float3 previousOctave = new float3(0.0f);
float3 previousAccumulator = new float3(0.0f);
for (int o = 0; o < octaves; o++)
{
max += amplitude;
float2 oPos = (pos * frequency) + (perturbFeatures * accumulator.yz);
#if USE_MULTIPOINT
// Expand the noise out to the central point and 4 surrounding points
// 2 on either side on the x any y (noise) axis
float[] expandedNoiseData = new float[5];
expandedNoiseData[0] = noise.srnoise(oPos + new float2(0.0f, 0.0f));
expandedNoiseData[1] = noise.srnoise(oPos + new float2(0.1f * frequency, 0.0f));
expandedNoiseData[2] = noise.srnoise(oPos + new float2(-0.1f * frequency, 0.0f));
expandedNoiseData[3] = noise.srnoise(oPos + new float2(0.0f, 0.1f * frequency));
expandedNoiseData[4] = noise.srnoise(oPos + new float2(0.0f, -0.1f * frequency));
// Generate billow (abs(x)) and ridge (1-abs(x)) noise for each point
float[] expandedBillow = Array.ConvertAll(expandedNoiseData, s => math.abs(s));
float[] expandedRidge = Array.ConvertAll(expandedBillow, s => 1.0f - s);
// Blend between billow and ridge as required for all points
expandedNoiseData = expandedNoiseData.Zip(expandedRidge, (n, r) => math.lerp(n, r, math.max(0.0f, sharpness))).ToArray();
expandedNoiseData = expandedNoiseData.Zip(expandedBillow, (n, b) => math.lerp(n, b, -math.min(0.0f, sharpness))).ToArray();
// Enhance Sharpness for all points
//
// Strength is basically just X to a power, but we don't want strange things when
// strength is negative so we multiply x by abs(x) to a power which means the sign is
// stable
expandedNoiseData = Array.ConvertAll(expandedNoiseData, n => n * math.pow(math.abs(n), sharpnessEnhance));
// Slope erosion
//
// Basically additional octaves count less the steaper the previous layers are
//
// We're using the accumulated derivative here which is technically only correct
// for the central point, but things are close enough together for this to work
// reasonably well.
float slopeErosionStrength = 1.0f / (1.0f + math.dot(accumulator.yz * slopeErosion, accumulator.yz * slopeErosion));
float dampedAltitude = amplitude * (1.0f - (ridgeErosion / (1.0f + math.dot(accumulator.yz, accumulator.yz))));
expandedNoiseData = Array.ConvertAll(expandedNoiseData, n => n * dampedAltitude * slopeErosionStrength);
// Build noiseData (noise at central point with reconstructed partial derivatives)
float3 noiseData = RecalculateDerivatives(expandedNoiseData);
// Add to our accumulators
accumulator.x += noiseData.x;
accumulator.yz += noiseData.yz;
// Altitude erosion
//
// Basically our amplitude falls off faster the lower the current accumulator value
// with the strength controlled by altitudeErosion
amplitude = amplitude
* math.lerp(persistence, persistence * math.smoothstep(0.0f, 1.0f, accumulator.x), altitudeErosion);
#else
// We're using the unity mathematics package noise functions
// as we can get back an analytical derivative (noise slope) from it
float3 noiseData = noise.srdnoise(oPos);
// Generate billow noise
//
// strength is just the abs value, the derivative is similar but
// if the noise strength is 0 we also want the slope to be 0
float3 billowNoise = new float3(math.abs(noiseData.x),
math.sign(noiseData.x) * noiseData.y,
math.sign(noiseData.x) * noiseData.z);
// Generate ridge noise
//
// strength is 1-x, but derivatives are just flipped
float3 ridgeNoise = new float3(1.0f - billowNoise.x,
-billowNoise.y,
-billowNoise.z);
// Switch between billow (rolling hills) and ridge (high sharp peaks)
// based on sharpness
//
// Q: Can we lerp the derivatives like this?
noiseData = math.lerp(noiseData, ridgeNoise, math.max(0.0f, sharpness));
noiseData = math.lerp(noiseData, billowNoise, -math.min(0.0f, sharpness));
// Enhance sharpness
//
// Strength is basically just X to a power, but we don't want strange things when
// strength is negative so we multiply x by abs(x) to a power which means the sign is
// stable
//
// Derivatives: Q: I've no idea if this is correct, but I'm assuming that as the
// derivative of X^2 is 2X I can just multiply by the sharpnessEnhance
noiseData.x = noiseData.x * math.pow(math.abs(noiseData.x), sharpnessEnhance);
noiseData.yz *= 1.0f + sharpnessEnhance;
// Slope erosion - Basically additional octaves count less the steaper the previous layers are
float slopeErosionStrength = 1.0f / (1.0f + math.dot(accumulator.yz * slopeErosion, accumulator.yz * slopeErosion));
float dampedAltitude = amplitude * (1.0f - (ridgeErosion / (1.0f + math.dot(accumulator.yz, accumulator.yz))));
noiseData.x *= slopeErosionStrength * dampedAltitude;
noiseData.yz *= slopeErosionStrength * dampedAltitude; // Q: What to do with derivatives this doesn't seem right
// Add to our accumulators
//
// Scale the derivatives by frequency as we're squishing the noise so the slope is steaper
accumulator.x += noiseData.x;
accumulator.yz += noiseData.yz;
// Altitude erosion
//
// Basically our amplitude falls off faster the lower the current accumulator value
// with the strength controlled by altitudeErosion
amplitude = amplitude
* math.lerp(persistence, persistence * math.smoothstep(0.0f, 1.0f, accumulator.x), altitudeErosion);
#endif
persistence += amplification;
frequency *= lacunarity;
}
// We also scale the derivatives here. We're squashing the
// world so the slopes get squashed too
//return lastOctave / max;
//return new float3(lastOctave.x, previousAccumulator.y, previousAccumulator.z) / max;
return accumulator / max;
}
#if USE_MULTIPOINT
private static float3 RecalculateDerivatives(float[] expanded)
{
float3 retVal = new float3();
retVal.x = expanded[0]; // Noise Value
if (math.sign(expanded[1]) != math.sign(expanded[2]))
{
// We've crossed a peak, can't risk a single pixel zero partial derivative
retVal.y = math.max(expanded[1], expanded[2]);
}
else
{
retVal.y = (expanded[1] - expanded[2]) / 0.2f; // X partial derivative
}
if (math.sign(expanded[3]) != math.sign(expanded[4]))
{
// We've crossed a peak, can't risk a single pixel zero partial derivative
retVal.y = math.max(expanded[3], expanded[4]);
}
else
{
retVal.z = (expanded[3] - expanded[4]) / 0.2f; // Y partial derivative
}
return retVal;
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment