隨機不僅是 Math.random —— 前端噪聲應用

做者:周志創
閱讀時間:10~15minjavascript

在瞭解噪聲以前,我對隨機的認識,僅僅停留在 Math.random 。它頗有用,好比 H5 裏的簡單抽獎程序,或者隨機選取一張卡片... 而最近工做中須要實現一些的隨機圖像效果,讓我發現這個函數能作的事十分有限。以後我偶然瞭解到噪聲這一種隨機形式,它很完美的解決了個人問題。因而我想寫這一篇文章,但願可讓一些前端同窗,特別是工做上涉及較多效果還原的前端同窗瞭解噪聲,或許在這以後,你會對設計師設計稿上這些隨意元素,有更多的想法。css

設計師常常會在他們的設計中添加一些隨意元素,這是一種很棒的設計技巧。可是對於前端實現來講,這類設計元素的還原大多數狀況會讓咱們感到無能爲力,由於基礎 Api 提供的都是規律的幾何形狀(圓,矩形等...),經常最後,這些效果都只好妥協使用切圖還原,咱們不能作更多優化,更不能經過增長動畫表現更多的設計想法,這一直是大多數同窗前端還原的空白領域。html

設計稿中的隨意元素

上面這些圖片都是從一些真實設計稿中截取的,其中很容易找到我所說的隨機元素,它們給設計增添神祕感和科技感,可是它們好像很難用傳統的前端設計還原技巧製做。而這些,偏偏很是適合使用噪聲來實現。前端

噪聲算法通過多年發展已經很是成熟,並且有不少種分類 Perlin噪聲, Simplex噪聲,Wavelet噪聲,本篇文章裏邊出現的噪聲默認指的是 Perlin 噪聲,不會深刻介紹具體噪聲算法,主要目的是向沒有接觸過這個領域的前端同窗做一個啓發式的介紹。java

隨機的線

先來解決一個簡單的問題:畫出一根隨意的線。這彷佛很簡單,咱們用 Math.random 生成一系列的點,而後把他們鏈接起來git

隨機線1

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

隨機線2

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 獲取到一個隨機偏移,而後再結合時間,讓偏移隨着時間平滑地改變:

codepen.io/chiunhauyou…

egg

好比下面這個火的效果,火焰尾部搖曳的效果的偏移,就是一維噪聲結合時間偏移實現的

codepen.io/gnauhca/pen…

fire

一維噪聲這種隨時間偏移的效果,還能夠經過把時間做爲二維噪聲函數的 y 參數實現。

二維噪聲

二維噪聲經過定義一個二維的方格,上一步定義的固定的隨機集會被分佈在這些方格頂點上,而每個頂點的隨機特徵是一個梯度向量,而後,計算方格內的點周圍四個頂點到方格內一個點(也就是須要求噪聲值的點,下圖中黃色的點)的向量,用他們與梯度向量點乘,最後使用插值函數進行插值獲得該點對應的隨機噪聲值。

二維噪聲1

下面圖像是使用 webGL 應用 2D 噪聲函數生成的,白色表明 1,黑色表明 -1,這是 5 * 5 方格的效果。這裏不用關心 webgl,咱們只要關心如何定義適用本身問題的方格,還有獲取對應的連續的隨機值。

二維噪聲2

這裏側重的是介紹噪聲使用,具體實現方法能夠本身點擊下面參考連接瞭解。使用方法很簡單:

// 這裏除 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 三維噪聲實際上第三個參數是被用做二維的偏移量

codepen.io/DonKarlsson…

二維噪聲示例1

若是保留運動痕跡,則能夠獲得更炫酷的效果

// 使用半透明清楚畫布
function drawBackground(alpha) {
  ctx.fillStyle = `rgba(0, 0, 0, ${alpha || 0.07})`;
  ctx.fillRect(0, 0, w, h);
}
複製代碼

codepen.io/DonKarlsson…

二維噪聲示例2

二維噪聲示例(svg)

經過把幾個不一樣頻率的 Perlin 噪聲相疊加,這項技術叫作分形噪聲,能夠實現更多紋理,好比水流,山川。 SVG 中有一個這種噪聲的濾鏡應用,feTurbulence,對比普通咱們熟知的 css 濾鏡,它很容易被人忽略,可是它卻能實現不少意想不到的效果:

<feTurbulence type="turbulence" baseFrequency="0.01 .1" numOctaves="1" result="turbulence" seed="53" />
複製代碼
  • numOctaves 噪聲疊加數
  • seed 隨機種子
  • type 噪聲類型

feTurbulence 其實是在每一個像素上應用獲得的噪聲值,從而獲得顏色值,而後能夠結合其餘濾鏡,將這些隨機顏色轉換成其餘隨機表現,如像素偏移,可使用 feTurbulence 濾鏡實現一些天然紋理,還有倒影效果。

feTurbulence 濾鏡輸出

codepen.io/yoksel/pen/…

二維噪聲示例(svg)

wow.techbrood.com/fiddle/3165…

二維噪聲示例(svg)2

這裏的噪聲疊加在頻率和振幅上有必定的約束,它們是自類似的,參考thebookofshaders.com/13

三維噪聲

三維噪聲實際上就只是在二維噪聲基礎上又增長一個緯度,定義一個三維的隨機頂點集,在三維方格當中的三維座標,將會對應獲得一個噪聲值,一樣也是連續的。

三維噪聲獲取的隨機值能夠轉換成一個三維點在其法向向量方向的偏移,從而實現一種隨機的起伏變形。在 webGL 當中,這一步這一般在頂點着色器當中完成,頂點着色器會對全部定義的頂點執行:

float addLength = maxLength * cn(normalize(position) * 2.9 + time * 0.9); // 計算隨機值
vec3 newPosition = position + normal * addLength; // 轉換爲法向向量方向上的偏移值
複製代碼

codepen.io/gnauhca/pen…

三維噪聲1

在片元着色器中,則能夠應用使用噪聲製做紋理:

codepen.io/timseverien…

三維噪聲2

就像以前提到的噪聲被設計出來的一開始的目的,就是應用在影視/遊戲做品當中模擬天然效果的,因此,使用 webGL 製做海洋,山川這種效果,也天然不在話下: codepen.io/matikbird/p…

三維噪聲3
三維噪聲4

固然這不只須要熟練使用噪聲,還須要掌握 webGL,雖然一開始咱們介紹了噪聲在 canvas/svg 中的使用方法,可是,如今 webGL 已經被電腦端手機端瀏覽器普遍支持,結合 webGL ,噪聲能發揮它更大價值。

結語

噪聲的應用十分普遍,是圖形學領域的重要知識,在三維程序扮演重要的角色。可是噪聲不是三維圖形領域的專屬,學習使用噪聲,在 canvas svg css 這些基礎的前端技術上應用,也能實現一些意想不到的效果,當某一天設計師又輸出了一個相似這種隨機特徵的效果圖,不妨直接找到設計師溝通。說不定這些效果,就是用某個圖像處理軟件使用噪聲生成的。若是獲取到生成的參數,在前端也是有可能實現的,經過結合時間偏移,說不定就能實現一個很棒的動畫。

參考資料

blog.csdn.net/qq_34302921…

codepen.io/DonKarlsson…

thebookofshaders.com/13/?lan=ch


關注【IVWEB社區】公衆號獲取每週最新文章,通往人生之巔!

相關文章
相關標籤/搜索