# Procedural Multiplicative Perlin Noise Model for Dust Cloud Generation

Thilen Savignon

► To cite this version:

Thilen Savignon. Procedural Multiplicative Perlin Noise Model for Dust Cloud Generation. LJK / Grenoble University - INRIA. 2025, pp.1-51. hal-05188630

HAL Id: hal-05188630

https://hal.science/hal-05188630v1

Submitted on 27 Jul 2025

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_1.png" /></figure>

Distributed under a Creative Commons Attribution 4.0 International License
Master of Science in Informatics at Grenoble
Master Informatique, Université Grenoble Alpes
Specialization Data Science and Artificial Intelligence

# Procedural Multiplicative Perlin Noise Model for Dust Cloud Generation

Thilen SAVIGNON

June 25th, 2025

Research project performed at LJK - INRIA

Under the supervision of:
Fabrice NEYRET

Defended before a jury composed of:

Dominique VAUFREYDAZ

Jean-Michel DISCHLER

Fabrice NEYRET
<figure><img src="image_2.png" /></figure>
Abstract

With the continuous improvements of imaging technology, astronomy images
are getting more and more detailed and fascinating. Images of nebulae and galaxies
by space satellites such as the Hubble Space Telescope or the James Webb Space
Telescope have had an impact on the entire world, from the scientific community
to the general public. However, when it comes to computer graphics, creating real-
time images of such complex structures for education and entertainment purposes
proves to be much of a challenge. In this paper, we present a way to efficiently
generate details to improve low-resolution data of dust and gas space clouds.

Acknowledgement

I would like to express my sincere gratitude to Fabrice Neyret for his tutorship and his support through this internship. I also thank him for his proposition to pursue this work during a possible thesis.

I would also like to thank Mathéo Moinet for his help, and the entire Maverick
team for their welcome.

Finally, I would like to thank the INRIA, LJK, UGA and ENSIMAG for giving
me the opportunity to study and work in the field I love.

Résumé

Avec l'amélioration constante des technologies de capture d'images, les images
de l'espace deviennent de plus en plus nettes et fascinantes. Les images de ga-
laxies ou de nebuleuses du télescope spatial Hubble ou du James Webb Space Te-
lescope ont eu un impact mondial, aussi bien sur la communauté scientifique que
sur le grand public. Mais, pour le domaine des Computer Graphics, générer des
images de structures si complexes á des fins éducatifs ou ludiques pose de nom-
breux problémes. Dans ce papier, nous présentons un méthode pour générer des
détails supplémentaires pour améliorer les données basses résolution des nuages
de poussières et de gaz.
<figure><img src="image_3.png" /></figure>
<table><tr><td></td><td></td><td>17</td></tr><tr><td>4</td><td>Implementation and results</td><td>19</td></tr><tr><td>4.0.1</td><td>2D implementation and analysis</td><td>19</td></tr><tr><td></td><td>Implementation details</td><td>19</td></tr><tr><td></td><td>Note on the Perlin noise implementation</td><td>19</td></tr><tr><td></td><td>Control of the visual aspect: Filaments</td><td>20</td></tr><tr><td>4.0.2</td><td>3D implementation and analysis</td><td>20</td></tr><tr><td></td><td>Implementation details</td><td>20</td></tr><tr><td>5</td><td>Conclusion</td><td>21</td></tr><tr><td>A</td><td>Appendix</td><td>23</td></tr><tr><td>Bibliography</td><td></td><td>25</td></tr><tr><td></td><td>Internet sources</td><td>25</td></tr></table>
# 1

## Introduction

### 1.1 Background and motivation

In the field of Computer Graphics, the ability to model and render realistic scenes in real-time is often an essential component of the production of visualization applications. In this study, we will be looking at the case of real-time rendering of astronomical objects such as galaxies and nebulae, which span across a wide number of scales, for example for use in digital planetariums. The real-time aspect of the rendering is required by our desire to create an interactive system, with the ability to "walk through" the objects, and switch from an exterior point of view to an inside view in a continuous motion.

This particular case poses a few interesting challenges. We want to achieve real-time rendering performances, which restricts the maximum computing time allowed per frame (approximately 41 milliseconds for the cinema standard of 24 images per seconds, and down to approximately 16 milliseconds for 60 images per seconds, which is the usual standard for video games). We also want to impose a limit on the required hardware. We want to achieve real-time rendering performances on commercially available Graphics Processing Units (GPUs).

This necessity to reconcile the high computational and memory requirements of large-scale

<figure><img src="image_4.png" /><figcaption>Figure 1.1: Real-life examples of possible procedural generation applications</figcaption></figure>
volumetric rendering and the real-time rendering performances on restricted hardware comes
with a number of great challenges.

1.2 Problem statement

Many of the issues related to real-time rendering of large-scale space scenes have to do with
volumetric rendering. While light is mostly emitted by stars, most of the visible interactions
of light with matter are due to gasses and dust clouds, mostly as part of specific objects like
galaxies and nebulae.

In graphics, gasses and dust clouds are often referred to as volumes, in opposition to the
surface-based objects modelled using meshes. To use them in a scene, these volumes require
specific data structures for the modelling aspect, and specific algorithms for the rendering as-
pect.

The main objective for the modelling aspect of volumetric rendering of space volumetric
density fields is to design a way to specify, for each point in space, the parameters of the volume,
such as the density. Existing data obtained from observation or simulation can be used, but is
often lacking in details and resolution, mostly because of the limits of observation devices.

Our goal is to improve the low-resolution density data for space dust clouds by generating
additional data to fill in the gaps with new details.

