極座標 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/極座標/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Tue, 20 Jul 2021 08:04:26 +0000 zh-TW hourly 1 https://wordpress.org/?v=6.2.2 https://creativecoding.in/wp-content/uploads/2022/03/cropped-cct-logo-icon-2-32x32.png 極座標 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/極座標/ 32 32 【p5.js創作教學】Quantum Unstable 量子不穩定 – 發光糾纏的量子系統 https://creativecoding.in/2021/07/19/p5-js%e5%89%b5%e4%bd%9c%e6%95%99%e5%ad%b8-quantum-unstable-%e9%87%8f%e5%ad%90%e4%b8%8d%e7%a9%a9%e5%ae%9a/ Mon, 19 Jul 2021 03:37:00 +0000 https://creativecoding.in/?p=1278 利用p5.js製作出變形的同心圓動態,沒有特別的創作點子時就隨意調整、利用不同的變數增加變化,終究會等到那個靈光一閃,增加許多畫龍點睛的細節,打造出科技HUD風格的量子系統作品《量子不穩定Quantum Unstable》

這篇文章 【p5.js創作教學】Quantum Unstable 量子不穩定 – 發光糾纏的量子系統 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
當初老闆一開始在做這個作品時,是沒有甚麼特定想法的,過程中其實也是蠻迂迴的。

一開始先以極座標的方式去定義點的位子,並用 curveVertex() 將點連成線。過程中不斷去調整 curveVertex() 中 X與Y的位置,到了步驟五,呈現出一顆中心原子核的時候才比較確定這次的主題,接下來就決定朝向物理以及科幻的風格去做,所以後面就加上了在四周圍繞的游離粒子、標示刻度的數字以及線條來包圍中間的量子完成作品。

《量子不穩定》作品完成圖
《量子不穩定》作品完成圖

透過此次互動藝術創作教學,你會學到

  • 在製作 HUD 這類科幻類型主題時,可以使用哪些裝飾性的技巧
  • 如何應用極座標的概念,來繪製線條,並透過旋轉角度速度、內外層圓圈、圓周上位子等不同的參數來調整出所希望的效果。
  • 利用原有的素材 ( X與Y的位置),來產生出不一樣的物件效果 (游離粒子)

