iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

成為Canvas Ninja ! ~ 理解2D渲染的精髓系列 第 29

Day 29 - 3D繪圖篇 - 噪聲地形演算I - 成為Canvas Ninja ~ 理解2D渲染的精髓

再兩天 ~!!

在鐵人賽的最後,我想要給各位帶來的是噪聲地形的演算~

之所以想要寫這個題目,原因是因為這個題目也可以承接我們上一篇講的內容(透視投影),

而且還可以順便帶到一個在電腦圖學中我覺得很有趣的概念:『噪聲(Noise)』。

不過我們這次還不會馬上的帶到主題的實作,而是會分成兩篇來進行。

今天的部分我們會先簡單給大家科普一下『噪聲(Noise)』的概念,並且會有一個使用噪聲來做動畫的實作範例。

接著就讓我們開始吧~

什麼是噪聲?

能夠熟練使用Photoshop做後製的人一定有看過或用過下面這個玩意~

img

Photoshop中,這種花紋的圖樣被稱為『雲狀效果(Cloud Effect)』,而他其實也就是我們現在正要講的『噪聲(Noise)』。

所以我們今天要講的主題就這? 一張圖?

Of course not.

噪聲(Noise)實際上是一種函數/演算法的統稱,而這種函數/演算法的意義就在於『創造出有平滑、連續、有規律但卻不循環的隨機』。

『創造出有平滑、連續、有規律但是卻不循環的隨機』這句話聽起來可能很難懂,所以我們一步一步來解釋~

一般情況下,我們在前端要產生隨機數值,大多會使用Math.random()來產生一個介於0~1的數字,然後給訂一個極小值,一個極大值,然後接著就在這個範圍內,用內插法的方式 取得一個隨機數。

如果不記得內插法是什麼的話,可以回去翻翻國中課本XD,或是點這裡

假設我們今天用for loop去run 100圈,也就是從x=0x=99,然後每圈我們都用Math.random()*1000 拋出一個隨機值y

那麼如果我們把這個結果畫在x/y圖上,可能會長得像這個樣子:

img

然後我們接著用線條把點連起來~

img

我們發現這些點形成了一個很崎嶇而且隨機的波形

這時候可能就會有人想:

有沒有可能透過某種方式,去產生一個相對不這麼崎嶇,但是同樣是隨機的波形呢?

當然有,而這個問題的解法之一就是我們所謂的噪聲(Noise)

img

噪聲(noise)這種函數/ 演算法可以藉由傳入座標值(可以是一維空間二維空間甚至到多維空間)而得到一個隨機數,而這個隨機數在 『傳入的座標值為鄰近座標』的情況下,會得到大小接近的值,所以像這樣的函數就可能帶來和緩但卻隨機的波形曲線