While the naive brute-force method would be to use physics-driven simulations, this solu-
tion comes with it's own challenges. First of all, the physics models used are not complete. In
particular the 3D/spatial distribution of gasses and dust clouds is an ongoing field of research
in astrophysics. Furthermore, fluids and particles simulations at this scale are extremely com-
putation intensive, and therefore are not compatible with our real-time generation goal. Since
the objects we want to render span across several scales, and since we do not restrict the view
point, it is simply impossible to compute and store enough data to have a good representation
of these objects from any view points. Even if we restricted ourselves to rendering a single
galaxy, there are so many different layers of details that storing all of them in a high enough
resolution to fly a camera through it would hardly be possible on a single computer, let alone
in a Graphics Processing Unit.

Instead, we will be using procedural noise generation to add details to the low resolution
data. Our goal is then to design a noise model for dust cloud generation which will be used
to modulate the original data with a multiplication. To do so, our model must satisfy some
quantitative and qualitative properties.

The quantitative properties we want are the following:

* Expected value of 1: The expected value of a random distribution is the mean of all the values in the distribution, weighted by their respective probability. The reason we need to ensure this property is that, since we will be multiplying our noise values with the data, we want to maintain the density of the low resolution real-life data to simply change its spatial distribution and add details. The expected value of 1 will ensure that the quantity of matter of the original data stays untouched in the final result.

* An open value range, with values between 0 and N: both the noise values and original data will correspond to a density value of the dust cloud, which cannot be negative. This
enforces a lower bound of 0 for our model. We will see later that keeping the lower bound
as close as possible to 0 is going to be part of our goal. Density fields can contain values
greater than 1, and there are no limits on the upper bound. However, the requirement for
the expected value means that *N* has to be greater than 1.

This requirements will ensure that the model will work for our use case. In addition, we
can define several qualitative guidelines we can use to evaluate the usability and visual aspect
quality of the noise model:

* High expressive range: We want to find a model that can be shaped in as many ways as possible while still meeting the requirements stated above.

* "Empty" zones: Dust and gas clouds in galaxies and nebulae tend to present holes and very low density zones. We want our model to provide these zones of very low density.

Finally, we discuss some guidelines and notes on the visual aspect of the objects our noise
generator attempts to recreate. One of the discussed visual elements shared by many instances
of space volumes are filaments. Figure 1.2 shows an example of these structures in the M51
galaxy, where different scales of lines are visible along the arms of the spiral. In Chapter 4, we
discuss a start of a possible solutions to obtain filaments-like patterns in our noise model.

1.3 Outline

In Chapter 2, We will give the necessary background knowledge and previous works on the
subject. Chapter 3 will present three different approaches based on Multiplicative Perlin noise
to try and create a new procedural noise model meeting all of the requirements previously
stated. In Chapter 4, we will discuss details about the implementation of the test framework.
Finally, Chapter 5 will give the conclusion and talk about future possible work on this subject.
<figure><img src="image_5.png" /><figcaption>Figure 1.2: The Whirlpool Galaxy (M51) captured by Hubble [15].</figcaption></figure>
State-of-the-Art

**2.1 Preliminary notions**

Before we begin, we introduce some preliminary notions used for this work, and present in the report.

**2.1.1 Procedural noise**

**Noise definition**

In graphics, a noise can be intuitively defined as a random, continuous, unstructured pattern generator. Noise functions are usually defined in 2 or 3 dimensional spaces, but can be defined for any dimension count. Noise functions are mainly used to add randomness and additional details to objects or textures. Noise functions can be used in a lot of situations, for example for cloud shape [10], terrain generation or procedural texture creation. Figure 2.1 shows examples of noise used in procedural generation.

<figure><img src="image_6.png" /><figcaption>Figure 2.1: Examples of noise used for procedural generation. Left: Texture example from Perlin [9]. Middle: Procedural terrain generation from Minecraft [14]. Right: Procedural cloud generation in Horizon: Zero Dawn [10]</figcaption></figure>

Noise patterns can be described in two different ways. First, in the spatial domain, a noise function will assign a value to every location in space. Given a N-dimensional space, a noise function assign a value from $R^N$ to $R$. To analyse statistical properties, noise is more often described as a signal, and treated as such. This means that the noise is described using its power spectrum, which specify the magnitude of each frequency (since the pattern is unstructured,
the phase is random and therefore not useful information). Particularly, a more specific defi-
nition of noise was given by Perlin and Hoffert [9]: *noise is an approximation to white noise
band-limited to a single octave*. White noise is a signal containing all frequencies with equal
amplitudes and random phases. This definition of noise as a signal described by a power spec-
trum is the most used across literature to analyse and compare different noise implementations,
and justify the creation of newer noise models [3][2].

Procedural Noise

While Section 2.1.1.1 presented a general definition of noise, we will now define *procedural*
noise. The *procedural* adjective is used in computer sciences to specify objects defined by
a program or a procedure rather than by data structures. In computer graphics, procedural
generation often refer to the creation of objects, volumes and textures by a program, as opposed
to storing and loading them from a file. A procedural noise function is then a program used to
evaluate noise at possibly any point in space, on the fly at render time.

The advantages of procedural noise functions (and procedural generation in general) com-
pared to stored data are the following:

* In computer graphics, programs are usually much more compact in memory than objects, texture or volumes stored in a 3-dimensional grid. This can help in managing V-RAM usage in GPUs.

* Procedural noise functions are non-periodic. They can cover a large area of the 2D or 3D space without seams or visible repetitions. On the contrary, using textures can introduce tiling or visible seams when the texture is repeated.

* Procedural functions are parametric, meaning they can be controlled using input param-
eters, and therefore can represent a class of similar noise patterns, which gives more
control on the final look.

* Procedural noise functions are randomly accessible, meaning the function can be evalu-
ated at any point in space regardless of neighbor evaluation and in constant time.

However, these advantages are not always guaranteed, and depend on the implementa-
tion [2].

Perlin Noise

Perlin noise is a noise function introduced by Perlin [7] in 1985. It is one of the most com-
monly used noise function in graphics, for its simplicity of use and implementation, and for its
low computational cost. 2-dimensional Perlin noise is evaluated by creating a pseudo-random
gradient at each vertex of a square lattice. The noise value for a given point is then obtained by
the spline interpolation of of the dot product of gradient at the 4 nearest vertices with the vector
from the vertex to the evaluated point. Figure 2.2 shows an example of Perlin noise.

