本文已参与[新人创作礼]活动,一起开启掘金创作之路
图形学的数学基础(三十九):噪声(上)
未经允许禁止转载,如需转载请注明出处。
本章将介绍噪声的基础内容,包括噪声是什么,它的属性以及可以用来做什么。噪声不是一个难以理解的复杂概念,但它有许多微妙之处。 正确使用它需要了解它的工作原理和制作方式。 为了创建一些图像并使用各种参数进行实验,我们将实现一个简单(但功能齐全)的噪声版本,称为。 本章我们将忽略许多过于复杂而无法在此处全面研究的技术,只是对噪声及其一些应用的初步介绍。
历史背景
噪声是在80年代中期发明的,起初是作为图像纹理的替代方法。主要原因在于80年代中期的电脑内存有限,无法容纳用于纹理的图片,于是人们开始寻找替代解决方案。用纯色渲染物体看起来太干净了,需要通过调整物体的表面材质属性来打破这种干净的外观。在编程中,我们通常在需要创建随机数时使用随机数生成器。然后使用是远远不够的。我们在自然界中观察到的随机模式通常是很自然的,物体表面上距离很近的两个点通常看起来也是很相似的。但是同一物体上相距很远的两个点却有很大不同。换句话说:局部变化是细微的,是渐进的,而全局变化则是很大的。无法满足这个要求,因为每次调用随机数生成器它会返回和其它数值完全不相干的随机数。因此调用这个函数很可能产生两个完全不同的数字,这将不适合对空间上两个接近点的视觉引入轻微渐进的变化。下面是一个例子:让我们观察一块真实岩石的图像,假设我们的让你无是创建一个再现该物体外观的CG图像。这个例子很有趣,因为我们可以看到岩石图案由三种颜色组成:绿色,粉色和灰色。这些颜色或多或少分布在岩石表面上。我们首先使用随机数生成器的版本:
void GenerateRandPattern()
{
unsigned imageWidth, imageHeight;
imageWidth = imageHeight = 512;
static const unsigned kNumColors = 3;
Color3f rockColors[ kNumColors ] = {
{ 0.4078, 0.4078, 0.3764 },
{ 0.7606, 0.6274, 0.6313 },
{ 0.8980, 0.9372, 0.9725 } };
std::ofstream ofs( "./rockpattern.ppm" );
ofs << "P6\n" << imageWidth << " " << imageHeight << "\n255\n";
for ( int j = 0; j < imageWidth; ++j )
{
for ( int i = 0; i < imageHeight; ++i )
{
unsigned colorIndex = std::min( unsigned( drand48 () * kNumColors ), kNumColors - 1 );
ofs << uchar( rockColors[ colorIndex ][ 0 ] * 255 ) <<
uchar( rockColors[ colorIndex ][ 1 ] * 255 ) <<
uchar( rockColors[ colorIndex ][ 2 ] * 255 );
}
}
ofs.close();
}
上图中间的图像是程序生成的,结果并不理想,实际上这个模式有个名字,白噪声()(稍后会解释什么是白噪声)。使用为程序生成的纹理的每个像素选择一种颜色,使得结果中每个像素的颜色值变化很大。为了改进结果,我们复制了纹理的一小块区域(10x10限速),将其调整到原始图像的尺寸(256x256),进行高斯模糊处理,生成的图像如上右所示,局部有了较小的变化,而全局变化比较明显。
这个实验的结论告诉我们,为了创建平滑的随机图案,需要在网格的固定位置上使用分配随机数,然后使用高斯模糊来模糊这些值。下章将详细介绍如何对随机数做模糊,现在只需要记住:噪声(在图形学上下文)是一个用于模糊在网格上生成的随机数的函数。
目前应用最广泛的噪声版本是由在1983年实现的,称为柏林噪声()当时他正在制作1982年版本的电影 Tron(© Walt Disney Pictures)。 如果访问他的网站(或在搜索引擎中搜索 Ken Perlin),会很容易找到对本人给出其噪声模式历史的页面的引用。 肯·柏林 (Ken Perlin) 于1997年获得美国电影艺术与科学学院颁发的奥斯卡技术成就奖,以表彰他对这部电影的贡献。 他于1984年在Siggraph展示了他的工作,并于1985年发表了一篇论文“An Image Synthesiser”,这是纹理合成领域的开创性成果。Perlin噪声会在后续章节中进行解释。
程序纹理的世界
噪声的发展引领了计算机图形学研究的一个全新领域,噪声可以被视为一个基本的构件模块,从中可以生成许多有趣的程序纹理。在程序纹理的世界中,可以生成许多类型的纹理,它们不一定都是模仿自然中的图案,可以是规则的,不规则的或随机的。 除了规则图案,所有其它的图案都可以使用噪声来生成纹理,自从发明了他的噪声函数以来,工业界开始逐渐使用它来生成复杂的材质,物体,例如 地形, 云 水体模拟等.噪声不仅可以通过生成纹理改变物体表面作色,也可以用于程序化建模,替换物体表面结构(地形),控制体积密度(体积渲染).通过每帧偏移噪声输入值,我们也可以将它同于程序动画对象.直到90年代中期,它一致是最受欢迎的水体模拟动画方法.
优势和劣势
在本章开始我们就提到,噪声不同于纹理,不会占用很大的内存用于纹理映射,同时噪声函数的实现也并不复杂.最后,使用噪声为对象添加纹理不需要表面的任何参数化数据,但这通常是纹理映射所必须的()。
但是,噪声函数生成通常比纹理映射要慢。执行噪声函数需要执行一些相对复杂数学运算,而纹理映射仅需要读取纹理图片的每个纹素数据即可。
噪声的属性
噪声是伪随机的。这可能是它最重要的一个属性。它看起来像是随机的,可实际上确实有迹可循的。输入相同的参数,噪声函数总会返回同样的数值。
噪声函数总是返回一个浮点数,无论输入数据的维度是多少。输入数据的维度通过噪声函数的名字来表达,1D 2D 3D噪声函数分别表示函数的入参是一个数值,二维的点,三维的点。另外还有4D噪声,它的输入参数有两个,一个三维点和一个额外的浮点值,用于随着时间的推移对噪声模式进行动画处理。从数学上讲噪声函数实际上是一个从到的映射。它以n维点作为输入返回一个浮点数。1D噪声通常用于物体动画,2D和3D噪声用于生成物体表面纹理,另外3D噪声对于体积渲染特别有用。
噪声是有限频带的。噪声是一个函数,可以将其视为一种信号(如果绘制噪声的函数图像,将得到一条曲线,即信号),在信号处理中,可以将信号从时域转换到频域,通过频域分析,能够直观的看到构成信号的不同频率。噪声函数可能由多个频率组成(低频表现为宏观变化,高频表现为微观局部的变化)。这些频率中的其中一个会起到主导作用,这一频率定义了噪声函数的整体视觉效果和特征。为什么我们要关心噪声函数的频率?因为当生成的噪声纹理在离相机很远的地方是,会表现为白噪声,俗称混叠或走样()。
之前我们提到过噪声函数使用插值函数或其它平滑函数(高斯模糊等)来模糊各点处生成的随机数,这些平滑函数必须具备两个属性:连续和可导。在某些情况下需要计算噪声函数的导数,所以最好选择一个即连续又可导的平滑函数。
最后,当观察一幅由噪声函数生成的图像时,理想情况下不应该能看到噪声模式的重复,换句话说,没有边界感(接缝处很自然无痕迹),一开始提到,噪声只能在预先定义的尺寸上生成。如果需要计算的噪声超过了噪声函数预定义尺寸的大小该怎么做呢?我们可以把预定义尺寸的噪声比作瓷砖,要使用特定大小的瓷砖覆盖更大的区域时,需要将许多瓷砖彼此相邻平铺。但是这种方法有一些问题。在瓷砖边界处,噪声可能是不连续的。但是我们想要的是从瓷砖到瓷砖的自然过渡,不会看到接缝的存在。在CG领域,当2D纹理是无缝的,它被称为是具有周期性的()。理想情况下,噪声函数应设计为使图案具有周期性。