
Note on "Ray Tracing The Next Week" - Part 4

The code is compiled with cmake, please check out CMakeLists.txt
icon-padding
I didn’t follow the article in this part.
Solid textures and Perlin noises
One common way to generate solid texture is to use Perlin noises, which is a noise generation algorithm that generates noises that looks like blurred white noise. The image below is a sample Perlin noise texture:
Image from Wikipedia
To generate Perlin noise in , we divide the surface into grids, assign a gradient vector for each grid vertex, and compute the dot product of the vector for the grid vertex and the distance vector of the point relative to the vertex to determine the value on that point. Then, we do an interpolation between the dot product result to make the noise smoother.
In , the process is similar, except we need to take the dot product 8 times.
icon-padding
Cold knowledge: Perlin noise is defined for any finite dimension, with time complexity of
Our class definition will be this:
MathUtil.h
1 |
|
icon-padding
The permutation table is the one Perlin used in his original paper, repeated twice to prevent integer overflow
First we do the hashing with the given perm lookup table:
MathUtil.cpp
1 | double x = p[0], y = p[1], z = p[2]; |
We first extract the integer part of the coordinate, take bitwise AND so it’s always less than 255. Then we extract the decimal part of the original value to find the relative position inside the block. Then we take hash with the integer coordinate. Here we are doing coordinate plus 1 because we need to find 8 perms that belongs to each vertex of the block(one element in 3d grid), and we assume the grid size is 1, so we plus 1 to find another vertex.
Now we generate the gradient vector in each vertex and find the dot product of each. One thing that is a little different from the Wikipedia implementation is that in the paper Improving noise by Perlin himself, he used an optimized approach. Instead of using a randomly generated random vector, he just used the vector that defined by the direction from the center to the edges:
We can implement that with an intuitive way:
MathUtil.cpp
1 | double Perlin::gradientDotProd(int hash, const AppleMath::Vector3 &pt) const { |
Then we do the dot product between the gradient and the distance vector, with the direction determined by the hashed value:
MathUtil.cpp
1 | double Perlin::noise(const Point3& p) const { |
Here we are doing grid - 1 to find the distance between dot and vertex. It cannot be 1 - grid because with 1 - grid, the result vector will be pointing from the input point to the corner, which is the opposite vector.
Before interpolation, we need to process our lerp weight value from linear to a curve. We do that in our fade
function, with Perlin’s original implementation, which is this formula:
And this code:
MathUtil.cpp
1 | double Perlin::fade(double x) const { |
Now we can implement the lerp function to smoothen the value:
MathUtil.cpp
1 | double Perlin::lerp(double begin, double end, double weight) const { |
Now all functions have been implemented, we can put all pieces together, and get the final part done:
MathUtil.cpp
1 | double Perlin::noise(const Point3 &p) const { |
Here we are doing a trilinear interpolation to get the interpolated value in . We first interpolate four values on the four axes that lies on xy-plane, then we interpolate the pairs that on the same vertical plane, finally we interpolate these two values to find the final value.
Note that the range of this interpolation since we have both positive and negative value. We can map that range to by + 1 then / 2.
Here is the final code:
MathUtil.h
1 | class Perlin { |
MathUtil.cpp
1 | double Perlin::gradientDotProd(int hash, const AppleMath::Vector3 &pt) const { |
Now let’s create a texture with it, make a two ball scene real quick, and apply that in the main function:
Texture.h
1 | class NoiseTexture : public ITexture { |
Texture.cpp
1 | Color NoiseTexture::value(double u, double v, const Point3& p) const |
scenes.cpp
1 | void perlinSpheres() |
main.cpp
1 |
|
and we get this image:
Tweaking the noise
We can use some parameters to tweak the outcome of the noise function. Four common ways are frequency, octave, amplitude, and persistence. Frequency is how fast the light and dark alternate, in an intuitive way. Octave is a method that generates multiple Perlin noise layers and add them together with a given weight. Amplitude is how large the texture fluctuates, which will initially be a value in and decrease over the course of octave. And persistence is the decreasing factor of amplitude when using octave to get noise pattern.
First we need to modify our rawNoise
function, since right now it returns value between , which will only increase the value if we add them up, but we want the raw value to be between , so we can properly do octave. We do this by removing the modification of the value at the end of the rawNoise
function:
MathUtil.cpp
1 | double Perlin::rawNoise(const Point3 &p) const { |
We will add octaveNoise
to the Perlin
class to implement the parameterized control over the generated Perlin noise:
MathUtil.h
1 | class Perlin { |
MathUtil.cpp
1 | double Perlin::octaveNoise(const Point3& p, double frequency, int octave_count, double persistence) const { |
Texture.h
1 | class NoiseTexture : public ITexture { |
And apply the octave noise to the value:
Texture.cpp
1 | Color NoiseTexture::value(double u, double v, const Point3 &p) const { |
Finally, modify the value in the scene file:
1 | void perlinSpheres() |
and we get a dark marble like texture:
And if we change the parameter, making the frequency bigger, persistence smaller, we will get a denser image with brighter area:
frequency 0.5, octave count 10, persistence 0.5
Normally, we use Perlin noise with modification by using the output of the noise function as input of another function, and use that function as the color function. One way we can manipulate the Perlin noise function is to use sine function to generate periodic texture. We can do it in the value
function:
Texture.cpp
1 |
|
The result is a texture that looks like white marble:
More application
Perlin noise has applications way beyond generating solid textures. For example, The terrain generation of many game uses Perlin noise. We can simulate that by doing a colorization of a sphere with certain return value of octave function corresponding to a color.
Let’s make a TerrainTexture
. The definition is almost the same with NoiseTexture
, but we are doing different colorization:
Texture.h
1 | class TerrainTexture : public ITexture { |
Texture.cpp
1 | Color TerrainTexture::value(double u, double v, const Point3 &p) const { |
Here, the return value of the noise function becomes the height. When the height is below 0, we treat that as ocean and make it blue. If a small portion of land is around the sea, it’s beach. The other are land and plant, colored in green. With the height, all colorization (except beach) are made with gradient.
Then we can make a scene called terrain
:
scenes.cpp
1 | void terrain() |
and modify the main function:
main.cpp
1 |
|
And the result looks good:
we can tweak our parameter a little bit, and we will get different terrains:
changed frequency from 0.2 to 0.5
associated code
code can be found by this link