【译】值噪声与过程性纹理_Part 1

原文链接:Value Noise and Procedural Patterns_Part 1

值噪声和程序纹理

Keywords: value noise, procedural pattern generation, deterministic, random number generator, RNG, periodic, continuity, differentiability, sampling, aliasing, white noise, solid textures, permutation, hash table.

“对于物理学家来说,任何随时间发生不可预测的变化数量V被称为噪声。”(The Science of Fractal Images, Richard F. Voss).

这节课用一种非常简单(近乎天真)的形式解释了噪音的概念。你将了解什么是噪声,它的属性是什么,以及你能用它做什么。噪音不是一个复杂的概念,但它有许多微妙之处。正确使用它需要了解它是如何工作的以及它是如何产生的。为了创建一些图像和各种参数的实验,我们将实现一个简单的(但功能齐全)版本的噪声,称为值噪声(value noise)。在阅读这一课的时候,请记住,我们将会忽略许多复杂到无法在此研究的技术。这只是对噪音和一些可能的应用的简要介绍。该网站提供了许多课程,其中每个主题都可以单独研究(混叠、纹理生成、复杂的噪声函数、景观云和水面生成,以及一些高级编程技术,如bitwise算法、哈希等)。

历史背景

噪声的概念是在80年代中期发展起来的,作为一种替代使用图像给对象贴图的方法。我们可以用图像来映射对象,以增加其外观的视觉复杂性。在CG中,这被称为纹理映射。然而,在80年代中期,计算机的内存和图像非常有限,不能很轻松地装入RAM中。在CG工作室工作的人开始寻找其他的解决方案。用纯色呈现的物体看起来太干净了。他们需要一些东西来打破这种干净的外观效果,比如通过调节物体表面的视觉属性(颜色、亮度)。在编程中,每当我们需要创建随机数时,我们通常使用随机数生成器(randon number generators)。然而,使用RNG(随机数生成的缩写)添加3D对象外观的变化是不够的。我们在自然界中可以观察到的随机模式通常是平滑的。在真实物体表面上的两个点彼此相当接近的时候,通常看起来几乎是相同的。但是同一物体表面上彼此分开很远的两个点看起来非常不同。换句话说,局部变化是渐进的,而全局变化可能是巨大的。RNG没有这种属性。每次我们调用RNG生成的数值彼此之间都是不相关的。因此,调用这个函数很可能产生两个非常不同的数字。这将不适用于对空间相近的两个点的视觉外观进行细微的变化的情况。下面是一个例子:让我们观察一个真实的岩石的图像(图1左),让我们假设我们的任务是创建一个CG图像,以再现这个对象的外观。你不能使用纹理(最明显的解决方法是将图像映射到平面上)。我们所拥有的只是一个没有纹理的平面,看起来完全平坦(均匀的颜色)。这个例子很有趣,因为我们可以观察到,岩石图案由三种主要颜色组成:绿色、粉红色和灰色。这些颜色或多或少地分布在岩石的表面。这个问题第一个显而易见的解决方案是,立刻用Photoshop打开这个图片,并拾取这三个平均颜色平均,以便能够在程序中重用它们,来使用程序生成图案,使它看起来或多或少类似于我们的参考图片。以下代码就是这样做的。然而,现在我们所能做的就是使用C drand48()函数来创建从像素到像素的变化,该函数返回范围[0:1]中的随机值。

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(); 
}

image
图1:左边我们的样品图案(岩石照片)。在中间,我们的程序生成一个随机模式从三种颜色在源图像中提取。在右边,一个中间图像区域(10x10像素)被复制并调整到一个更大的图片。我们还将高斯模糊应用到Photoshop中,以获得更平滑的效果。

正如您在图1中看到的,这个程序的结果并不令人信服(实际上,这个模式有一个名称;它被称为白噪声white noise)。我们稍后将解释白噪声是什么,但现在请记住,它看起来就像图1中间的图像),我们使用RNG为程序生成的纹理的每个像素选择一个颜色,这将导致像素到像素的巨大变化。为了改善效果,我们从图像中复制了一个纹理的小区域(10x10像素),我们将其缩放(放大)到原始图像的尺寸(256x256像素)。放大这部分帧会模糊像素,但为了使效果更平滑,我们在Photoshop中应用了高斯模糊。我们还没有很好地匹配参考图像,但是正如您所看到的,所得到的图像(图1中的正确图像)已经比原来的(中间)好了。在局部,我们确实有小的变化(颜色从像素到颜色的小变化),而在全局范围内,我们确实有很大的变化(三种输入颜色的混合)。

这个实验的结论是,创建一个平滑的随机图案,我们在需要在网格固定的位置,我们称之为晶格(lattice)(在我们的例子中网格对应着10 x10输入图像的像素位置)上使用RNG分配随机值使用RNG,并使用高斯模糊(一个用于模糊

这些随机值的平滑函数)来模糊这些随机值。在下一章中,我们将向您展示如何实现这一点。但是现在你需要记住的是,噪声(在计算机图形的上下文中)是一个模糊在网格(我们经常把它称为晶格)上生成的随机值的函数。