在開始製作之前你該知道的p5.js小技巧

  • 極座標 來繪製粒子位置,並將點的位子連接起來形成曲線
  • vertex() 有極座標位置後,以 vertex() 將點連成線,需要在畫線的前後加上 beginShape()endShape()
  • curveVertex()vertex() 相同將點連成線,差異在於 vertex() 所連成的線為直線,而 curveVertex() 連成的為曲線
  • 透過 noise() 產生隨機的效果,比起一般常用的 random() 來說會更自然、和諧一點。老闆的範例中會時常使用 noise,所以建議在開始之前點進去先稍微理解 noise 的概念
  • drawingContext 繪製陰影
  • push() / pop() 保存與還原畫布的狀態(可以參考老闆互動藝術程式創作入門的課程筆記:章節 7 進階繪圖 – 畫布操作與編織複雜圖形 (pop/push 的圖解)
  • setAlpha() 可設定顏色的透明度

就讓老闆帶你們一步步完成吧!

《量子不穩定》全步驟圖
《量子不穩定》全步驟圖

1. 繪製圓形與波形

在最初的時候我們先由畫布中心向外畫出同心圓,來確認一下每一個圓大致上的位置。(程式碼中的1. 同心圓版本)

接下來就將原本繪製圓形的 ellipse 替換成以極座標 cos 與 sin 來作呈現,詳細的原理可參考 來用可怕的三角函數做網頁吧! – Part 1 。這樣我們就可以動態地指定半徑 – rr 的大小。(程式碼中的2. 改成 vertex,並以 cos 以及 sin 來指定點的位置)

另外,也可以更改 ang 的角度,展示出不同的波形,而途中會產生動態旋轉效果的原因是因為老闆在設定 ang 中,加入了系統變量(也就是計算程式啟動以來的幀數 frameCount)。(程式碼中的3. 以 frameCount 來產生動態的角度變化 ang,隨時間製造出不同的波型)

這裡提供一個畫圓的示範連結,可以嘗試改變 cos 與 sin 裡面的 i ,以及後面所相乘的 50 ,透過修改去理解各個參數所代表的是甚麼含意。

function setup() {
  createCanvas(800, 800);
}

function draw() {
  background(230)
  translate(width/2,  height/2)

  // 1. 同心圓版本
  for(var i=0; i<width; i+=100){
    ellipse(0, 0, width-i);
  }

  // 2. 改成 vertex,並以 cos 以及 sin 來指定點的位置
  for(var i=0; i<width; i+=100){
    beginShape()
    let rr = width-i;
    // ellipse(0, 0, width-i);
    for(var o=0; o<360; o+=3){
      vertex(cos(o)*rr, sin(o)*rr)
    }
    endShape()
  }

  // 3. 以 frameCount 來產生動態的角度變化 ang,隨時間製造出不同的波型
  for(var i=0; i<width; i+=100){
    beginShape()
    let rr = width-i;
    // ellipse(0, 0, width-i);
    for(var o=0; o<360; o+=3){
      let ang = o/1.2+frameCount;
      vertex(cos(ang)*rr, sin(ang)*rr)
    }
    endShape()
  }
}

2. 加上顏色

在顏色的選擇上,老闆一樣使用配色網站 https://coolors.co/ 抓取隨機產生的配色,在選定好顏色後,可以直接複製上面的網址到程式中處理,變成可以使用的色票格式。

而在波形上老闆有做三個更動:

  • 減少連接點的數量,360 個 → 100個。
  • 在點與點的連接上,由 vertex 改為 curveVertex,差異在於 curveVertex 在連接點與點之間會帶有弧形,會比較有滑順感,vertex 則是直線連接。
  • 在 ang 角度的設定上,若是把係數拿掉,會長成 o / i + frameCount / o ,其中 i 所代表的是由內而外的每一圈,當它被放在分母的時候,這也代表當越外圈( i 越小),o / i也會越大,再參數相加後,ang 也跟著變大,這樣就達成了越外圈頻率越快,越是內圈頻率越慢的效果。
/*1. 設定顏色 */
let colors = "3d348b-7678ed-f7b801-f18701-f35b04".split("-").map(a=>"#"+a)

function setup() {
  createCanvas(800, 800);
}

function draw() {
  background(0)
  translate(width/2,  height/2)
  strokeWeight(2)
  noFill()
  for(var i=0; i<width; i+=100){
    beginShape()
    let rr = (width-i)*0.8;
    // ellipse(0, 0, width-i);
    /*1. 設定顏色 */
    stroke(colors[int(i/100)%5])
    /*2. 改變波型數量: 360 -> 100 ; 改變角度(ang)設定  ; Vertex -> curveVertex */
    for(var o=0; o<100; o+=3){
      let ang = o/(1.2+i/2000)+frameCount/(40+o);
      curveVertex(cos(ang)*rr, sin(ang)*rr)
    }
    endShape()
  }
}
《量子不穩定》步驟二階段性成果
《量子不穩定》步驟二階段性成果

3. 加上材質 調整波型大小

接著為了讓上面的波型有點分段,所以加上開始的角度 stAng ,在 stAng 中,老闆根據上該點是第幾圈 i,以及是在圓周上哪個位置上 o,來決定是哪個角度開始。

而在每一個點的位置上,老闆在 x 與 y 的座標上都加上 i 值,因為所有的點都同時加上一樣的正值,所以原本可以完整圓的畫面,現在看去只能顯示原本圖形的左上的部分,其餘的點並非消失,而是該點的位置已經超出畫面的範圍了。

/* 2. 加上  start angle */
for(var o=0; o<100; o+=3){
  let stAng = frameCount/1000 + (o/2) + (i/2)
  let ang = stAng+o/(1.2+i/2000)+frameCount/(40+o);
  /* 3. 在 x 與 y 的位置上都加上 i */
  curveVertex(i+cos(ang)*rr, i+sin(ang)*rr)
}

下面是以只有一個圓的簡化版本,並且標示出位置點,如此一來比較容易思考點在位置上的變化。

《量子不穩定》步驟三:比較位置點
《量子不穩定》步驟三:比較位置點

除了調整波型外,老闆也在這裡加上材質,新增材質共有三大步驟,分別是命名材質、設定材質、疊加材質。

  • 命名材質
    在全域中命名材質變數 let overAllTexture
/* 1. 加上材質 */
let overAllTexture
  • 設定材質
    setup() 中設定材質的樣式
  overAllTexture=createGraphics(width,height)
  overAllTexture.loadPixels()
  // noStroke()
  for(var i=0;i<width+50;i++){
    for(var o=0;o<height+50;o++){
      overAllTexture.set(i,o,color(100,noise(i/3,o/3,i*o/50)*random([0,40,80])))
    }
  }
  overAllTexture.updatePixels()
  • 疊加材質
    draw() 中改變與色塊的混合模式
push()
  blendMode(MULTIPLY)
  image(overAllTexture,0,0)
pop()
《量子不穩定》步驟三階段性成果
《量子不穩定》步驟三階段性成果

4. 調整縱向比例製造中心壓縮

這裡老闆先將原本上個步驟在 x 與 y 位置加上 i 的效果先拿掉。改以增加 x 參數中半徑的大小,在 rr 後加上了 i/2 ,這會左右拉伸,製作出被上下壓縮的效果。而在 y 在位置上則是改加上 i/2,產生出由上而下的,俯瞰的感覺。為什麼這裡不是一樣加上 i 呢 ? 這是因為這會使圖形超出畫面的範圍。

  /*3. 線條變粗 */
  strokeWeight(10)

  /*1. 將 X 變小 */
  for(var o=0; o<100; o+=5){
    let stAng = frameCount/1000 + (o/2) + (i/10)
    let ang = stAng+o/(3+i/5000)+frameCount/(30+o);
    curveVertex(cos(ang)*(rr+i/2), i/2+sin(ang)*rr)
  }

接著老闆替線條加上陰影,在陰影的設定上,有四個基本的參數,分別是顏色 (shadowColor)、 X 的偏移量(shadowOffsetX)、 Y 的偏移量(shadowOffsetY)以及模糊的程度(shadowBlur)。加上陰影是老闆在很多的創作時候會使用的手法,當這個陰影就是根據線條本身改變的時候,就會有拼貼的立體感出來。

function setup() {
  /*設定材質 */

  /*2. 加上陰影 */
  drawingContext.shadowColor = color(0,100)
  drawingContext.shadowOffsetY = 3
  drawingContext.shadowOffsetX = 3
  drawingContext.shadowBlur = 20
}
《量子不穩定》步驟四階段性成果
《量子不穩定》步驟四階段性成果

5. 移動中心點繞中心旋轉

再次調整波型呈現方式 !

這裡回歸到原始純粹的模樣,將 stAng 設定為 0,並且將原本加在 y 位置上的i/2 拿掉,這樣就像是一個圍繞的中心在旋轉的粒子球。

/*1. 回歸到最於原始,並且變成是繞中心點旋轉 */
let rr = (width-i)*0.5
...
for(var o=0; o<100; o+=5){
  let stAng = 0
  let ang = stAng+o/(3+i/5000)+frameCount/(30+o);
  curveVertex(cos(ang)*(rr+i/2), sin(ang)*(rr-i/10))
}

接著就把作品固定到中央,並且讓它慢慢地旋轉,此時突然發現這好像磁力線以及極光的感覺,所以就決定朝物理學的方向做,便是外圍先作圓形,在外面做一圈圓形,將原本裡面的線條做限制,並且將裡面的圓縮小且帶旋轉。

function draw() {

  /*4. 加上背景 */
  fill(30,30,100,0.85)
  rect(0,0,width,height)

  push()
    /*1. 將球中心置於畫布中心 */
    translate(width/2,height/2)
    /*3. 縮小並旋轉 */
    scale(0.7)
    rotate(frameCount/500)
    stroke(255,150)

    /*2. 外圍加上一圈 */
    noFill()
    strokeWeight(10)
    ellipse(0,0,900)
  
    for(var i=0; i<width; i+=100){
      ...
    }

  pop()
}
《量子不穩定》步驟五階段性成果
《量子不穩定》步驟五階段性成果

6. 加上發光效果跟調整材質

再加上圓形後,就開始覺得這個東西應該是會發光的,所以加上 Screen mode,不過視覺上又會覺得顏色有點太多層,所以加上了 setAlpha ,以降低透明度的方式來去做調整,而且老闆還特別將線條與陰影分開做設定,可以看到陰影與線條的顏色是一樣的 (let cc = color(clr)) ,只是透明度不同,透過這樣將 setAlpha 降低,畫面上就不會過曝,顏色看上去也就會比較明顯一點。

調整完色彩後,覺得可以再多加上一些變化,所以就再加上了 cos(ang*8+o/100)*rr/200 以及 sin(ang*8+o/100)*rr/200 ,前面這串程式看上去很長,加上現在圖形還同時在旋轉,不太容易看出效果,所以這邊可以試著將程式簡化,用一開始所提供的同心圓範例去修改成簡單版本的(如下方示意圖)所呈現的效果是可以看到不只是完整的大波,上面另外也會有一些小的褶皺。

push()
  ...
  /*1. SCREEN mode */
  blendMode(SCREEN)

  for(var i=0; i<width; i+=100){
    beginShape()
    strokeWeight(8)
    let rr = (width-i)*0.5
	
    /*2. 降低透明度,本體與陰影分開 */
    let clr = color(colors[int(i/100)%5])
    clr.setAlpha(150)
    stroke(clr)
	
    let cc = color(clr)
    cc.setAlpha(100)
    drawingContext.shadowColor =cc
	
    /*3. 加上小的波形 */
    for(var o=0;o<150;o+=5){
      let stAng =0 
      let ang = stAng+o/(3+i/5000)+frameCount/(30+o)+mouseY/100
      curveVertex(
        cos(ang)*(rr+i/2)+ cos(ang*8+o/100)*rr/200 ,
        sin(ang)*(rr-i/10) + sin(ang*8+o/100)*rr/200
      )
    }
    endShape()
  }

調到這裡其實這次的主題就明確了,就是物理量子力學的主題。

《量子不穩定》步驟六階段性成果
《量子不穩定》步驟六階段性成果

7. 加上外部裝飾

接下來,在做這種科幻型的主題時,老闆的慣用手法就是加上一些弧形或者是刻度。這裡老闆決定在四周畫上線條,有點像是準心的感覺,並且加上有一個似乎意義不明的數字,這樣一來就產生一種「好似有複雜的機制在後面運作」的感覺,為了避免顯示的數字太長,所以在數字上使用 toFixed(2) 來限制小數點後可顯示的位數為兩位數。另外在此星球旋轉上也加上了 mouseX ,讓滑鼠在移動時,也會進而影響星球旋轉的速度。

function draw() {
  push()
    /*1. 旋轉上加上滑鼠互動 */
    translate(width/2,height/2)
    scale(0.73)
    let totalRotate = frameCount/500+mouseX/100
    rotate(totalRotate)

    /*2. 加上四象的線條,像是準心 */
    stroke(255)
    line(480,0,550,0)
    line(-480,0,-550,0)
    line(0,-480,0,-520)
    line(0,480,0,520)
    noStroke()
    fill(255)
    textSize(20)
    text(totalRotate.toFixed(2),500,30)
  pop()
}
《量子不穩定》步驟七階段性成果
《量子不穩定》步驟七階段性成果

8. 加上游離粒子

這裡老闆想要加上一些游離粒子,而加上游離粒子的方式非常的特別,因為曲線看上去是一條線,但是實際上都是一個點一個點所連接起來的,所以老闆就想說那不如把 X 與 Y 拿來畫。

在將 X 與 Y 拿來使用前,先將畫曲線的點抽出來命名變數 let xx = cos(ang)*(rr+i/2)+ cos(ang*8+o/100)*rr/200 以及 let yy = sin(ang)*(rr-i/10) + sin(ang*8+o/100)*rr/200 ,這樣在畫曲線 curveVertex(xx,yy)ellipse(xx,yy,3) 粒子都會比較方便,程式碼也不會顯得太長。透過直接使用畫曲線的點去畫粒子,可以讓視覺上粒子與線條具有一致性的效果。

這裡有一點要注意,由於在粒子有使用 scale(1.4) 放大,為了不影響到其他物理,所以要用 pushpop把它包起來。

for(var o=0;o<150;o+=5){
  let stAng =0 
  let ang = stAng+o/(3+i/5000)+frameCount/(30+o)+mouseY/100
  let xx = cos(ang)*(rr+i/2)+ cos(ang*8+o/100)*rr/200
  let yy = sin(ang)*(rr-i/10) + sin(ang*8+o/100)*rr/200
  curveVertex(
    xx ,yy
  )
  /*1. 加上游離粒子 */
  if (random()<0.1){
    push()
    scale(1.4)
    ellipse(xx,yy,3)
    pop()
  }
}
《量子不穩定》步驟八階段性成果
《量子不穩定》步驟八階段性成果

9. 加上裝飾

再來加一些一些裝飾性的物件,希望可以達到畫龍點睛的效果。

外圈還缺甚麼呢?一開始想要加像是光場或是力場的東西把它包住,但是又覺得似乎有些干擾,而且目前已經有一圈白線,所以改畫個在 HUD 裡面蠻常見的手法 – 虛線,畫弧形虛線的方式就是用小小的 arc 來畫,在畫面的下方。

到這裡差不多要做結尾了,這邊老闆在思考如何讓畫面變得更豐富,不是說硬將東西塞上去,而是能不能透過輔助的小物件,讓整件作品更有故事性。一開始老闆有想說加上像是折線圖或條圖,代表星球當下的狀態或速度,讓人可以知道這個星球當下的狀態。後來決定在整體的旋轉上面加上 noise,製造出就像是一個羅盤旋轉的感覺。

/* 1. rotate 加上 noise */
translate(width/2,height/2)
scale(0.73)
let totalRotate = frameCount/500+mouseX/100 + noise(frameCount/1000)*5
rotate(totalRotate)

stroke(255,150)
noFill()
strokeWeight(4)
ellipse(0,0,900)

stroke(255)
line(480,0,550,0)
line(-480,0,-550,0)
line(0,-480,0,-520)
line(0,480,0,520)
/* 2. 裝飾性橢圓 */
ellipse(0,550,50,20)
ellipse(0,-550,50,20)
ellipse(0,580,30,10)
ellipse(0,-580,30,10)
noStroke()
fill(255)
textSize(20)
text(totalRotate.toFixed(2),500,30)

noFill()
blendMode(SCREEN)

/* 3. 裝飾性線條 */
for(var i=0;i<200;i+=10){
  stroke(255,50)
  arc(0,0,1200,1200,PI/200*i,PI/200*i+0.035)
}
《量子不穩定》步驟九階段性成果
《量子不穩定》步驟九階段性成果

10. 加上原子核

既然作品現在看上去像是一個粒子,所以總該要有個原子核,而這個核心很不穩定,而周圍有個力場包圍住。為了製造出不穩定的感覺,所以在原子的位置以及大小,都加上了 noise 來產生出隨機的效果。

/* 1. 中間不穩定的圓心 */
fill(random()<0.3?colors[2]:255)
ellipse(0 + (noise(frameCount/5,10)-0.5)*50,
	0+ (noise(frameCount/3,1000)-0.5)*50,
	30+ noise(frameCount/10)*40)
noFill()
《量子不穩定》步驟十成果
《量子不穩定》步驟十成果

結語

我們總結一下這次的量子系統,製作過程可以分為三大部分:

  1. 中心帶有磁力線量子效果的圓心是此次作品的核心,老闆使用極座標的方式是先去定義每一個點的位置,再使用 curveVertex 以將點連接成一成線條。在極座標的表示上是 (cosθ * 半徑,sinθ * 半徑),在過程中不斷透過改變半徑比例的調整以及點的位置。 在這邊只要一個小小的變化,像是將半徑數值除以二,就會形成一個被壓縮的球體,建議大家可以一開始所提供畫圓的範例,多去嘗試改變其中不同的參數去觀察所對應會產生什麼效果,也因為是各個點都是相對獨立的,所以在位置的變化上可以有相對多的彈性,像是老闆在過程中就嘗試了加上 stAng 以及 ang 來試試看能不能有些有趣的變化,說不定你在嘗試的過程中就會發展出另一個和老闆完全不一樣的作品出來。
  2. 延續前面所說,線條實際上是由每一個點來定義,所以老闆就利用這樣的機會,透過定義好的點,加上一點變化形成四周的游離粒子;增加畫面的豐富度、疊加材質,做出與滑鼠X與Y位置的小互動。
  3. 想要製作 HUD 風格的作品可以加上單一顏色外框以及虛線線條,尤其可加上看似神秘的數值會更有效果。

這就是我們用p5.js寫出來的簡單的小作品啦!老闆的成品這邊去,也非常歡迎大家到社團裡跟我們分享你們完成的作品。

相同的繪製原理還能應用在甚麼作品上呢?若對p5.js寫成的互動藝術程式創作有興趣,歡迎加入老闆開的Hahow互動藝術程式創作入門課程,與另外將近兩千位同學一起創作吧!

此篇直播筆記由幫手 阮柏燁 協助整理

墨雨設計banner

這篇文章 【p5.js創作教學】Quantum Unstable 量子不穩定 – 發光糾纏的量子系統 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js創作教學】來畫一個瘋狂的龍捲風吧!(直播筆記) https://creativecoding.in/2021/06/04/p5-js%e5%89%b5%e4%bd%9c%e6%95%99%e5%ad%b8-%e4%be%86%e7%95%ab%e4%b8%80%e5%80%8b%e7%98%8b%e7%8b%82%e7%9a%84%e9%be%8d%e6%8d%b2%e9%a2%a8%e5%90%a7/ Fri, 04 Jun 2021 02:23:00 +0000 https://creativecoding.in/?p=897 身為台灣人可能沒親眼看過龍捲風,但也大概它知道長甚麼樣子,威力又有多強大。今天我們要利用p5.js來完成瘋狂的龍捲風捲起乳牛與房屋的一張動態圖片。

這篇文章 【p5.js創作教學】來畫一個瘋狂的龍捲風吧!(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
身為台灣人可能沒親眼看過龍捲風,但從新聞或影片畫面裡大概它知道長甚麼樣子,威力又有多強大。

瘋狂的龍捲風-成品

今天我們要利用p5.js來完成瘋狂的龍捲風捲起乳牛與房屋的一張動態圖片,在最一開始老闆當然是先嘗試著刻劃龍捲風的外型,接著分階段完成被龍捲風捲起的物體:乳牛以及穀倉,最後進行美術上的修改,包含了背景的顏色、加上材質以及調整龍捲風的線條等,最後加上滑鼠的互動來增加整體作品的豐富度。

透過此次互動藝術創作教學,你會學到

  • 以幾何圖形繪製龍捲風,其中涵蓋了線條的調整,使用透明的填色讓龍捲風有雲霧感
  • 如何應用極座標的概念,使用三角函數的觀念來繪製不規則飛行的物體的運動軌跡
  • 繪製漸層背景的技巧,以及透過旋轉來增加背景的動態感
想要更了解三角函數,歡迎閱讀這兩篇複習:
來用可怕的三角函數做網頁吧! – Part 1 衛星繞月球(直播筆記)
來用可怕的三角函數做網頁吧! -Part 2科幻時鐘(直播筆記)

在開始製作龍捲風之前,你該知道的p5.js小技巧

就讓老闆帶你們一步步完成吧!

利用p5.js創作《瘋狂的龍捲風》步驟拆解
利用p5.js創作《瘋狂的龍捲風》步驟拆解

1. 畫龍捲風

讓我們一起先來思考龍捲風看上去的樣子,它其實就是一層層圓圈,由下面的一個小圓圈開始慢慢地往上,圓圈變得越來越大。一開始我們先嘗試由上而下劃出一排圈圈,暫時不考慮大小的變化排列。

function setup() {
  createCanvas(800, 800);
  background(100);
}

function draw() {
  translate(width/2, 0)
    for(var i=0; i<height; i+=2){
      let nn = noise(i/10, frameCount/10)
      ellipse(0, i, nn*100)
    }
}
步驟一:畫龍捲風
步驟一:畫龍捲風

下一步要讓圈圈有大小區分的排列,我們必須由 “nn” 這個變數上來調整,在後面加上 “i” 這個變數,這樣一來,nn 的數字大小也會相對應地跟著從小到大。

function setup() {
  createCanvas(800, 800)
  background(100)
}

function draw() {
  translate(width/2, 0)
  for(var i=0; i<height; i+=2){
    let nn = noise(i/10, frameCount/10) + i/10
    ellipse(0, i, nn*10, nn*3)
  }
}
步驟一:畫龍捲風,慢慢修改形狀

然而這樣上面小、下面大的樣式並不是我們所要的,所以在 nn 變數中的 i 要改成 “height-i”,這樣才能讓數字由大到小排列,所畫出的樣子才會像是龍捲風的模樣。除了大小排列外,我們也順便將圖形刪除填色,留下僅有線條的樣子。
我們使用 nn 這個變數,可以發現它在一開始的時候有加上 noise 的效果,這是一個可以產生出隨機的變數,有了它,龍捲風看上去更增添動態感。

function setup() {
  createCanvas(800, 800)
  background(100)
}

function draw() {
  fill(0)
  rect(0,0,width,height)
  translate(width/2, 0)
  stroke(255)
  noFill()
  for(var i=0; i<height; i+=3){
    strokeWeight(random(2))
    let nn = noise(i/10, frameCount/100)*20+(height-i)/9
    ellipse(0, i-100, nn*8, nn*3)
  }
}
步驟一:畫龍捲風慢慢成形
步驟一:畫龍捲風慢慢成形

2. 加上被捲飛的物體

一開始我們必須思考物體被龍捲風捲起時的運動軌跡,先不用繪製乳牛、車子這些我們最後真正所要呈現的物體,先以簡單的方塊完成軌跡之後,最後再替換掉即可。

在物體位置的繪製上,我們要使用極座標的方式表示,極座標需給定半徑r,以及角度 θ,有了這兩個參數,那一個點在平面座標上就可以表示為 [rcosθ,rsinθ]。老闆使用 ang 表示角度, r 來表示半徑。設定好後,就使用 translate 來進行偏移。現在的原點位於整張圖中間最上方的位置,為了讓物體平均分散在整個龍捲風的周圍,所以在 y 座標中,會再加上 o*50,來讓物體是由上而下來進行分布的。

// 龍捲風
for(var i=0; i<height; i+=3){
    strokeWeight(random(2))
    let nn = noise(i/10, frameCount/100)*20+(height-i)/9
    ellipse(0, i-100, nn*8, nn*3)
}

// 飛行物體
for(var o=0;o<10;o++){
    let ang = o+frameCount/10
    let nn = noise(i/10,frameCount/100)*20+(height-i)/9
    let r = nn*10
    fill('blue')
    push()
        translate(cos(ang)*r,sin(ang)*r+o*50)
        rect(0,0,50,50)
    pop()
}
步驟二:加上被捲起的物件
步驟二:加上被捲起的物件

現在看上去,物體仍旋轉得很制式,這是因為每一個物體的角度是直接根據它是第幾個來去設定它的角度的。為了增加隨機性,我們增加一個 rot 變數,並且透過 noise 增加隨機性,設定好再使用rotate做旋轉。

// 飛行物體
for(var o=0;o<10;o++){
    let ang = o*5+frameCount/50
    let nn = noise(i/10,frameCount/100)*20+(height-i)/9
    let r = nn*(30+noise(o,frameCount/50)*5)
    let rot = noise(o,frameCount/100)*10
    fill('blue')
    push()
        translate(cos(ang)*r,sin(ang)*r+o*50)
        rotate(rot)
        fill('white')
        rect(0,0,50,50)
    pop()
}
步驟二:為被捲起的物件增加隨機性

3. 畫牛

設定好物體的旋轉方式後,我們來將方塊換成牛隻。在牛的繪製上可大致分成頭部、身體以及腳。為了使牛看起來精緻可愛,所以這裡大多都是由基本形狀如圓形、方形等等所構成,在這裡就是要考驗大家一筆一筆繪製、調整的耐心了。 這裡比較要注意的是,為了讓牛看起來比較生動些,我們將腳加上一些動畫,不管是前腳還是後腳,老闆都讓它左右邊的腳相差大約一個 PI 的角度左右,腳擺動起來才不會那麼地生硬。

另外,由於現在物體都是被龍捲風吹起來亂亂飛的,有點看不清楚到底有沒有把牛畫正,所以這裡會在 draw() 加入 drawCow(50, height-50),讓下方有一個固定的牛隻方便參考。

function drawCow(x,y){
  push()
    //身體
    translate(x,y)
    fill(255)
    noStroke()
    rect(0,0,50,25,5)
    fill(0)
    rect(5,0,12,15,5)
    rect(25,10,10,10,5)
    rect(45,5,5,10,5)

    // 後腳
    fill(255)
    push()
      translate(3,20)
      push()
        rotate(sin(frameCount/35))
        rect(0,0,5,20,5)
      pop()
      push()
        translate(5,0)
        rotate(sin(frameCount/20+PI))
        rect(0,0,5,20,5)
      pop()
    pop()

    // 前腳
    push()
      translate(40,20)
      push()
        rotate(sin(frameCount/35))
        rect(0,0,5,20,5)
      pop()
      push()
        translate(5,0)
        rotate(sin(frameCount/20+PI))
        rect(0,0,5,20,5)
      pop()
    pop()

    //頭部
    translate(-22,-5)
    rect(0,0,20,25,5)
    fill(255, 179, 160)
    rect(0,15,20,10,5)
    fill(0,60)

    ellipse(6,20,6)
    ellipse(14,20,6)
    fill(0)
    ellipse(5,8,3)
    ellipse(15,8,3)
    fill(247, 216, 150)
    rect(0,-8,3,10,10)
    rect(16,-8,3,10,10)

  pop()
}

// 飛行物體
for(var o=0;o<10;o++){
    let ang = o*5+frameCount/50
    let nn = noise(i/10,frameCount/100)*20+(height-i)/9
    let r = nn*(30+noise(o,frameCount/50)*5)
    let rot = noise(o,frameCount/100)*10
    fill('blue')
    push()
        translate(cos(ang)*r,sin(ang)*r+o*50)
        rotate(rot)
        drawCow(0,0)
        // fill('white')
        // rect(0,0,50,50)
    pop()
}
drawCow(50, height-50)
步驟三:畫牛
步驟三:畫牛

4. 調整龍捲風視覺 + 天空上色

第四部分一開始先將天空調整成偏向綠色外,其餘都是調整龍捲風的視覺,調整內容有以下幾點:

  • 加上陰影
    不管是龍捲風或是飛行的牛隻,都在繪製之前加上陰影,先是指定顏色,接著再去設定其偏移的量
drawingContext.shadowColor = color(0,30);
drawingContext.shadowOffsetY = 0
drawingContext.shadowOffsetX = 0
  • 以 arc 繪製龍捲風線條
    原本繪製的方式是使用 ellipse 形成一個完整的圓,我們改以 arc 角度來繪製,使用 sin 來設定龍捲風的起始位置後,再放入 arc 中,這樣子這樣子看起來更像是旋風。
  • 左右移動
    身為一個龍捲風,左右搖擺起來是一定要的,所以在 translate 的 x 位置再加上 cos(frameCount/10)*10,讓整個龍捲風搖擺起來。
  • 線條與填色
    將構成龍捲風線條註解起來,並且新增填色 fill,讓龍捲風以一個雲霧的感覺呈現

上述幾點調整的位置都是 draw() 中,修改後程式碼如下

function draw() {
  fill("#9BC1BC")
  noStroke()
  rect(0,0,width,height)
  translate(width/2 + cos(frameCount/10)*10,0)

  // stroke(255,100) // 拿掉 stroke
  noFill()

  // 龍捲風
  drawingContext.shadowColor = color(0,30);
  drawingContext.shadowOffsetY = 0
  drawingContext.shadowOffsetX = 0
  for(var i=0; i<height; i+=4){
    strokeWeight(random(1)+2)
    fill(255,30) // 加上填色
    let nn = noise(i/10, frameCount/100)*20+(height-i)/9
    let stAng = sin(i+frameCount/2)
    arc(0,i-100,nn*8,nn*3,stAng,stAng+PI*1.5)
  }


  // 飛行物體
  noStroke()
  drawingContext.shadowColor = color(0,30);
  drawingContext.shadowOffsetY = 2
  drawingContext.shadowOffsetX = 2
  for(var o=0;o<10;o++){
    let ang = o*5+frameCount/50
    let nn = noise(i/10,frameCount/100)*20+(height-i)/9
    let r = nn*(30+noise(o,frameCount/50)*5)
    let rot = noise(o,frameCount/100)*10
    fill('blue')
    push()
      translate(cos(ang)*r,sin(ang)*r+o*50)
      rotate(rot)
      drawCow(0,0)
    pop()
  }
  drawCow(50, height-50)
}
步驟四:調整龍捲風視覺以及將天空上色
步驟四:調整龍捲風視覺以及將天空上色

5. 漸層天空

我們要把原本單色系背景改為漸層天空,先將原本的背景註解起來。在繪製漸層背景上,首先先指定一個稍微偏綠色的藍,接著每隔一層線條就加上數值五的藍色,使其越來越偏向藍色,但是單純只有漸層似乎還是有點單調,畢竟此次的主題是龍捲風,所以老闆同時也加上不規則的旋轉,使整體更加有一種不受控制的感覺。

// 漸層背景
let cc = color("#6AD5CB")

push()
  rectMode(CENTER)
  for(var i=0;i<height;i+=100){
    push()
      cc.setBlue(cc._getBlue()+5)
      fill(cc)
      rotate(random(-1,1)/10)
      rect(width/2,i,width*1.2,100)
    pop()
  }
pop()


// 原本的背景
// fill("#9BC1BC")
// noStroke()
// rect(0,0,width,height)
步驟五:將天空改為漸層有生命力
步驟五:將天空改為漸層有生命力

6. 加上材質

為了增加一點繪畫感,我們可以為整個畫面新增材質疊加。共有三大步驟,分別是命名材質、設定材質、疊加材質。

  • 命名材質
    在全域的區域命名材質變數 let overAllTexture
  • 設定材質
    setup() 中設定材質的樣式
overAllTexture.loadPixels()
// noStroke()
for(var i=0;i<width+50;i++){
  for(var o=0;o<height+50;o++){
    overAllTexture.set(i,o,color(100,noise(i/3,o/3,i*o/50)*random([0,30,50])))
  }
}
overAllTexture.updatePixels()
  • 疊加材質

setup() 中改變與色塊的混合模式

push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
pop()
步驟六:為畫面增添繪畫感
步驟六:為畫面增添繪畫感

加上材質後,畫面上黑色一條一條的痕跡更明顯了,我們稍微將它修正成沒有線條,並且改變透明度。

push()
let cc = color("#6AD5CB")
cc.setAlpha(100) // 設定透明度
noStroke() // 設定沒有線條
  push()
    rectMode(CENTER)
    for(var i=0;i<height;i+=100){
      push()
        cc.setBlue(cc._getBlue()+5)
        fill(cc)
        rotate(random(-1,1)/10)
        rect(width/2,i,width*1.2,100)
      pop()
   }
  pop()

// fill("#9BC1BC")
// noStroke()
// rect(0,0,width,height)
步驟六:修正背景線條
步驟六:修正背景線條

7. 加入穀倉

在畫穀倉上也沒有太高深的技巧,與繪製牛一樣,像個手工業一樣慢慢一筆一畫描繪出來。不過有一個比較少看到的用法是 shearX ,它可以將物體沿著X軸歪斜指定角度,用來協助繪製梯形的屋頂。 另外老闆在生成這些穀倉時,並沒有再多寫一個迴圈,而是直接用餘數的方式去決定要生成牛還是穀倉。原先放在左下角當成繪製參考的穀倉也沒有拿掉,因為老闆後來覺得將單一穀倉就這樣固定在那裏也不錯。

function drawHouse(x,y){
  push()
    translate(x,y)
    // rect(0,0,100,50)
    fill(199, 77, 93)
    rect(20,0,80,40)
    fill(219, 77, 93)
    rect(0,0,80,40)
    fill(255,50)
    rect(0,0,80,6)

    stroke(255,100)
    strokeWeight(2)
    noFill()
    rect(40,10,20,10)
    rect(40,20,20,10)
    rect(40,10,10,20)
    noStroke()

    //屋頂
    shearX(PI/12)
    fill(68, 61, 63)
    rect(20,0,80,-20)
    shearX(-PI/6)
    fill(58, 51, 53)
    rect(0,0,80,-20)
  pop()
}
// drawCow(0,0)
if (o%6!=0){
  drawCow(0,0)
}else{
  drawHouse(0,0)
}
步驟七:繪製穀倉
步驟七:繪製穀倉

8. 修飾細節跟最後收尾

translate 加上與滑鼠 mouseX 做互動

// translate(width/2 + cos(frameCount/10)*10,0)
translate(width/2 + cos(frameCount/20 + mouseX/50)*50,0) // 加上 mouse

龍捲風的細線條隨機出現

for(var i=0; i<height; i+=4){
  strokeWeight(random(1)+2)
  fill(255,10) // 加上填色

  // 隨機出現線條
  if (random()<0.1){
    stroke(255)
  }else{
    noStroke()
  }

  let nn = noise(i/10, frameCount/100)*20+(height-i)/9
  let stAng = sin(i+frameCount/2)
  arc(0,i-100,nn*8,nn*3,stAng,stAng+PI*1.5)
}
步驟八:最後修飾及收尾即完成

結語

我們總結一下這次的龍捲風小品,製作過程可以分為三大部分:

  1. 龍捲風的繪製,思考龍捲風的模樣以及它具有什麼特性,我們可以用什麼圖形以及線條去模擬,從最一開始使用圓形來構成龍捲風的架構,慢慢地調整細節,讓它更加接近真實的模樣。
  2. 被捲起的物體 – 乳牛與穀倉兩者所使用技巧都是用基本的圖形建構而成。另外使用極座標表示被捲起的軌跡也是相當值得學習的技巧。
  3. 視覺上的調整,加入材質以及漸層背景等應用,以及滑鼠的簡單互動讓作品更加完整。

這就是我們用p5.js寫出來的簡單的小作品啦!相同的繪製原理還能應用在甚麼作品上呢?你們也可以參考老闆這件作品的OpenProcessing頁面,一起來切磋吧!

若對互動藝術程式創作有興趣,歡迎加入老闆開的Hahow課程互動藝術程式創作入門,與其他兩千位同學一起創作吧!

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 【p5.js創作教學】來畫一個瘋狂的龍捲風吧!(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>