噪聲其實有很多種類型,主要是因為演算方式的不同,而會在波形規律的細節上有差異。而在電腦動畫或電腦繪圖的領域,最常出現的噪聲就是『柏林噪聲(Perlin's Noise)』,它是當代科學家Ken Perlin所發明的一種演算方法。

是的,這位老兄還活著,而且是個中年大叔,他不是古人~

噪聲能被運用的範圍很廣,但是他在電腦繪圖領域最常被使用的地方就在於創造自然的凹凸面,像下面這個3D模型就是柏林噪聲的應用範例之一,而我們這次要介紹的案例也是要使用到柏林噪聲函數來做運算~

img

我們對於噪聲的介紹就差不多到這個地方,接著我們要來看看我們這次的實作~

如果對柏林噪聲還有興趣的話可以點這邊

噪聲在Canvas繪圖的實際運用案例

github repo: https://github.com/mizok/ithelp2021/blob/master/src/js/silky-wave/index.js

github page: https://mizok.github.io/ithelp2021/silky-wave.html

光看影片可能不好理解這個動畫是怎麼實作出來的,所以我們一步一步講

這個動畫主要的部分就是核心方法 - drawAll()

drawAll()是一個在每一圈RAF都會運作的渲染方法,他在一幀內執行的內容大概是這樣

  • 把整張canvas填滿config中設定的背景色
  • 如我們所見,這個案例其實是由很多波動的線條所構成的,而在這個方法中我們會有兩層迴圈,外面那圈會決定一條線條的顏色深淺,然後裡面那圈則是負責畫波形線條

外面那圈迴圈基本還算好理解,所以這邊會把重點放在裡面那圈迴圈。

首先這行的用意在於把一整個canvas沿著x軸方向分割成很多部分來執行,這個就是波形的成因(波形其實是由很多細小的折線所構成的~)

for (let x = 0; x < this.cvs.width + this.config.vertexGap; x += this.config.vertexGap)

然後接著重點就是這部分

let randomNoise = perlinNoise(x * this.config.horizontalNoiseParameter, i * this.config.verticalNoiseParameter, this.frameCount * this.config.frequency);
        let y = linearInterpolation(randomNoise, 0, 1, 0, this.cvs.height);

randomNoise會用柏林噪聲的函數回傳一個浮點數,而這個浮點數是藉由輸入x,y,z值來得到的。
我們在這邊輸入的x會是內圈for loop在迭代時取得的canvas片段位置座標。
y值是外圈for loop在迭代時使用的變數i。
最後z值則是動畫當下已經執行的總幀數。

我們剛剛有說過柏林噪聲的函數,會因為傳入的座標位置相鄰,而產生相似的值。

假設今天內圈跑完行程(也就是外圈執行一次的情況),我們可以發現內圈行程的每圈:

傳入的x: 基本不相同(因為x座標會迭代)
傳入的y: 基本相同(因為迭代的i沒有變)
傳入的z: 基本相同(因為是同一幀)

而這樣內圈跑了一圈之後,我們就會的到一連串相鄰的x/y/z座標值(只有x不一樣),而我們把他丟到柏林噪聲的函數中,就會得到一連串近似的浮點數,最後我們再把這一連串的浮點數拿去乘以canvas的高,就得到了一連串位於canvas的y座標,然後再搭配迭代的x值,就會形成一條波形。

然後接著外圈執行第二次,內圈執行第一圈,這時我們會發現:

傳入的x: 跟之前外圈執行第一次,內圈執行第一圈時一樣(因為x座標歸0了)
傳入的y: i 因為外圈是跑第二次所以+1了
傳入的z: 基本相同(因為還是同一幀)

我們會發現在外圈執行第二次時,所得到的浮點數都會略比上一批第一圈得到的浮點數多(少)一點點,這樣結果就造就了一條位置略偏差一咪咪的波形曲線。

後面我應該就不用講了吧~

大致上原理就是這樣了,這邊我再把drawAll方法的源碼補上,給各位方便對照~


drawAll() {
    
    this.background(this.config.bgColor);
    for (let i = 0; i < this.config.range; i++) {
    
       //定義單一線條顏色
      let thisLineAlpha = linearInterpolation(i, 0, this.config.range, 0, 1); 
      this.ctx.strokeStyle = `rgba(255,255,255,${thisLineAlpha})`;
      this.ctx.globalAlpha = this.config.globalAlpha;
      //把水平座標分割成複數段落
      for (let x = 0; x < this.cvs.width + this.config.vertexGap; x += this.config.vertexGap) {
        let randomNoise = perlinNoise(x * this.config.horizontalNoiseParameter, i * this.config.verticalNoiseParameter, this.frameCount * this.config.frequency);
        let y = linearInterpolation(randomNoise, 0, 1, 0, this.cvs.height);
        if (x === 0) {
          this.ctx.beginPath();
          this.ctx.moveTo(x, y);
        }
        else if (x < this.cvs.width + (this.config.vertexGap / 2)) {
          this.ctx.lineTo(x, y, x, y + 100)
        }
      }
      this.ctx.stroke();
    }
    requestAnimationFrame(this.drawAll.bind(this))
  }

小結

這次我們示範了柏林噪聲在canvas渲染中實際的運用案例,下一次我們就要進入主題: 『噪聲地形』實作了~

敬請期待 :D ~


上一篇
Day 28 - 3D繪圖篇 - 2D圖片上面的3D物件是怎麼產生的?II - 成為Canvas Ninja ~ 理解2D渲染的精髓
下一篇
Day 30 - 3D繪圖篇 - 噪聲地形演算II - 成為Canvas Ninja ~ 理解2D渲染的精髓
系列文
成為Canvas Ninja ! ~ 理解2D渲染的精髓31

尚未有邦友留言

立即登入留言