Since its introduction, efforts have been made to try and improve this model, mainly with a
better interpolation function and gradient computation [8] and with the introduction of Simplex
noise.
<figure><img src="image_7.png" /><figcaption>Figure 2.2: Example of Perlin noise</figcaption></figure>

## Fractional Brownian Motion

While Perlin noise can be used on its own, it is usually lacking in details for most applications. One common way to solve this problem is referred to as Fractional Brownian Motion (fBm).

Fractional Brownian Motion consists of layering multiple Perlin noises with different frequencies, called octaves. Each additional octave will usually have twice the frequency and half the amplitude of the previous octave. The phase is considered random, which is obtained in practical implementations by adding a random offset to the sampled position for each octave. The final fBm model is given by the following formula:

$$fBm(p) = \sum_{n} \frac{P(p * 2^n)}{2^n}$$

where *p* is the location in space where the function is evaluated, *P(p, f)* is the Perlin noise at point *p* and with frequency *f*, assuming a random phase for each frequency. See Figure 3.2 for an example of fBm.

## 2.1.2 Volumetric Rendering

Volumetric rendering is the process of creating an image from volumetric data. The main way of achieving this is through an algorithm known as Ray-Marching. Since the amount of light transmitted by a volume along a ray depends on the density of the volume along the ray, we sample the volume several times, moving along the ray with a fixed step size. This allows us to compute an approximation of the integral of the density of the volume along the ray, which we can use to compute the quantity of light transmitted along the ray.

Several extensions of this algorithm are possible to include more complex lighting formulas, and these extension are usually specific to a particular type of volumetric rendering such as the rendering of clouds viewed from the ground.
## 2.2 Previous works

### 2.2.1 Unary Multiplicative Perlin

In a chapter of his book on procedural texturing [6], F. Kenton Musgrave mentions a method of improving fBm terrain generation by scaling higher frequencies of the noise by the local values of the lower frequencies, in an attempt to obtain a smoother surface for the bottom of valleys in a mountain settings. In a similar way, internship report by Loubet [4] mentions the use of multiplication to stack different octaves of Perlin noise. While the report does not explore too much of the details of this method, we will be looking into this more in Chapter 3. We will see how this method gives more control and better results with regards to the goals stated in Chapter 1.
# Our contribution: controlling and shaping the multiplicative noise

## 3.1 Basic Approach

### 3.1.1 First approach with Fractional Brownian Motion

In procedural generation, the use of Perlin noise usually refers to the use of fBm. This is because most applications of procedural generation (terrain, ocean surface, clouds) usually consists of multiple layers of details with different frequencies. For example, 3-dimensional fBm is used as a base for the cloud shapes (in combination with another noise model called Worley noise) in the generation of clouds for the game Horizon: Zero Dawn [10] (see Figure 3.1 for an example). But in our case, the fBm method has some drawbacks that prevent its use.

<figure><img src="image_8.png" /><figcaption>Figure 3.1: Basic cloud shapes using fBm in Horizon: Zero Dawn [10]</figcaption></figure>

First, the range of values is restricted. The first octave has a range of [-1; 1], and each following octave divide this range by 2. Since we add all the octaves together, we can determine that the maximum value of the noise, as we increase the number of octaves, is the following:

$$ \lim_{n \to \infty} \sum_{i=0}^{n} \frac{1}{2^n} = 2 $$
The fBm method will maintain the symmetry of the Perlin noise distribution, so the maximum range of fBm is [-2; 2]. Since we need to shift the values to fit in our [0; N] range requirements, while keeping an expected value of 1, this means that the maximum possible range is [0; 2]. The symmetry of the model offers very little control on the final result and upper bound of the model.

Furthermore, another drawback of the fBm method is the lack of correlation between detail levels. Each octave's contribution to the final result is not impacted by the other octaves. This means that "empty" zones of the low frequencies will still contain the same amplitude of high frequency variations. Thus, this method does not provide real empty zones.

Figure 3.2 shows the evolution of the 2-dimensional fBm noise model as octaves are added to the results. In particular, we can observe the presence of high frequency details across the whole image, and the low range of values of the model.

<figure><img src="image_9.png" /><figcaption>Figure 3.2: Different 2-dimensional fBm patterns with increasing number of octaves. a) 1 octave (standard Perlin noise). b) 2 octaves. c) 3 octaves. d) 4 octaves. e) 5 octaves. 1) to 5) Corresponding value histograms.</figcaption></figure>

To find a solution to these drawbacks, we choose the Multiplicative Perlin noise instead.

### 3.1.2 Basic Multiplicative noise

The idea behind Multiplicative Perlin noise is similar to the fBm method, but instead of adding the octaves with a diminishing amplitude, the octaves are shifted into a positive range of values and multiplied together. Here is the basic process:

Multiplicative noise description:

* Generate each octave's Perlin noise, with a range of [-1; 1] and a mean of 0.

* Add 1 to each octave, to make the range positive only. The range is now [0; 2] and the mean is 1.

* Multiply all the octaves together to form the result.

Using this method, we expect the following results

* A range of [0; 2^n], with n being the number of octaves used.

* An expected value of 1, as the expected value product of two statically independent random variables is the product of the expected values of the two variables.
## Metrics

Figure 3.3 shows the results obtained using the basic Multiplicative Perlin noise with various number of octaves. Since the values are no longer in the [0; 1] range, the figure presents the results in two different ways. The first row shows the noise values normalized to a range [0; 1] for visualization purposes, while the second row displays the raw values of the noise. The histogram is computed from the raw values.

<figure><img src="image_10.png" /><figcaption>Figure 3.3: Multiplicative Perlin noise. a) 1 octave. b) 2 octaves. c) 3 octaves. d) 4 octaves. e) 5 octaves. 1) Multiplicative Perlin noise (scaled to fit in [0; 1] range for visualization). 2) Multiplicative Perlin noise. 3) Value Histogram</figcaption></figure>

