Leveraging Beta Distributions For Flexible Random Gradients

Using Beta Distributions for Dynamic Randomness

Beta distributions are continuous probability distributions defined over the interval [0, 1] parameterized by two positive shape parameters, commonly labeled α and β. Unlike the uniform distribution which assigns equal probability over the [0, 1] range, beta distributions allow for a variety of distribution shapes based on the choice of the α and β parameters.

Some properties of beta distributions that make them useful for procedural content generation include:

  • Flexibility – Different choices of α and β lead to different distribution shapes like uniform, triangular, normal, and more.
  • Bounded range – Always defined over [0, 1], convenient formodeling percentages and random gradients.
  • Conjugate prior – Useful for Bayesian analysis when the data is assumed beta-distributed.
  • Defined for positive and negative α and β – Allows skewed J shaped and mirrored J shaped distributions.

By providing control over the shape of the randomness, beta distributions have benefits over default uniform distributions for tasks like adding aesthetic variety, tuning challenge difficulty, or creating natural-looking outputs. The α and β parameters directly map to controlling the form of the distribution.

Controlling the Shape with Alpha and Beta Parameters

The α and β parameters have an intuitive effect on the distribution shape. α mainly controls the height of the peak while β controls how fast the distribution drops off. Some examples:

  • α = β = 1 is the standard uniform distribution
  • α < 1, β = 1 is a J shaped distribution clustered near 0
  • α = 1, β < 1 is clustered near 1
  • α = β > 1 is a distribution clustered in the middle peak
  • α < β results in negative skew clustered on the high end
  • α > β results in positive skew clustered on the low end

Code to sample from a beta distribution is simple using common math libraries. For example with Python and NumPy:

import numpy as np

# Sample with a=2, b=5 
samples = np.random.beta(2, 5, size=100) 

By tuning the parameters, beta distributions can generate randomness matched to your use case, whether a softly tapering gradient or clusters of sharp spikes.

Fitting Gradient Noise to Your Game

Dynamic randomness like Perlin noise is commonly used in games for effects animation, environment texturing, and procedural content generation. Beta distributions provide a flexible way to control gradient randomness for enhanced variety.

Noise for Aesthetic Effects

Gradient noise mapped to color channels, transform properties, or shader parameters can create flowing heat shimmer, flickering lights, wavering dimensions, pulsing power-ups, and watery refractions. Beta distributions lend organic-feeling variance by clustering parameter changes only slightly away from defaults rather than uniformly across extremes.

float betaSample = SampleBetaNoise(2.0, 5.0); 

// Map to 0-1 color gradient
color = lerp(darkColor, lightColor, betaSample);

Noise for Gameplay Variety

Procedural generation via noise creates unpredictable level geography, enemy spawn locations, loot drop quantities, and character stats. Beta distribution driven noise stops extremes like excessively sparse or dense enemy counts. This maintains enjoyable challenge without too much randomness.

int numEnemies = Round(SampleBetaNoise(1.5, 3) * maxEnemies);

Optimizing Challenge with Noise Parameters

The α and β allow explicit control over the noise profile used for generation. Increasing β relative to α concentrates magnitude towards average values. This tunes content to a softly varying sweet spot rather than frequently hitting extremes. Lower α biases downwards and higher biases upwards for asymmetric effects.

// Cluster nearer to low quantities 
int numCoins = Round(SampleBetaNoise(1, 4) * maxCoins);

Sample Code for Gradient Noise Functions

Beta distribution noise can be implemented efficiently in code by sampling the standard library beta distribution. Wrapping with Perlin ridged noise gives the standard flowing gradient effect.

float SampleBetaNoise(float alpha, float beta) {
  
  // Perlin ridged noise from -1 to 1
  float perlin = PerlinRidged2D(uv);  

  // Map Perlin value to beta distribution parameters
  float a = Remap(perlin, -1, 1, alpha*0.5, alpha*2);
  float b = Remap(perlin, -1, 1, beta*0.5, beta*2);

  return SampleBetaDistribution(a, b); 
}

The flexible beta distribution integrates well into procedural pipelines for aesthetic and functional randomness.

Advanced Usages and Special Cases

While the standard univariate form covers most use cases, beta distributions also support multivariate, parameterized, and data-fit versions for special needs.

Correlated Multivariate Betas

Multivariate beta distributions allow encoding correlations between multiple random dimensions. This maintains consistent relationships in the noise. For instance terrain elevation could modulate settlement density or temperature shifts might alter weapon spread.

// Correlated samples
float[2] sample = MultivariateBeta([a1, a2], 
                                  [b1, b2], 
                                  Rho); 

// Terrain and structures
float elevation = sample[0];
float density = sample[1];

Beta Distributions in Shader Graphs

The Unity Shader Graph allows rapid visual prototyping of graphics effects using node workflows. Beta distribution nodes offer enhanced control compared to default uniform noise.

// Flowing beta gradient mapped to color
float alpha = RandomRange(0.5, 2); 
float beta = RandomRange(3, 6);
float t = BetaDistribution(uv.rg, alpha, beta);
color = GradientLerp(t);

Issues with Extreme Parameter Values

Beta distribution sampling can encounter numerical instability for very small α < 0.05 or very large α, β > 250 parameter values due to floating point precision limits. Workarounds include clamping parameters or directly computing the probability density function.

float sample = ClampedBeta(a, b);

float pdf = BetaPDF(x, a, b); 
float cdf = BetaCDF(x, a, b)
float sample = BetaCDFInverse(cdf, a, b);

Example Code for Advanced Beta Usage

This reusable BetaDistribution class encapsulates best practices for robust sampling and handles edge case parameter values.

public class BetaDistribution {

  public float Sample(float alpha, float beta) {
    
    alpha = Mathf.Clamp(alpha, 0.05, 250);
    beta = Mathf.Clamp(beta, 0.05, 250);
    
    if (alpha < 0.1 || beta < 0.1) {
      return SampleCDF(alpha, beta); 
    }
    else {
      return Math.Beta(alpha, beta);
    }
  }

  private float SampleCDF(float alpha, float beta) {
    // Implement CDF sampling
  }

}

With smart parameter selection and error handling, beta distributions enable advanced procedural effects.

Conclusion

Summary of Techniques

Beta distributions' flexibility through the α and β parameters gives improved control over random gradients compared to plain uniform noise. Sample uses:

  • Aesthetic variety - flowing colors, animation offsets
  • Tuned challenge - enemy counts, loot drops
  • Organic feel - clustered nearer to defaults
  • Correlated dimensions - linking environment factors
  • Shader graphs - unlocked new noise effects

Links to Other Procedural Generation Methods

Beta noise combines well with other common procedural techniques like Perlin and cellular noise, diffusion limited aggregation, and Markov chains for powering entire generative pipelines.

Invitation for Reader Suggestions and Feedback

Does your team have clever uses for beta distribution noise? What other distribution shapes would be useful? Share your experiences in the comments below!

Leave a Reply

Your email address will not be published. Required fields are marked *