我们刚才描述的过程非常类似于双线性插值(见插值课)。在下面的例子中(图2),我们创建了一个大小为10x10的2D网格,在网格的每个顶点上设置一个随机数,并使用线性插值来呈现更大的图像。

image
正如你所看到的,结果的质量不是很好。我们在插值的课上指出,双线性插值是插值数据最简单的方法,为了提高结果的质量(如果需要),可以使用更高质量的插值算法(用于插入数据的函数)。这将在下一章详细解释,我们将能够编写一个噪声函数,它能提供比这个简单的二维网格数据线性插值更好的结果。

image
最受欢迎的噪音的实现是由Ken·Perlin在1983年写的(可能是第一个),当时他正在拍摄1982年版的电影《Tron》(迪斯尼电影)。如果你去他的网站(或者在你最喜欢的搜索引擎中搜索Ken·Perlin),你会很容易地找到一个这篇论文,在论文中Ken·Perlin自己给出了他的噪音模式的历史。1997年,Ken·Perlin获得电影艺术与科学学院的技术成就奖,以表彰他对这部电影的贡献。他于1984年在Siggraph上展示了他的作品,并在1985年发表了一篇论文,“An Image Synthesiser”,在纹理合成领域具有开创性。如果你有兴趣了解噪声函数的基本原理(及其发展历史),你应该阅读这篇文章。在第2部分课程中中解释了Perlin噪声。

程序纹理的世界

噪声的发展开拓了计算机图形学研究的一个全新领域。噪声可以被看作是一个基本的构建块,基于它可以生成许多有趣的过程纹理(也称为实体纹理(solid texture),参见纹理的课程)。在程序纹理的世界中,可以生成许多类型的纹理,这些纹理并不总是试图模仿自然图案。例如,编写一个程序来生成一个砖墙类型的图案是非常简单的。一些图案可能是规则的、不规则的或随机的(非确定性的)。

image
图3:在现实世界中的许多图案可以通过程序纹理重新生成。噪音可以使用在任何表现出某种随机性的图案。它也可以用来改变体积密度来模拟云层或平面的动画来模拟水面。

除了规则图案(只有遵循几何规则和规整的形状)之外,所有其他图案都可以使用噪声来引入不规则性,或者将明显的随机性载入到程序生成的纹理中。在Ken Perlin介绍了他的噪音功能后,CG组织中的许多人开始使用它来建模复杂的材质,物体,比如地形,云或动态的水平面。噪声不仅限于改变对象的视觉外观,还可以用于使用程序建模,替换对象的表面(用于生成地形)或控制体积密度(云建模)。通过偏移帧间的噪声输入,我们也可以用它来使用程序上动画化对象。直到90年代中期,它一直是最受欢迎的方法(Jerry Tessendorf在90年代后期提出了一种更现实的方法)。看看关于动态海水的课程。

本课的最后一章给出了一些简单的固体纹理(分形)的例子。关于纹理合成的课程将为您提供关于这个主题的更多信息。

噪声的主要优势/劣势

正如我们在简介中提到的,噪音具有占用内存小的优点。与纹理映射相比,噪声函数不需要太多的内存(一个噪声函数需要很少的数据存储)。从这个非常基本的功能,也可以创建各种各样的纹理(我们将在本课的最后一章给出一些例子)。最后,使用噪声将纹理添加到对象不需要物体模型表面的参数,而这些参数通常在纹理映射需要用到(纹理映射需要纹理坐标)。

另一方面,它们通常比纹理映射慢。噪声函数要求执行相当多的数学运算(即使它们很简单,但数量相当多),而纹理映射只需要访问存储在内存中的纹理(图像文件)的像素。

理想噪声的属性

如果有可能列出一个理想的噪声ideal noise)应该具有的所有属性,不是所有的噪声函数的实现都能与它们匹配(存在相当多的版本)

噪声是伪随机的,这可能是它的主要特性。它看起来是随机的,但是是确定的。给定相同的输入,它总是返回相同的值。如果您多次渲染一个电影的图像,您希望噪声模式是一致的/可预测的。或者,如果你将这种噪声模式应用到平面上,你希望这种图案能保持不变,从帧到帧(即使是摄像机移动或平面改变了)。这个图案仍固定在平面上。我们认为这个函数具有不变性:它不会随着物体变换下发生改变)。