From Figure 3.3, we can observe that the range of values is larger than what we obtained in Section 3.1.1 with the fBm method. The histogram shows that the distribution of values is no longer symmetric, and therefore that we are no longer restricted on the upper bound of the range, while the mean remains 1.

## Drawbacks

While the results obtained with Multiplicative Perlin noise are better than what we obtained with the fBm method, they still fall slightly short of our expectations in terms of the upper bound of the range, which is lower than the theoretical limit of $2^n$ of the multiplicative noise model.

These shortcomings can be explained by considering that, while Perlin noise is theoretically in the range [-1, 1], the noise rarely generates extreme values. Figure 3.4 shows the histogram of a Perlin noise instance (on the left) with red and green markings for the minimum and maximum values. It is very visible then that these bounds are far from the expected bounds of the Perlin noise.

We remind the reader that one of our goals for the final look of the model is to include empty zones. Since our model will describe the volume in terms of the density of the clouds in the spatial domain, we know that during the rendering process, the computation of the transmittance of the volume will involve summing a lot of density values along a ray. Given the scales of the objects involved in this study, this ray can be very long, and therefore even areas
of very low density can have an important impact on the transmittance. The lack of extreme values in the Perlin noise results in the lack of empty zones and high upper bound observed in the Multiplicative Perlin noise results. We will look in Section 3.2 at a solution to this issue.

<figure><img src="image_11.png" /><figcaption>Figure 3.4: Perlin noise histogram. Left: Perlin noise, scaled to range [0; 1] for visualization. Right: Value Histogram with minimum and maximum values markings. The red line marks the minimum value found in the left image, and the green line marks the maximum value</figcaption></figure>

## 3.2 Applying transformations on the octaves before the multiplication

Since we want to obtain empty zones in our model, we need to find a way to "force" the values of each octaves to contain enough values close to the bounds. One way to achieve this is to apply transfer functions to the noise before multiplying the octaves together. Since these functions will modify the final look of the model, they also are a source of control on the final look of the model. Passing a noise through functions to modify its look is a common occurrence in procedural generation, but we still want our model to abide to specific requirements of expected values and range, so we need to take some precautions when performing transformations to the noise. For example, applying the $1 - |x|$ function on a Perlin noise shifts the mean to the right (see Figure 3.5 for a visual example).

<figure><img src="image_12.png" /><figcaption>Figure 3.5: Mean value shift when applying $1 - |x|$ to a Perlin noise. Left: Perlin noise (Scaled to [0; 1] for visualization). Center: Perlin noise after the $1 - |x|$ function. Right: Histogram of the Perlin noise after transformation. The vertical red line marks the mean value of the image.</figcaption></figure>

In this section, we look at three different ways to approach this issue, each of them with different advantages and drawbacks.
### 3.2.1 Symmetric functions

Our first approach is to take advantage of the fact that the distribution of values for Perlin noise is symmetric around 0. This means that for any function $f(x)$ such that:

$$f(-x) = 2 * f(0) - f(x) \qquad (3.1)$$

Applying this function to a Perlin noise will transform the noise into a new distribution of values with a mean of $f(0)$. This means that we can easily use any functions following this rule to modify the noise before the multiplication, while keeping the mean of each octave in control.

#### Using the Logistic function to increase the contrast

With the previous rule in mind, we introduce the Logistic function to control the contrast of each octave. The Logistic function is defined by the following:

$$f(x) = \frac{L}{1 + e^{-k(x-x_0)}}$$

where $L$ is the carrying capacity (upper bound), $x_0$ is the $x$ value of the midpoint, and $k$ is the steepness of the curve. Figure 3.6 shows a graph of this function for a given set of parameters. We choose to use this function because it provides a very intuitive and smooth way of increasing the contrast by concentrating the values of the distribution near the upper and lower bounds.

We set the $x_0$ value to 0 as it is necessary for the Logistic function to follow the rule stated above. As such, we have:

$$\begin{aligned} f(-x) &= \frac{L}{1 + e^{kx}} \\ &= \frac{Le^{-kx}}{e^{-kx} + 1} \end{aligned} \qquad (3.2)$$

and:

$$\begin{aligned} 2f(0) - f(x) &= 2\frac{L}{1+e^0} - \frac{L}{1+e^{-kx}} \\ &= L - \frac{L}{1+e^{-kx}} \qquad (3.3) \\ &= \frac{Le^{-kx}}{e^{-kx} + 1} \end{aligned}$$

We can deduce from Equation 3.2 and Equation 3.3 that the Logistic function follows the rule stated above for every $L$ and $k$, assuming $x_0 = 0$.

Furthermore, since we want the mean of the resulting noise to be 1, we need to Logistic function to follow this rule:

$$f(0) = 1$$

This means that we need to fix the value of $L$, with $L = 2$ to guarantee this result. We can note that this is mathematically equivalent to letting the $L$ parameter variable and normalizing the result of the function by $f(0)$.
<figure><img src="image_13.png" /><figcaption>Figure 3.6: Graph of the Logistic function, with $L = 2$, $x_0 = 0$ and $k = 3.9$</figcaption></figure>

The $k$ parameter of the Logistic function is not restricted. Since this parameter controls the steepness of the curve, it can be used directly as a contrast parameter. Higher values will push the noise values toward the lower and upper bounds. The value of $k$ can be negative, but this has the same effect as using a positive value while multiplying the input noise by $-1$.

Figure 3.7 shows some results obtained with this new method. As we did before, the noise values are presented in two ways, the first row being normalized to fit the [0; 1] range and the second row displaying the raw values. We can observe that we have a lot more extreme values, which is much closer to our expected results. There are more empty zones, and the upper bound is higher.

