转载自:http://www.cnblogs.com/babyrender/archive/2008/10/27/BabyRender.html
说起perlin noise, 最初也就是在课上大概了解了一下, 知道是个生成仿真贴图的东西. 学的时候没怎么细想, 只是知道这个东西很快. 生成3d贴图很方便. 不过最近在做sampling的时候, 发现我的算法有个很大的内存问题, 在超过3d的空间里sampling需要太大的内存. 结果我敬爱的victor一语敲醒梦中人(抬抬一台一台台). "2d或者3d如果可行的话, 去做noise吧" ... 于是就去找了perlin从85年开始的文章一篇篇看了过来...(说实话, 真正的perlin noise 其实是在89年, 85年那篇个人认为更大的突破在于提出了shading language的思想...) 嗯.. 发现noise还是很有意思的 ... 而且最近在siggraph和eurographics都很火的样子...恩, 好吧, 不废话了, 大家乱起来~~~~
perocedural texture?
说起texture, 一般来说分成2种. 一种是在物体表面直接贴图片的. 优点很明显, 就是真实感非常的强, 能精确的表达想要的效果(比如墙上的画啊, 酒瓶上的标签之类的). 不过缺点也很明显. 就是内存需要量太大, 尤其是做3d贴图的时候. 一个128*128*128的单色贴图至少需要2^21个BYTE. 而实际上, 很多我们需要的贴图都不是需要跟某张图一一对应的. 举个例子, 比如我们想在一个平面上贴一张国际象棋棋盘的纹理. 我们没必要让这张纹理和某张国际象棋棋盘的图片一模一样, 而只是需要平面"看起来"覆盖了一张棋盘的纹理就好了. 比如下面这张图, 我们完全可以用几行代码加几个mod运算就可以生成. 这种方法被人称作procedural texture. 简单来说就是一个时间(运算)换空间(内存)的换算.
理论上说, 所有的texture都可以用procedural texture来模拟. 但事实上, 很多东西是很难模拟的, 比如说某人的照片, 这个恐怕很难找到一个合理的算法来生成. 但自然界的很多物体, 或者现象都是在混乱的基础上规律出现的. 比如说树的年轮, 我们可以说是很多个不规则的同心圆. 比如说水面, 我们可以说是很多个不同的sin函数的和, 等等. 而这些现象, 如果找到一个合理的函数, 在加上一些"合理的"随机值的话, 完全可以被模拟. 而perlin noise的目的就是来模拟这种现象.
coherent noise?
procedural texture的目的(其实是所有贴图的目的)是在给定一个坐标值的时候, 能返回一个相应的颜色值. 在2d空间里, 就相当于一个这样的函数
Color Texture(float x, float y);
如果在这个函数里, 对于每一个x和y返回的颜色都是无关的, 比如, 只是返回一个随机的颜色值. 那我们就得到了一个非常杂乱的图像.:
显然, 这种图片还里现实中的物体表面相差很远. 原因就是我们没有考虑到 : 自然界中, 物体的表面都是相对平滑的, 物体表面距离很近的2个点, 颜色会比较相似, 而距离很远的2个点, 颜色相对没有任何关系. 这种相对关联的关系怎么表现呢? 一种流行的做法就是, 规定好一些关键点. 然后把这些关键点的颜色固定. 之后"平滑"的插入这些关键点之间的点.
2d的图片可以表象的更好一点:
虽然我们依旧不能说右边这张贴图是个什么物体的表面, 但起码看起来要比左边那张看起来"自然"多了.左边那张只是些点的杂乱无章的排列, 叫做噪音(一般叫做白色噪音), 而右边那张被平滑过的, 就算做关联噪音coherent noise.
multi-octaves?
如果你拿到很多个这种"平滑"过的噪音再把它们加起来的话, 呵呵, 会有很有意思的现象发生的. 比如下面这个1d的例子.
其中每一个噪音的频率都是之前一个的2倍. 而每一个噪音的振幅都是之前一个的一半. 他们的和是这个样子的:
我们可以看到, 这个"和噪音" 像极了一个1d的山峰, 恩, 我要说,还真看起来蛮像那么回事的,其中, 频率比较低的噪音表现出了山峰的大概轮廓, 频率比较高的噪音表现了相对比较杂乱的细节. 恩.其实很多游戏里的山都是这么生成的. 而这个现象, 其实应用到了很多方面. 像fourier transform, wavelet transform之类的转换.在noise这个领域, 相加之前的每个噪音函数叫做octave, 这种把很多个频率是之前一个一般的octave加起来的技术叫做multi octaves. 应用这种技术, 可以仿真很多真实的纹理, perlin在他的网页里给出了一张图片. 实际上, 因为这张图片太经典了, 基本上, 只要是介绍perlin noise 的网页都会给出, 为了遵守行业潜规则, 我也不得不把它加上.
perlin noise?
就像前面我说的, 生成perlin noise , 要分3步走:
- 固定一部分点的颜色
- "平滑"这些固定点中间的颜色
- 用上面的方法生成几个不同频率的平滑噪音, 然后相加,.
为了保证生成噪音的频率, 一般是通过改变固定点的数量. 固定点多的, 噪音自然相对比较杂乱(因为2个点之间的间隔比较小, 整体上来看, 平滑的程度自然不高), 也就是说, 噪音的频率比较高. 相对的, 固定点少的, 噪音的频率就比较低. 基于这个原因, 对于每个噪音, 我们固定4倍于之前噪音的点就可以保证频率是之前噪音的2倍(2d环境下).
说到现在, 你可能觉得perlin noise也不过如此. 也不算什么太革新的东西. 但实际上, 我们还有一个很大的问题没有解决, 就是那个"平滑". 怎么平滑? 用什么函数来平滑? 线性的? polynomial的? gaussian的? 复杂的函数肯定会带来更好的结果, 但运算时间呢? 要知道, cpu或者gpu可不能浪费大把的时间来处理一个小小的texture, 还有大把更重要的工作要做.
于是乎, perlin noise 的核心思想出现了. 叫做gradient noise . (其实在很多文献里, 人们把perlin noise 叫做gradient noise).
gradient noise?
首先要说的是, 固定一部分点的颜色值, 然后"平滑"这些颜色绝对是一个笨办法(其实叫做value noise. 可能在其他方面有他的应用. 但在texture方面绝对不可行.).因为为了到达很好的连续度 要用到很高阶的过滤函数. 恩, 换种说法. 为了达到很好的连续度, 用于"平滑"的函数要很复杂才行. perlin在89年提出了一种更有效的"平滑"方法. 就是给每个固定点固定一个gradient(中文不知道该怎么说, 应该是叫坡度吧.), 然后在中间的点"平滑"这些坡度. 这样, 哪怕是使用线性函数, 也能保证达到C1的平滑度(具体定义比较麻烦, 其实C1是指两条曲线在接触点的微分值相等, 简单的说, 就是Cn中n越大, 连续度越好, 一般C2在人眼看来就已经很平滑了. 具体定义还是参看相关文献). 于是乎, 对于生成perlin noise 的那三步, 现在变成了:
- 固定一部分点的gradient
- "平滑"这些固定点中间的gradient
- 用上面的方法生成几个不同频率的平滑噪音, 然后相加,.
而对于"平滑"函数, perlin用了一个hermite曲线 : w(t)=3t² - 2t³ (hermite嘛... 反正就是一个曲线啦....) .因为这个曲线保证了w(0)=0, w(1)=1. (换句话说, 保证了能在固定点取固定值, 而在非固定点取一个平滑的值.) 而且保证了w'(0) = 0和w'(1)=0, 也就是说保证了C2的连续度. (在02年, perlin推荐了一个更高阶的曲线, 保证了w''=0, 能够保证更好的连续度). 下面这张图是w曲线的1d表示
结束了? 还没有... 还有个内存上的小问题...
. 如果说我们的最终texture只是由一些低频的噪音生成的. 那么当用户把镜头拉的离物体很近的时候, 会像普通贴图一样产生一块一块的现象(回忆一下 Doom 撞墙的时候). 所以高频率的噪音绝对是有必要的. 而为了储存固定点的gradient, 要建立一个数组才行. 而当噪音的频率非常高的时候, 由于需要的固定点会很多, 这个数组也会非常的大. 为了降低内存的使用, perlin使用了1个256个元素的哈希表. 也就是说, 预先找出合理的, 足够随机的256个gradient, 存在一个表里. 然后每次需要某个固定点的gradient值的时候, 通过这个这个点的坐标, 伪随机的在表里选出一个值. 对于3d的情况, 如果我们想要坐标(i,j,k)的gradient g(i,j,k),而P里预存储了256个gradient, 那么:
g(i, j, k) = P[ ( i + P[ (j + P[k]) mod 256 ] ) mod 256 ]
这样, 在生成perlin noise的时候, 内存的使用限定在了1个256大小的哈希表. 在02年, perlin进一步缩小了这个哈希表的大小到16.
想想还有什么要说的...
作为对比, 下面第2张图使用了perlin noise 来达到"旧"的效果,是不是看起来更真实了?
perlin noise 的应用那是相当的广泛. texture, terrain, bump map, 云动画, 烟雾(ray marching)... 可以用在几乎所有那些"杂乱而有规律"的现象上.
尤其是对材质的模拟, 可以说是出神入化. 原因就是perlin noise 快, 简单, 而且只占用很少的内存. 当然, 他的缺点也一样明显. 就是他的fourier spectrum不够band limited. 由于要阐述到fourier domain里的东西, 和其他类型的noise , 像 blue noise 的定义, 有机会再写吧. 值得一提的是, 这两年有不少人在研究完善perlin noise 和开发其他具有blue noise性质的其他技术. 比较成功的有wavelet noise (siggraph 2005) 和anisotropic noise(siggraph 2008). 有兴趣的朋友欢迎一起讨论.