噪声函数总是返回一个浮点数,不管输入值的维数是多少。根据点的维度赋予了噪音的名称。1D、2D、3D的噪声函数是将一维、二维和三维点作为输入参数的函数。甚至存在4D的噪声,它以一个3D点作为输入参数,还有一个附加的浮点数,用来在时间上对噪声图案进行动画操作(跟着时间变化的实体纹理)。本课给出了一维和二维噪声的例子。在第2部分中给出了三维和4D噪声实例。在数学术语中,我们说噪声函数是一个从Rn到R的映射(其中n是传递给噪声函数的值的维数)。它以一个n维点作为输入并返回一个浮点数。1D噪声用于动画化对象,2D和3D噪声用于纹理化对象。三维噪声对于调节体积的密度特别有用。

噪音的范围是有限(band limited)的。记住,噪声主要是一个函数,你可以把它看成一个信号(如果画出这个函数图像,你得到一条曲线,他就是一个信号)。在信号处理中,可以接收信号并将其从空间域转换为频域。这个操作给出了一个结果,它可以看到一个信号的不同频率。如果你还不明白从空间到频率空间意味着什么,不要太担心(这有些超越了我们的主题范围,但你可以查看关于Fourrier转换的课程来了解更多)。你需要记住的是,噪声函数可能是由多个频率组成的(低频率是大尺度变化的原因,高频是小变化的原因)。但其中一个频率支配着所有其他频率。并且这一频率决定了视觉和频率(如果你在频率空间中观察你的信号)外观/你的噪声函数的数字特征。为什么我们要关心噪声函数的频率?当图像中噪音很小的时候(当一个物体在远离摄像机的地方使用噪音成像),它又变成了白噪音,这就是我们行话所说的混叠。如图4所示。

image
图4:噪声图应用于大平面上。从远处看,远处的噪声图(红线上方)的噪音太小了,这就造成了一种被称为混叠的视觉假象。当相机缓慢移动时,这种现象尤为引人注目。

在此图像的背景下,邻域像素取随机值。不仅视觉上令人感到不愉快和不真实,如果你渲染的序列图像,像素的值在这个领域将会改变彻底从帧到帧(图4)。混叠采样的主题有关,是一个非常大的,在计算机图形学中非常重要的话题。如果你想知道是什么造成了这种不想要的效果,我们邀请你阅读关于纹理采样的课程。

我们提到,噪声使用平滑函数来模糊在格点生成的随机值。数学中的函数具有属性。其中两个与这一课的背景特别相关:
连续性可微性。在这里解释这些是什么有些朝纲了,但通过一个简单的例子,你会直观地理解它们的含义。导数是一条曲线的切线,如图5所示。然而,如果函数不是连续的,那么计算这个导数是不可能的(函数也可以是连续的,但不是处处可微的,如图5所示)。我们稍后会解释,在一些地方需要用到噪声函数的计算导数,最好选择一个连续的和可微的平滑函数。由Ken Perlin最初实施的噪声函数使用了一个不连续的函数,他在几年之后提出了另一个函数来修正这个问题。

image
图5:在标记点处的函数的导数是通过这一点(左)的曲线的切线。不是所有的函数都是连续的。在中间,函数在两个红点之间没有定义,因此不是连续的。最后(对),函数可以是连续的,但不一定是可微的。红点表示函数的导数不能计算的点。

我们应该能在噪声曲线上找到切线的位置。如果在这条曲线上有一个地方这个切线不能被发现,或者它突然发生变化,它就会导致我们所说的不连续。

最后,当你观察一个物体上的噪声纹理时,理想情况下你不应该看到重复的噪音图案。正如我们开始建议的那样,噪声只能从预定义大小的网格中计算出来。在上面的例子中,我们选择了10x10网格。如果我们要计算网格边界之外的噪声怎么办?噪音就像瓦片一样,为了覆盖更大的区域,在一定尺寸的瓦片上,你需要把许多瓦片放在一起。但这种方法存在两个明显的问题。在瓦片的边界,噪音图案将是不连续的。

理想情况下,你想要的是一个隐形的从瓦片到瓦片的过渡,这样你就能覆盖一个无限大的区域,而不会看到一个接缝。在CG中,当一个2D纹理是无缝的,据说它在两个方向上都是周期性periodic)的(x和y)。任何纹理都是可重复的,但可能不是无缝的。理想情况下,你的噪声函数应该被设计好,使生成的图案具有周期性。此外,由于您使用了许多具有相同图案的瓦片,您可能会发现该模式的重复。这个问题的解决方法在这一节课上更难解释。您现在需要知道的是,由噪声函数创建的图像没有大的特性(所有的特性都有大致相同的大小,记住我们说过的函数是带限制的)。这意味着如果你缩小,你将看不到大特征的重复,因为它们不在函数中。至于其他更小的特点,噪音是由,当你放大了很多,它们太小而不能真正看到。这个技巧(因为它是一种)使模式隐形,就是使它足够大(使格点的网格足够大),这样当你把它覆盖到你的整个屏幕的时候,它的特征就会在框架内被看到。我们将在下一章中演示这一效果。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页