<figure><img src="image_14.png" /><figcaption>Figure 3.7: Multiplicative Perlin noise with logistic function applied to individual octaves. a) 1 octave. b) 2 octaves. c) 4 octaves. d) 6 octaves. e) 8 octaves. 1) Noise result (scaled to fit in [0; 1] range for visualization). 2) Noise result. 3) Value Histogram</figcaption></figure>

However, despite the significant improvements on the previous model, this solution has its drawbacks. Mainly, this solution lacks in control and expressive range. While having simple
handles on the model can be a advantage for the user, it can often lead to a poor expressive range, where the user can't really shape the model how they need to. This is the case here, as the set of functions that abide to the rule in Equation 3.1 is very limited. This is due to the strictness of the rule itself. The next two solutions will focus on finding a solution by finding ways to compensate for the shift of expected values, instead of trying to avoid it.

### 3.2.2 Compensating for the shift of expected value

As stated previously, an ideal model would allow us to use any function to modify the noise, giving the user full control on how to shape the look of each octaves prior to blending them together in the multiplication. This requires us to find a solution to the shift of expected value, to ensure that every octave has a mean of 1. In this section, we look at partial solutions to this problem.

#### Perlin noise as a normal distribution

As we can observe from the histogram in Figure 3.4, Perlin noise is similar to a normal distribution. If we can find the parameters of this normal distribution, we can use an interesting property of the normal distribution: If X is a random variable following a normal distribution of mean $\mu$ and standard deviation $\sigma$, and $F(x)$ is the Cumulative Distribution Function (CDF) of this normal distribution, then $F(X)$ is a random variable following a continuous uniform distribution of range [0; 1] with a mean of 0.5. Applying transformations to this uniform distribution while maintaining the mean is much easier, as we can determine analytically the modification of the mean. Let $X$ be the random variable following the uniform distribution of range [0; 1], $f(x)$ a function we want to apply to the distribution, then mean of the new distribution $f(X)$ will be:

$$ \int_{0}^{1} f(x) dx \qquad (3.4) $$

We can use this to compensate the mean by dividing every value of the noise by the result of this integral. This model still makes two assumptions to guaranty a valid result:

* $f(x)$ should be positive on the range [0; 1], as a negative value is not valid for a density field.

* We need to be able to compute the value of Equation 3.4 for the given function $f(x)$

The next step is to find the parameters of the normal distribution which corresponds the best to Perlin noise. The $\mu$ parameter is straightforward, as the mean of the Perlin noise is 0. For the standard deviation $\sigma$, we estimate the value by randomly sampling a Perlin noise and computing the standard deviation of the obtained sample. Using this estimation, we get $\sigma \approx 0.1995$. Figure 3.8 shows the Perlin noise histogram with a Gaussian distribution using the parameters obtained above.

The normal distribution's Cumulative Distribution Function is obtained using the following formula:

$$ F(x) = \Phi \left( \frac{x - \mu}{\sigma} \right) = \frac{1}{2} \left[ 1 + \operatorname{erf} \left( \frac{x - \mu}{\sigma\sqrt{2}} \right) \right] \qquad (3.5) $$
<figure><img src="image_15.png" /><figcaption>Figure 3.8: Perlin noise value Histogram overlaid with a Gaussian of mean $\mu = 0$ and standard deviation $\sigma = 0.1995$.</figcaption></figure>

Where $\mu$ and $\sigma$ are the mean and standard deviation of the normal distribution, and $erf(x)$ is the error function, defined by the following formula:

$$ erf(x) = \frac{2}{\sqrt{\pi}} \int_{0}^{x} e^{-t^2} dt \qquad (3.6) $$

By applying the CDF of the normal distribution to the Perlin noise, we can obtain an approximation of a uniform distribution of range [0; 1]. Figure 3.9 presents the results of such a transformation.

However, we can still see on the right histogram of Figure 3.9 that the approximation of the uniform distribution. While the Perlin noise distribution is similar to a normal distribution, there are still differences between them. For example, the Perlin noise is bounded, which isn't the case of the normal distribution. Further work could be done to provide a better analysis of the correctness of the approximation of a Perlin noise by a normal distribution.

The ideal solution would be to determine the exact Cumulative Distribution Function of Perlin noise from its definition, but unfortunately we lacked the time to get a closer look in this direction.

## Estimating the shift of the expected value through sampling

Another solution to compensate the shift of expected value is to estimate it through sampling. The process is the following:

* Choose the function to apply to the noise. If the function requires extra parameters, they need to be set to a specific value before hand.

* Sample a Perlin noise and apply the function.
<figure><img src="image_16.png" /><figcaption>Figure 3.9: Transformation of Perlin noise into a uniform distribution. a) Perlin noise. b) Perlin noise after applying the normal distribution's CDF. 1) Noise. 2) Value Histogram.</figcaption></figure>

* Compute the mean of the obtained sample.

* Use the new mean to normalize any further use of the function on a Perlin noise

This solution is very straightforward and doesn't have any requirements, other than for the applied function to be positive. However, this method requires pre-computation, which makes its use in a procedural settings more complicated. For a function that do not require any external parameters, such as $1 - |x|$ for example, this pre-computation only needs to be done once. However, the situation is more complicated with more complex functions, which need to perform the estimation again every time a parameter is changed. Figure 3.10 illustrates this method with $f(x) = 1 - |x|$. The noise on the left is passed through $f(x)$ and then divided by a mean estimated previously on another sample. The resulting histogram on the right shows that the mean shift is properly compensated.

