Noise is a function that returns a float in the range [0:1] for a certain input x (x can be a float, a 2D, 3D or 4D point but in this chapter we will only be looking at the one dimensional case. Our input position can also be positive and negative and extend
from 0 to infinity or minus infinity). We could generate these floats using a random number generator but we have explained in the first chapter that it returns values which are very different from each other each time the function is called. This creates
a pattern known as white noise which is not suitable for texture generation (white noise patterns are not smooth while most natural patterns in nature are).
Noise 是这样一个函数:它返回一个值域范围在[0:1]之间的值,函数的输入参数x可以是一个 float ,或者 vector2, vector3, vector4.
在本章节,我们只讨论一维的情况. 作为输入的值(也可以称为坐标)的范围没有限制,从负无穷到正无穷。我们可以用一个纯随机函数来生成输入数据。但是这个函数就如同第一章所分析的那样,每次调用它返回的结果是完全没有相关性的(样本平均分布)。具有此特性的数据集合我们称之为white noise (白噪音),white noise不适合用来生成贴图,因为各数据之间没有平滑性,而自然界大多数模式是有平滑性的。
What we will do instead is create a series of random points (using drand48()) spaced at regular intervals. If you work in 2D we create these random values on the vertices of a regular grid (lattice) and if you work in 1D this grid can be seen as a ruler. To make things simple, we will assume that the vertices of that grid or the ticks on that ruler are created at coordinates along the x and y axis which have integer values (0, 1, 2, 3, etc.). These random numbers are generated at these positions only once (when the noise function is initialised).
接下来我们创建一系列随机的值(用 drand48() ),这些值在一维坐标上的分布是等间距的。(*这也能叫一维...)
如果你是在处理2D输入(来自x,y两个维度的值),你可以为一个二维网格上每个点生成一个随机值。
为了简化,我们只在整数坐标上生成随机数(如x=1,2,3); 这些随机数只在noise函数初始化的时候生成一次。
PS:在生成原始噪点时,加上不同的约束,可以影响最终生成的图像。
图9: 如果我们创建一个2Dnoise函数,我们需要给网格(如上面那张图)上的顶点赋初始噪点值.对于1D noise函数,我们给x的整数坐标分别指定初始值。 在这里,我们对于两种情况都只处理整数坐标。在这个例子里,我们只随了10个值,分别对应坐标上的0-9。 格子里就是随机的值。为了方便看,我们把表示noise值的轴放大了。
ps: 绿点是原始噪点值.
If we continue with our 1D example, we can see that we are now left with a series of dots or values defined at the integer position on the ruler. For example the result of the function for x = 0 is 0.36, the result for x = 1 is 0.68, etc. But what's the result of that function when x is not an integer ? To compute a value for any point on the x axis all we need to do is to find out the two nearest integer positions on the x axis for this input value (the minimum and the maximum) and use the random values that are associated with these two positions. For instance if x equals 0.5, the two points with integer values surrounding x are 0 and 1. And these two points have the associated random values 0.36 and 0.68. The result of the function for x when x = 0.5, is a mix of the value 0.36 defined at point 1 and the value 0.68 defined at point 2. To compute this number we can use a simple interpolation technique called linear interpolation. Linear interpolation is a simple function that returns a mix of the values a and b, for a certain value t, where t is in the range [0:1]:
现在 x = 0时, noise = 0.36 , x = 1 , noise = 0.68。对于 x = 0.5怎么办? 我们的做法是:做插值.
Noise(x) = Lerp( Noise(floor(x), Noise(ceiling(x)), x - floor(x)) 。
线性插值公式: f(t) = a*(1-t) + b*(t); t的取值范围是 [0,1]。
x=1.2
a = floor(x) = 1
b = ceiling(x) = 2
dt = 1.2 - floor(x) = 0.2
noise(x) = noise(1) * (1 - dt) + noise(2) * dt
= 0.68 * 0.8 + 0.11 * 0.2 = 0.566
这里我用了上取整 下取整函数。
事实上,比如我们的随机样本长度是10.那么可以将任意坐标映射到x=[0,10]
例如,size是输入数据实际的个数,那么在处理第i个元素的时候,其坐标可以表示为 (i/size) * size ,一般我们称 i/size 这个为uniform coordinate(坐标), 用这个coor 再去乘以noise函数定义的处理范围,比如这里的10,就可以得到一个映射后的x值,然后x前后的原始噪点值也确定了,就可以插值了。
In the case of our noise function, we will replace a and b with the random values defined at the integer positions surrounding x (if x = 1.2, the random values for x = 1 and x = 2), and we will compute t from x, by simply subtracting the minimum integer value found for x from x itself (t = 1.2 - 1 = 0.2).
没啥要说的...
int xMin = (int)x; float t = x - xMin;
float Mix( const float &a, const float &b, const float &t ) { return a * ( 1 - t ) + b * t; }
The mix function is usually known as the Lerp (for linear interpolation) function by CG programmers. If you find a Lerp function in the source code of a renderer or mentioned in a book you should know that it is the same thing as the mix function used here.
如果你在其他书里看到 Lerp 这个函数,其实就是线性插值函数,和这里的 Mix函数是一回事。
Computing a value for any x in the range [0:1] using linear interpolation is similar to drawing a line from point 1 to point 2 (figure xx, left). If we repeat this process for all the points in the range [1:2], [2:3] and so on, we get the curve from figure 11. You may understand now why we call this type of noise value noise (noise can be created in a few different other ways). The idea is to create some values at regular intervals on a ruler (1D) or a grid (2D) and interpolate them using linear interpolation.
如果得到所有在[0,1]之间的x对应的noise值,就像是将 noise(0) 和 noise(1)之间连一条线。同样的[1:2], [2:3],也是连线。这样我们就可以连出一条线序曲线。
现在你也许已经理解到我们为什么叫这种 noise 为 value noise (PS:因为noise代表了一个值)( noise 有时会用来代指一些不常用的含义)(PS:也许是说不是数字或标量,可能是别的什么玩意.)
生成受控noise的 主要思路 就是(1D:等间距坐标点上, 2D:网格顶点上) 先预先生成噪点,然后利用插值来完成任意点的噪声值计算。
We now have all the bits and pieces we need for creating a very simple noise function. When initialising the function, we will create a series of random values which we will store in a float array (the numbers written in boxes at the bottom of figure 9). As you can see in the code, the length of the array can easily be changed. This will be important later, but for now, to keep the demonstration simple, we will only create 10 values (from the origin 0, to 9). The index of a number in the array corresponds to its position on the ruler. The first number in the array correspond to x = 0, the second to x = 1, etc. To compute a noise value for x, we will first compute the integer boundaries for x (the minimum and the maximum integer value for x). We can then use these two integer values as index positions in the array storing the random numbers. The two numbers we get, a and b, are the two random values stored at these index positions. We also need to find t from x using the technique described above (subtract the minimum integer for x from x). The final step is to perform a linear interpolation of a and b using t by calling the mix function. And you get the result of your noise for x.
有了上述知识点,我们可以实现一个非常简单的noise生成函数了。在初始化阶段,我们人为指定一组数,保存在一个数组里(数值如图9所示)。 本来这个数组的长度可以随意指定,为了概念上简单,我们只使用10个数。数组下标对应坐标刻度。我们使用之前提到的方法计算任意x,[0:10]的噪音值。
class Simple1DNoiseA { public: Simple1DNoiseA( unsigned seed = 2011 ) { srand48( seed ); for ( unsigned i = 0; i < kMaxVertices; ++i ) { r[ i ] = drand48(); } } /// Evaluate the noise function at position x float eval( const float &x ) { int xMin = (int)x; assert( xMin <= kMaxVertices - 1 ); float t = x - xMin; return Mix( r[ xMin ], r[ xMin + 1 ], t ); } static const unsigned kMaxVertices = 10; float r[ kMaxVertices ]; }; int main( int argc, char **argv ) { Simple1DNoiseA simple1DNoiseA; static const int numSteps = 200; for ( int i = 0; i < numSteps; ++i ) { float x = i / float( numSteps - 1 ) * 9; std::cout << "Noise at " << x << ": " << simple1DNoiseA.eval( x ) << std::endl; } return 0; }
We only have 10 random values defined at each integer position on the x axis starting at x = 0 so we can only compute a value for any x in the range [0:10]. Why [0:10] instead of [0:9] ? When x is in the range [9:10] we will use the random value at index 9 and at index 0 to compute a noise value. As you can see in figure 13, if you do this, the beginning and the end of the curve are the same. In other words noise when x = 0 and when x = 10 is the same (in our example 0.36). Lets make a copy of the curve and move it to the left or to the right of the existing one. The existing curve (curve 1) is defined over the range [0:10] and the new copy (curve 2) is defined over the range [10:20].
我们只使用[0:10],为什么不用[0:9]呢?如果x in [9:10],我们就使用 a = noise(9) , b = noise(0) 来进行插值. 这样 noise(0) == noise(10) ,从曲线角度上看,[0:10] 和 [10:20] 就首尾相连了。这种模式可以推广到整个一维坐标。
图12:为了让我们的noise函数呈现周期性变化,我们需要让曲线的首尾值一致,这样计算任意位置的噪音值的函数就是连续的。如果是不连续的函数,会在一个值上产生二义性,甚至会导致某些插值方法产生无穷大的值(PS:或者说截断,这在地形生成中会产生极不自然的断崖。)
You can see that there is no discontinuities where the curves join (for x = 10). Why ? because the noise value at the end of curve 1 is the same as the noise value at the start of curve 2. And that's because when x is in the range [9:10] we interpolate the random value for x = 9 and x = 0. Because there is no discontinuities between successful copies of the curve, we can make as many copies as we want and extend our noise function to infinity. The value for x is not limited anymore to the range [0:10]. It can take any positive or negative values going from 0 to infinity (or minus infinity. The noise function should work for negative values).
How do we make that possible in the code ? We know how to compute a noise value when x is in the range [0:9]. However when x is greater than 9 (and the same thing applies for the case where x is negative), lets say 9.35, we want to interpolate the random position at x = 9 and x = 0 as explained above. If we take the minimum and maximum integer for x we get 9 and 10. But instead of using 10 we want to use 0. What we need here is the modulo operator. The modulo operator gives theremainder of a number divided by another number (in our case the reminder of x divided by 10). If you do the math, the remainder of 9 divided by 10 is 9. And the remainder of 10 divided by 10 is 0. In other words using the modulo operator on the minimum and maximum integer value for x = 9.35, gives us 9 and 0 which is exactly what we want. And if you do the test you will see that using this operator always returns the right integer boundaries for any x greater than 10 or lower than 0 (special care muse be taken for negative values but the principle is the same).
Using this technique we can repeatedly cycle over the noise functions as we move along the x axis (which is similar to making copies of the original curve). We mentioned in the first section that the noise function was periodic. In this case, the period of the function is 10 (it repeats itself every 10 units on each side of the origin for negative and positive values of x). We will be using the modulo operator for now (% in C++) but later on we will see how this can be simplified and made more efficient (be aware that the following code does not work for values of x lower than 0. This restriction will be removed in the last version of this code):