做者:周志创
阅读时间:10~15minjavascript
在了解噪声以前,我对随机的认识,仅仅停留在 Math.random 。它颇有用,好比 H5 里的简单抽奖程序,或者随机选取一张卡片... 而最近工做中须要实现一些的随机图像效果,让我发现这个函数能作的事十分有限。以后我偶然了解到噪声这一种随机形式,它很完美的解决了个人问题。因而我想写这一篇文章,但愿可让一些前端同窗,特别是工做上涉及较多效果还原的前端同窗了解噪声,或许在这以后,你会对设计师设计稿上这些随意元素,有更多的想法。css
设计师常常会在他们的设计中添加一些随意元素,这是一种很棒的设计技巧。可是对于前端实现来讲,这类设计元素的还原大多数状况会让咱们感到无能为力,由于基础 Api 提供的都是规律的几何形状(圆,矩形等...),经常最后,这些效果都只好妥协使用切图还原,咱们不能作更多优化,更不能经过增长动画表现更多的设计想法,这一直是大多数同窗前端还原的空白领域。html
上面这些图片都是从一些真实设计稿中截取的,其中很容易找到我所说的随机元素,它们给设计增添神秘感和科技感,可是它们好像很难用传统的前端设计还原技巧制做。而这些,偏偏很是适合使用噪声来实现。前端
噪声算法通过多年发展已经很是成熟,并且有不少种分类 Perlin噪声, Simplex噪声,Wavelet噪声,本篇文章里边出现的噪声默认指的是 Perlin 噪声,不会深刻介绍具体噪声算法,主要目的是向没有接触过这个领域的前端同窗做一个启发式的介绍。java
先来解决一个简单的问题:画出一根随意的线。这彷佛很简单,咱们用 Math.random 生成一系列的点,而后把他们链接起来git
for (let i = 0; i < POINTCOUNT; i++) {
const y = HEIGHT / 2 + (Math.random() - 0.5) * 2 * MAXOFFSET;
const point = {
x: i * STEP,
y
};
POINTS.push(point);
}
...
ctx.moveTo(POINTS[0].x, POINTS[0].y);
for (let i = 1; i < POINTS.length; i++) {
const point = POINTS[i];
ctx.lineTo(point.x, point.y);
}
ctx.stroke();
复制代码
可是这没有什么特别的,像是刻意画出来的折线图。直线让咱们的图像很生硬,下面咱们作点小改良,咱们在两个随机点间找两个控制点,用贝塞尔曲线再次将随机点连起来:github
for (let i = 0; i < POINTS.length - 1; i++) {
const point = POINTS[i];
const nextPoint = POINTS[i + 1];
ctx.bezierCurveTo(
controlPoints[i][1].x,
controlPoints[i][1].y,
controlPoints[i + 1][0].x,
controlPoints[i + 1][0].y,
nextPoint.x,
nextPoint.y
);
}
ctx.stroke();
复制代码
此次天然多了,咱们这一步作的事情很关键,虽然这不是噪声算法的自己,可是其中的思想是同样的。上面例子的随机点,咱们能够理解为随机特征,单个随机特征是没有意义的。以后咱们用直线链接起来,它们总体看起来依然没有联系,可是咱们将他们平滑地串联起来以后,它们互相以前造成了一个完整的图案,获得了一种天然的随机效果。这也是我学习完噪声的主观理解——噪声主要解决的问题,就是让互不相关的随机特征,产生某种平滑地过渡或联系。web
好,到这里咱们停下来,想一想大天然,放松一下。想一想云、河流、山脉... 这些、都是大天然里各类随机事件,日积月累创造出来的。这里的关键,就是“日积月累”,也就是说不会无缘无故地出现一片云、不会忽然就出现一条河,他们都是一点一点演变而来的,因此在这些随机事件中是“平滑”变换的。算法
在现代游戏和影视做品中,已经有能力逼真地还原这些天然元素了。它们并非也不可能使用大量建模软件制做,而是用代码生成的,这里用到的关键技术,就是噪声算法。这种算法重要的部分,就是在随机特征中的插值算法,在随机特征中间的值能平滑过分,以模拟大天然中这种演变的结果。canvas
Ken Perlin 早在1983年正在参与制做迪士尼的动画电影《TRON》,就是为了实现这种天然的纹理效果,提出了 Perlin 噪声,Perlin 噪声很成功,他也所以得到了奥斯卡科技成果奖。
咱们上面例子为了在随机特征间平滑过分使用了贝塞尔曲线,而柏林噪声则使用了更加科学复杂的插值方法:
f(t)=t*t*t*(t*(t*6-15)+10)
复制代码
有兴趣能够自行查阅其中实现细节。柏林噪声通过发展演变出了不少变形实现,包括后来柏林噪声的优化版本 simplex 噪声,本文侧重于介绍噪声的使用,下文其中一些实现,默认使用就是柏林噪声。
噪声函数的使用方法都是接受一个点,而后返回一个 -1 到 1 的结果,随机特征长度单位为 1,因此在使用噪声函数以前,须要注意单位的转换。 好比,你有一个 1000 x 1000 的画布,想要使用噪声对应每一个像素的随机值,那就首先就要定义想应用多少个随机方格,若是应用 n x n 个方格,那在获取随机值以前,就须要对坐标进行转换
scale = 1000 / n;
nx = x / scale;
ny = y / scale;
pointRandom = noise2D(nx, ny);
复制代码
js 实现的噪声工具: github.com/josephg/noi… github.com/jwagner/sim…
噪声接受相同的参数老是能返回相同的结果,因此一般要预设一个随机集,目的就是为了生成固定的随机特征点,而后平滑的随机结果将在这些随机特征点中插值产生。当中的实现不作讨论,有些噪声工具默认已经设置好了一个随机集,也能够自定义不一样的种子来生成不一样的随机效果,上一个噪声工具 noisejs 能够这样简单定义便可:
noise.seed(Math.random());
复制代码
一维噪声能够这样简单理解,将随机集分配到 x 轴上整数位上,经过平滑插值函数计算连续变换的随机值。这个说法可能和具体 Perlin 噪声不彻底同样,可是基本思路能够这样简单理解
简单从结果上来说就是给定一个坐标,你将获得一个随机值,若是给定连续的坐标,你将能够获得连续的随机值,而不是像 Math.random() 那样获得互不相关的错落的值。
下面的例子是使用一维噪声制做的一个鸡蛋的效果,先在圆上取固定的等分的点,它们分别经过 noise 获取到一个随机偏移,而后再结合时间,让偏移随着时间平滑地改变:
好比下面这个火的效果,火焰尾部摇曳的效果的偏移,就是一维噪声结合时间偏移实现的
一维噪声这种随时间偏移的效果,还能够经过把时间做为二维噪声函数的 y 参数实现。
二维噪声经过定义一个二维的方格,上一步定义的固定的随机集会被分布在这些方格顶点上,而每个顶点的随机特征是一个梯度向量,而后,计算方格内的点周围四个顶点到方格内一个点(也就是须要求噪声值的点,下图中黄色的点)的向量,用他们与梯度向量点乘,最后使用插值函数进行插值获得该点对应的随机噪声值。
下面图像是使用 webGL 应用 2D 噪声函数生成的,白色表明 1,黑色表明 -1,这是 5 * 5 方格的效果。这里不用关心 webgl,咱们只要关心如何定义适用本身问题的方格,还有获取对应的连续的随机值。
这里侧重的是介绍噪声使用,具体实现方法能够本身点击下面参考连接了解。使用方法很简单:
// 这里除 100 是为了以一百个像素为单位应用一个噪声方格
let randomValue = noise.simplex2(x / 100, y / 100);
复制代码
噪声函数获取的随机值就很自由了,它能够用来定义各类你须要的变量,好比下面这个例子,平滑地随机值被用做定义粒子的速度向量从而实现既随机又连续的运动效果:
function calculateField() {
for(let x = 0; x < columns; x++) {
for(let y = 0; y < rows; y++) {
let angle = noise.simplex3(x/20, y/20, noiseZ) * Math.PI * 2;
let length = noise.simplex3(x/40 + 40000, y/40 + 40000, noiseZ) * 0.5;
field[x][y].setLength(length);
field[x][y].setAngle(angle);
}
}
}
复制代码
生成向量一个随机角度,和向量长度,这里使用 simplex3 三维噪声实际上第三个参数是被用做二维的偏移量
若是保留运动痕迹,则能够获得更炫酷的效果
// 使用半透明清楚画布
function drawBackground(alpha) {
ctx.fillStyle = `rgba(0, 0, 0, ${alpha || 0.07})`;
ctx.fillRect(0, 0, w, h);
}
复制代码
经过把几个不一样频率的 Perlin 噪声相叠加,这项技术叫作分形噪声,能够实现更多纹理,好比水流,山川。 SVG 中有一个这种噪声的滤镜应用,feTurbulence,对比普通咱们熟知的 css 滤镜,它很容易被人忽略,可是它却能实现不少意想不到的效果:
<feTurbulence type="turbulence" baseFrequency="0.01 .1" numOctaves="1" result="turbulence" seed="53" />
复制代码
feTurbulence 其实是在每一个像素上应用获得的噪声值,从而获得颜色值,而后能够结合其余滤镜,将这些随机颜色转换成其余随机表现,如像素偏移,可使用 feTurbulence 滤镜实现一些天然纹理,还有倒影效果。
wow.techbrood.com/fiddle/3165…
这里的噪声叠加在频率和振幅上有必定的约束,它们是自类似的,参考thebookofshaders.com/13
三维噪声实际上就只是在二维噪声基础上又增长一个纬度,定义一个三维的随机顶点集,在三维方格当中的三维坐标,将会对应获得一个噪声值,一样也是连续的。
三维噪声获取的随机值能够转换成一个三维点在其法向向量方向的偏移,从而实现一种随机的起伏变形。在 webGL 当中,这一步这一般在顶点着色器当中完成,顶点着色器会对全部定义的顶点执行:
float addLength = maxLength * cn(normalize(position) * 2.9 + time * 0.9); // 计算随机值
vec3 newPosition = position + normal * addLength; // 转换为法向向量方向上的偏移值
复制代码
在片元着色器中,则能够应用使用噪声制做纹理:
就像以前提到的噪声被设计出来的一开始的目的,就是应用在影视/游戏做品当中模拟天然效果的,因此,使用 webGL 制做海洋,山川这种效果,也天然不在话下: codepen.io/matikbird/p…
固然这不只须要熟练使用噪声,还须要掌握 webGL,虽然一开始咱们介绍了噪声在 canvas/svg 中的使用方法,可是,如今 webGL 已经被电脑端手机端浏览器普遍支持,结合 webGL ,噪声能发挥它更大价值。
噪声的应用十分普遍,是图形学领域的重要知识,在三维程序扮演重要的角色。可是噪声不是三维图形领域的专属,学习使用噪声,在 canvas svg css 这些基础的前端技术上应用,也能实现一些意想不到的效果,当某一天设计师又输出了一个相似这种随机特征的效果图,不妨直接找到设计师沟通。说不定这些效果,就是用某个图像处理软件使用噪声生成的。若是获取到生成的参数,在前端也是有可能实现的,经过结合时间偏移,说不定就能实现一个很棒的动画。
参考资料
thebookofshaders.com/13/?lan=ch
关注【IVWEB社区】公众号获取每周最新文章,通往人生之巅!