In this Chapter, we looked at three approaches to create a noise generation model conforming to the requirements and guidelines stated in Chapter 1. The first approach focused on working with transformations of the original Perlin noise while keeping the mean. The next two approaches attempted to find a way to compensate for the impact of the transformations on the mean, either by approximating the Perlin noise as a normal distribution to turn it into a uniform distribution, or by pre-computing the effect of the transformation through sampling. The next Chapter will provide details on the implementations used and discussions on the results.
<figure><img src="image_17.png" /><figcaption>Figure 3.10: Example of mean compensation through sampling. Left: Perlin noise (scaled to [0; 1] for visualization. Center: Perlin noise after applying $1 - |x|$ and compensating the mean with an estimate from a sample. Right: Histogram of the Perlin noise after transformation. The red line marks the mean of the resulting noise</figcaption></figure>
# Implementation and results

In this chapter, we give a more in-depth description of the implementations of the model used to test the different strategies and obtain the results used to illustrate the previous chapter. In addition, we will also give more examples and results from the different strategies.

Most of the tests and research was done using a 2-dimensional implementation of the models. It is worth noting that the goal is to extend the implementation to a 3-dimensional model more suited for the problem we want to solve. All of the strategies and results from Chapter 3 are independent of the dimension. However, the 2-dimensional implementation is easier to setup. Therefore, a 3-dimensional implementation was started but we lacked the time to get enough results.

## 4.0.1 2D implementation and analysis

### Implementation details

The 2-dimensional implementation was done using GNU Octave, Jupyter notebooks and Vulkan. The GNU Octave is a free and open-source programming language specifically designed for mathematical and scientific computing. It was chosen for its ease of use and for its efficient matrix manipulation tools and mathematical tools. Octave was used inside a Jupyter notebook, using Xeus-octave [16], an Octave kernel for Jupyter notebooks allowing the execution of Octave code in Jupyter notebooks code cells.

Since the model is intended to be used as part of a computer graphics rendering pipeline, and to leverage the computing capacities of a Graphics Processing Unit (GPU), the Perlin noise generation function was implemented using a Vulkan compute shader. The function was then exposed to the Octave code using the Octave's C++ extension API using an Oct file. This allowed the noise generation function to be called from the Octave code like any other Octave function. Appendix A contains a print of the notebook used as an example.

### Note on the Perlin noise implementation

There are many different implementations of Perlin noise available, with minor differences. Most implementations differ in the way the pseudo-random gradient of vectors is computed, with varying levels of correctness. The implementation used in our tests was provided by Mathéo Moinet, PhD student in the team. This implementation uses a pseudo-random gradient generation method from a scratchpixel.com course [11], and a hash function from a Journal
of Computer Graphics Techniques publication by Jarzynski et al. [1]. Furthermore, it uses a normalization factor as presented in this digitalfreepen.com post [12].

## Control of the visual aspect: Filaments

In Chapter 3, as shown in Figure ?? and Figure 3.10, we use the $1 - |x|$ transformation as an example. We can observe from the results that such a transformation has a tendency to bring a filament-like pattern from the Perlin noise. This type of transformation could be useful for obtaining the type of patterns observed in Figure 1.2. However, while this pattern appears correct in 2 dimensions, the first tests seem to indicate that the intuition doesn't transfer as easily to 3 dimensions, and more work is needed to obtain a correct visual representation in a 3-dimensional context.

As mentioned in Chapter 1

## 4.0.2 3D implementation and analysis

### Implementation details

A 3-dimensional implementation of the models we developed in Chapter 3 was started using the Vulkan compute shader developed for the 2-dimensional implementation extended to include a basic Ray-Marching algorithm (see 2.1.2), and set to run inside the Godot game engine [13]. The Godot game engine was selected for its easy integration of Vulkan compute shader in its rendering pipeline, paired with an easy setup for capturing user inputs and power user interface creation tools.

However, this implementation could not be finished in time to obtain meaningful results.
5

**Conclusion**

In this work, we tasked ourselves with designing a new procedural noise generation model to find a solution to the issues involved with mimicking spatial dust and gas clouds when it comes to real-time rendering of space objects such as galaxies and nebulae. Based on this goal, we defined several requirements that our model needed to meet, both in terms of mathematical and qualitative properties, and used these requirements to create a model based on the Perlin noise.

During this work, we discussed three methods to design a model following the expected requirements, all based on the Multiplicative Perlin noise. The first approach used the symmetric property of Perlin noise to justify the use of functions following a specific rule to apply transformations to the individual layers of the Multiplicative Perlin noise. We observed that despite its advantages brought by its strict correctness, this method lacked expression range and fine control on the resulting look of the noise model. The second approach used an approximation of Perlin noise as a normal distribution to create a uniform distribution based on the Perlin noise. We showed that, while this method allowed for a greater expressive range than the previous approach, the approximation of Perlin noise by a normal distribution isn't as accurate as it seems. Further work into determining the true quality of the approximation, and possibly even defining a stricter Cumulative Distribution Function of Perlin noise could improve this approach. The last approach used estimation through sampling to compensate for the impact on the mean value of applying any function to the noise. This method also showed promising results but restrict the procedural capabilities of the model.

There are several ways our work could be extended in the future. First of all, we already discussed the possible improvements of the second method. In addition, there is still work to be done to extend and test those approaches in a 3-dimensional context. Even if the results obtained in this paper are mostly dimension-independent, there could still be a noticeable variation of the visual aspect of the models between 2-dimensions and 3-dimensions. On a more general note for the future, when it comes to the rendering aspect and integration into existing rendering models, there are still a lot of questions to be answered. First of all, no matter the approach, our current model is isotropic, meaning that the properties of the noise do not depend of the direction in which we observe it. This is usually not the case in the real dust and gas clouds we want to represent, so the model still needs to be modified. For example, work by Moinet [5] presents a way to sculpt isotropic noises into anisotropic noises. Another way would be to experiment with anisotropic noise generation models such as Gabor noise [3], but these models are usually much more computation-heavy and will come in conflict with the real-time requirements of our model. Furthermore, there is still work to be done when it comes to the lighting aspect of rendering, which is a hard topic in our particular case. The clouds are
lit from the inside by thousands of different light sources, which makes computing an accurate
light model a real challenge for a real-time rendering application.
A

Appendix
# clound-generator

June 17, 2025

## 0.1 Parameters of the models

Constant parameters set for the models. - N: Number of octaves used by the models - scale_offset: Scale of the first octave, input is used as a power of 2. Each octave i has a scale of $2^{i+\text{offset}}$.

```
[1]: # Parameters
N = 10; # Number of octaves
scale_offset = 1; # Scale exponent of the first octave

# Precomputed values
n = scale_offset:N+scale_offset;
scales = 2.^n; # Scales of each octave
```

## 0.2 Functions used for this notebook

This section contains the definitions and implementations of functions used in the notebook.

### 0.2.1 Logistic function

$$f(x) = \frac{L}{1 + e^{-k(x-x_0)}}$$

```
[2]: functiononto = logistic(x, L, k, x0)
onto = (1 ./ (1 + exp(-k .* (x - x0)))).*L;
endfunction
```

#### Smoothstep function

Original smooth(er)step function suggested by Ken Perlin

$$f(x) = \begin{cases} 0, & x < 0 \\ 6x^5 - 15x^4 + 10x^3, & 0 \le x \le 1 \\ 1, & x > 1 \end{cases}$$

Extended to add a range parameter $[a, b]$

$$f(x) = \begin{cases} 0, & x < a \\ 6\left(\frac{x-a}{b-a}\right)^5 - 15\left(\frac{x-a}{b-a}\right)^4 + 10\left(\frac{x-a}{b-a}\right)^3, & a \le x \le b \\ 1, & x > b \end{cases}$$
[3]: function retval = smoothstep(x, a, b) {
    retval = zeros(size(x));
    retval(x >= b) = 1.;
    retval(x > a & x < b) = 6.*(((x(x>a & x<b)-a)/(b-a)).^5) - 15.*\Box
    ↪→(((x(x>a & x<b)-a)/(b-a)).^4) + 10.*(((x(x>a & x<b)-a)/(b-a)).^3);
    endfunction

### Controllable $e^{-x}$

$$f(x) = \left(A - \frac{B - Ae^{-x_0}}{1 - e^{-x_0}}\right) e^{-x} + \left(\frac{B - Ae^{-x_0}}{1 - e^{-x_0}}\right)$$

Controlable $e^{-x}$ function where A is the value of $f(0)$ and B is the value of $f(x_0)$.

[4]: function retval = range_exp(x, A, B, x0) {
    offset = (B - A * exp(-x0)) / (1 - exp(-x0));
    retval = zeros(size(x));
    retval = (A-offset) .* exp(-x) + (offset);
    endfunction

### 0.2.2 Gaussian

$$G(x) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}$$

Probability Distribution Function for a normal distribution of mean $\mu$ and standard deviation $\sigma$.

[5]: function retval = gaussian(x, mu, sigma) {
    factor = 1 / sqrt(2 * pi * (sigma^2));
    retval = zeros(size(x));
    retval = exp(-1.*((x - mu) .^ 2) ./ (2 * (sigma^2))) .* factor;
    endfunction

### 0.2.3 Normal distribution CDF

Cumulative distribution function of a normal distribution of mean $\mu = 0$ and standard deviation $\sigma$.

$$F(x) = \phi\left(\frac{x}{\sigma}\right) = \frac{1}{2}\left[1 + erf\left(\frac{x}{\sigma\sqrt{2}}\right)\right]$$

[6]: function retval = cdf(x, sd) {
    retval = 0.5.* (1 + erf(x ./ (sd * sqrt(2))));
    endfunction
### 0.2.4 Gaussian-sin splat

$$f_{g,s}(x) = g * G(x) * \sin(s * x)$$

with a maximum value of $f(x_{max})$ for

$$x_{max} = \frac{\pi}{2 * s}$$

maximum value of 1 is achieved for

$$g = \frac{1}{G(x_{max})}$$

### 0.2.5 Normalized $1 - |x|$

Compute $1 - |x|$ and normalize the result by the resulting (empirical) mean.

```matlab
[7]: function retval = inv_abs_emp(x)
        # retval = zeros(size(x));
        retval = 1 - abs(x);
        m = mean retval(:);
        retval = retval ./ m;
    endfunction
```

### 0.2.6 Slab Opacity

Computes the transmittance of the density field, considering a depth $d$, using the Beer's law.

$$f_d(x) = e^{-d*x}$$

```matlab
[8]: function retval = opacity(x, d)
        retval = zeros(size(x));
        retval = exp(-d.*x);
    endfunction
```

### 0.2.7 Base noises

Compute and stores N noise textures with different scales to be used as inputs of the different models. Noises are in range [0; 1].

```matlab
[9]: noises = {}
    for i = 1:N
        noises{i} = noise(scales(i));
    endfor
```

```matlab
noises = {}(0x0)
```

```
Device Name : NVIDIA GeForce GTX 1080 Ti
```

```
Vulkan Version : 1.4.303
```

```
Memory Type Index: 8
```

```
Memory Heap Size : 23 GB
```
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB

## 0.3 Models

### 0.3.1 Fractional Brownian Motion

[10]: fBm = zeros(size(noises{1}));

for i = 1:N
    fBm = fBm + ((noises{i} .* 0.5 + 0.5) ./ (2^(i-1)));
endfor
imshow(fBm ./ 2)

<figure><img src="image_18.png" /></figure>

[11]: [nn, xx] = hist(fBm(:), 50, 1);
bar(xx, nn, 0.5);

hold on
x = mean(fBm(:));
y = max nn;
line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 3)
<figure><img src="image_19.png" /></figure>

### 0.3.2 Basic Multiplicative Perlin noise

Raw data computation:

```matlab
[12]: mult_perlin = ones(size(noises{1}));

for i = 1:N
    mult_perlin = mult_perlin .* (noises{i} + 1);
endfor

.imshow(mult_perlin)
```
<figure><img src="image_20.png" /></figure>

Normalized values for preview:

[13]: mult_perlin_max = max(mult_perlin(:));
imshow(mult_perlin ./ mult_perlin_max);
<figure><img src="image_21.png" /></figure>

0.3.3 Logistic model

[14]: [nn, xx] = hist(mult_perlin(:), 50, 1);

bar(xx, nn, 0.5);

hold on

x = mean(mult_perlin(:))
y = max nn;

line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 3)
<figure><img src="image_22.png" /></figure>

x = 0.9534

[15]: k = 10;
noises Contrast = {};

for i = 1:N
    noisescontrast{i} = logistic(noises{i}, 2, k, 0);
endfor

logistic_perlin = ones(size(noisescontrast{1}));

for i = 1:N
    logistic_perlin = logistic_perlin .* (noisescontrast{i});
endfor

mean(logistic_perlin(:));

imshow(logistic_perlin)
<figure><img src="image_23.png" /></figure>

Normalized values for preview:

[16]: logistic_perlin_max = max(logistic_perlin(:));
imshow(logistic_perlin ./ logistic_perlin_max);
<figure><img src="image_24.png" /></figure>

[17]: [nn, xx] = hist(logistic_perlin(:), 50, 1);

bar(xx, nn, 0.5);

hold on

x = mean(logistic_perlin(:));
y = max nn;

line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 1)
<figure><img src="image_25.png" /></figure>

### 0.3.4 Cumulative Distribution Function approximation

[18]: perlin=noise(20);
imshow(perlin .* 0.5 + 0.5)

Device Name : NVIDIA GeForce GTX 1080 Ti

Vulkan Version : 1.4.303

Memory Type Index: 8

Memory Heap Size : 23 GB
<figure><img src="image_26.png" /></figure>

[19]: [nn, xx] = hist(perlin(:), 50, 1);

bar(xx, nn, 0.5);

hold on

x = mean(perlin(:));
y = max nn;

line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 3)
<figure><img src="image_27.png" /></figure>

[20]: perlinUniform = cdf(perlin, 0.1996);
illing (perlinUniform)
<figure><img src="image_28.png" /></figure>

[21]: [nn, xx] = hist(perlinUniform(:), 50, 1);

bar(xx, nn, 0.5);

hold on

x = mean(perlinUniform(:));
y = max nn;

line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 3)
<figure><img src="image_29.png" /></figure>

### 0.3.5 Mean estimation through sampling

Example tested on $f(x) = 1 - |x|$. We take a first Perlin noise texture, apply the transformation and compute the mean. We then test the result on a second texture and plot the histogram.

[22]:
perlin = noise(20);
perlin_abs_mean = mean((1-abs(perlin))(:))

Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB
perlin_abs_mean = 0.8404

Perlin texture after the transformation but before the compensation:

[23]:
perlin_abs_test = 1-abs(noise(20));

imshow(perlin_abs_test)

Device Name : NVIDIA GeForce GTX 1080 Ti
Vulkan Version : 1.4.303
Memory Type Index: 8
Memory Heap Size : 23 GB

<figure><img src="image_30.png" /></figure>

[24]: [nn, xx] = hist(perlin_abs_test(:), 50, 1);

bar(xx, nn, 0.5);

hold on

x = mean(perlin_abs_test(:));
y = max nn;

line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 3)
<figure><img src="image_31.png" /></figure>

After compensation with the mean estimated above:

[25]: perlin_abs_test_comp = perlin_abs_test ./ perlin_abs_mean;

imshow(perlin_abs_test_comp)
<figure><img src="image_32.png" /></figure>

[26]: [nn, xx] = hist(perlin_abs_test_comp(:), 50, 1);

bar(xx, nn, 0.5);

hold on

x = mean(perlin_abs_test_comp(:));
y = max nn;

line ("color", "red", "xdata", [x,x], "ydata", [0,y], "linewidth", 3)
<figure><img src="image_33.png" /></figure>
<figure><img src="image_34.png" /></figure>
# Bibliography

[1] Mark Jarzynski and Marc Olano. Hash functions for gpu rendering. *Journal of Computer Graphics Techniques (JCGT)*, 9(3):20–38, October 2020.

[2] Ares Lagae, Sylvain Lefebvre, Rob Cook, Tony DeRose, George Drettakis, David S Ebert, John P Lewis, Ken Perlin, and Matthias Zwicker. A survey of procedural noise functions. In *Computer Graphics Forum*, volume 29, pages 2579–2600. Wiley Online Library, 2010.

[3] Ares Lagae, Sylvain Lefebvre, George Drettakis, and Philip Dutré. Procedural noise using sparse gabor convolution. *ACM Transactions on Graphics (TOG)*, 28(3):1–10, 2009.

[4] Guillaume Loubet. Rapport de projet de fin d'études, 2014.

[5] Mathéo Moinet. Sculpting procedural noise for real-time visualization of astronomical objects. Master's thesis, ENSIMAG ; UGA (Université Grenoble Alpes), June 2023.

[6] F Kenton Musgrave. Procedural fractal terrains. *Texturing & Modeling: A Procedural Approach*, pages 489–506, 2003.

[7] Ken Perlin. An image synthesizer. *ACM Siggraph Computer Graphics*, 19(3):287–296, 1985.

[8] Ken Perlin. Improving noise. In *Proceedings of the 29th annual conference on Computer graphics and interactive techniques*, pages 681–682, 2002.

[9] Ken Perlin and Eric M Hoffert. Hypertexture. In *Proceedings of the 16th annual conference on Computer graphics and interactive techniques*, pages 253–262, 1989.

[10] Andrew Schneider. The real-time volumetric cloudscapes of horizon: Zero dawn. SIGGRAPH 2015 Course - Advances in Real-time Rendering in Games, 2015.

## Internet sources

[11] https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/perlin-noise-part-2/perlin-noise.html

[12] https://digitalfrepen.com/2017/06/20/range-perlin-noise.html

[13] https://godotengine.org

[14] https://minecraft.wiki/w/World_generation

[15] https://science.nasa.gov/mission/hubble/science/universe-uncovered/hubble-galaxies/

[16] https://github.com/jupyter-xeus/xeus-octave/