p5.js 3D圖形-立方體

本文簡介

帶尬猴固阁,我嗨德育處主任


前面寫了幾篇 p5.js 文章 都還沒涉及到3D圖形,但其實(shí) p5.js 是提供了基礎(chǔ)的3D圖形的城菊。

本文就從最簡單的立方體講起备燃,并做幾個(gè)小demo和各位工友一起掌握立方體的用法。



立方體的基礎(chǔ)用法

p5.js 里使用 box() 方法可以創(chuàng)建立方體凌唬。


基礎(chǔ)語法說明

根據(jù)官網(wǎng)的說明并齐,box() 語法如下:

box([width], [height], [depth], [detailX], [detailY])

官網(wǎng)給出的參數(shù)解釋我覺得有點(diǎn)繞,以下是我的理解

  • width:立方體的寬度(選填)客税,默認(rèn)值是 50况褪。
  • height:立方體的高度(選填)。
  • depth:立方體的深度(選填)更耻。
  • detailX:一個(gè)用于指定立方體在x軸方向上的細(xì)分級(jí)別的數(shù)字窝剖,數(shù)值越大,立方體的表面越平滑酥夭。(選填)
  • detailY:一個(gè)用于指定立方體在y軸方向上的細(xì)分級(jí)別的數(shù)字赐纱,數(shù)值越大,立方體的表面越平滑熬北。(選填)


首先需要了解 width疙描、heightdepth 這3個(gè)參數(shù),它們都是可選參數(shù)讶隐,傳參時(shí)會(huì)出現(xiàn)以下幾種情況:

  1. 3個(gè)參數(shù)都不傳的情況:它們的值默認(rèn)為50起胰。
  2. 只傳 width 的情況:heightdepth 都會(huì)跟著使用 width 的值。
  3. 傳了 widthheight 的情況:depth 會(huì)使用 height 的值巫延。
  4. 3個(gè)參數(shù)都有傳的情況:各自使用各自的值效五。


動(dòng)手試試

先試試創(chuàng)建一個(gè)基礎(chǔ)立方體。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL) // 創(chuàng)建 200 * 200 的畫布
    background(200) // 設(shè)置畫布背景色(灰色)
    box(100) // 創(chuàng)建立方體
  }
</script>

這個(gè)例子使用 box() 創(chuàng)建出來的立方體炉峰,看上去不像立方體畏妖,只是一個(gè)平面。主要原因是我們是正對(duì)著它疼阔,所以只能看到它的一個(gè)面戒劫。

旋轉(zhuǎn)一下角度就看到它是一個(gè)立方體了。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL) // 創(chuàng)建 200 * 200 的畫布
    background(200) // 設(shè)置畫布背景色(灰色)
    // 旋轉(zhuǎn)角度
    rotateX(10)
    rotateY(10)
    box(100) // 創(chuàng)建立方體
  }
</script>


設(shè)置樣式

給立方體設(shè)置樣式婆廊,要把樣式函數(shù)寫在 box() 前Q赶浮!淘邻!


填充色 fill

使用 fill() 方法可以設(shè)置填充色茵典。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL) // 創(chuàng)建 200 * 200 的畫布
    background(200) // 設(shè)置畫布背景色(灰色)

    // 旋轉(zhuǎn)角度
    rotateX(10)
    rotateY(10)
    
    // 填充色
    fill(255, 0, 0)
    
    box(100) // 創(chuàng)建立方體
  }
</script>


不使用填充顏色

box() 的默認(rèn)填充色是白色,如果你不需要填充色宾舅,可以使用 noFill() 方法進(jìn)行修改统阿。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL) // 創(chuàng)建 200 * 200 的畫布
    background(200) // 設(shè)置畫布背景色(灰色)

    // 旋轉(zhuǎn)角度
    rotateX(10)
    rotateY(10)
    
    // 不使用填充顏色
    noFill()
    
    box(100) // 創(chuàng)建立方體
  }
</script>


描邊顏色 stroke

使用 stroke() 方法可以設(shè)置立方體的描邊顏色枚尼。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL)
    background(200)

    noFill()
    stroke(255, 0, 0) // 設(shè)置紅色邊框顏色

    rotateX(10)
    rotateY(10)

    box(100)
  }
</script>


設(shè)置邊框?qū)挾?/h3>

使用 strokeWeight() 方法可以設(shè)置立方體邊框?qū)挾龋枰獋魅胍粋€(gè)數(shù)值型數(shù)據(jù)砂吞。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL)
    background(200)

    noFill()
    stroke(255, 0, 0) // 設(shè)置紅色邊框顏色
    strokeWeight(4) // 設(shè)置邊框?qū)挾?
    rotateX(10)
    rotateY(10)

    box(100)
  }
</script>


紋理

除了基礎(chǔ)的填充和描邊外署恍,立方體還可以設(shè)置紋理。

紋理可以是圖片蜻直,也可以是視頻盯质。我先用圖片資源舉例。

加載資源需要在 preload() 這個(gè)生命周期里處理概而,我在 《p5.js 光速入門》 里有講到呼巷,忘記這知識(shí)點(diǎn)的工友可以去看看。

將紋理貼到立方體上赎瑰,有以下幾個(gè)步驟:

  1. 加載紋理資源(圖片或者視頻)
  2. 設(shè)置紋理
  3. 創(chuàng)建立方體
<script>
  let myTexture = null

  function preload() {
    myTexture = loadImage('texture.gif') // 加載紋理
  }

  function setup() {
    createCanvas(200, 200, WEBGL)
    background(200)

    rotateX(0.5)
    rotateY(0.5)

    texture(myTexture) // 設(shè)置紋理

    box(100) // 創(chuàng)建立方體
  }
</script>

在這個(gè)例子中王悍,我加載了一個(gè) gif 紋理,但這個(gè)紋理貼到立方體上是不會(huì)動(dòng)的餐曼,因?yàn)榱⒎襟w是在 setup() 里創(chuàng)建的压储,如果需要它會(huì)動(dòng),我們需要在 draw() 聲明周期里設(shè)置紋理和創(chuàng)建立方體源譬。這部分我會(huì)放到后面“動(dòng)畫”章節(jié)講集惋。


光照效果

你沒看錯(cuò),p5.js 也有提供了光照效果的踩娘,我在前面的文章沒講過光照效果刮刑,本文也不會(huì)講這部分(我要留到下一篇水文里講),但工友們也可以先了解一下這部分內(nèi)容养渴。

我使用了 環(huán)境光 ambientLight()定向光 directionalLight() 打在立方體上雷绢。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL)
    background(200)

    rotateX(10)
    rotateY(10)

    ambientLight(255) // 設(shè)置環(huán)境光照
    directionalLight(255, 255, 255, 0, 0, -1) // 設(shè)置定向光照

    box(100)
  }
</script>

可以看出,不同面的顏色是有點(diǎn)不一樣的理卑。


動(dòng)畫

要做動(dòng)畫非常簡單翘紊,只需要在 draw() 生命周期里改變立方體的屬性即可。

除此之外傻工,我們還要了解 frameCount霞溪,這是 p5.js 提供的一個(gè)全局系統(tǒng)變量,它記錄了 p5.js 運(yùn)行了多少幀中捆。在 setup() 時(shí),frameCount 的值是0坊饶,之后每執(zhí)行一次 draw() 都會(huì)給 frameCount 加1泄伪。

還不清楚 draw() 這個(gè)生命周期的工友一定要看看 《p5.js 光速入門》的“drap”章節(jié)


旋轉(zhuǎn)動(dòng)畫

比如想做旋轉(zhuǎn)動(dòng)畫匿级,只要在 draw() 里不斷的改變 rotateX 蟋滴、 rotateYrotateX 就能出一個(gè)不錯(cuò)的效果染厅。

<script>
  function setup() {
    createCanvas(200, 200, WEBGL)
  }

  function draw() {
    // 每次刷新都要重新填充畫布顏色,不然會(huì)留下上一次繪制的立方體
    background(200)

    // 旋轉(zhuǎn)
    rotateX(frameCount * 0.01)
    rotateY(frameCount * 0.01)
    rotateZ(frameCount * 0.01)

    // 繪制立方體
    box(100)
  }
</script>


gif 貼圖

在前面的紋理例子中我們已經(jīng)知道怎么貼圖了津函,如果你貼的是gif動(dòng)圖肖粮,又希望這個(gè)圖是真的能在運(yùn)行時(shí)動(dòng)起來,就需要在 draw() 設(shè)置紋理貼圖了尔苦。

<script>
  let myTexture = null

  function preload() {
    myTexture = loadImage('texture.gif') // 加載gif
  }

  function setup() {
    createCanvas(200, 200, WEBGL)
  }
 
  function draw() {
    background(200)

    rotateX(frameCount * 0.01)
    rotateY(frameCount * 0.01)
    rotateZ(frameCount * 0.01)

    // 設(shè)置紋理貼圖
    texture(myTexture)
    box(100)
  }
</script>


視頻紋理

設(shè)置視頻紋理其實(shí)和設(shè)置圖片紋理差不多涩馆,只是加載的資源類型不同。

使用 createVideo() 方法加載視頻資源允坚,然后要將視頻隱藏魂那,不然它會(huì)在頁面中占位。


<script>
let video = null

function preload() {
  video = createVideo('video.mp4') // 加載 mp4
  video.hide() // 隱藏視頻元素
}

function setup() {
  createCanvas(640, 480, WEBGL)
  video.loop() // 循環(huán)播放
  video.volume(0) // 設(shè)置音量
}

function draw() {
  background(200)

  rotateX(frameCount * 0.01)
  rotateY(frameCount * 0.01)

  // 設(shè)置視頻紋理
  texture(video)
  box(200)
}
</script>

上面的代碼還是用了 video.loop()video.volume() 方法稠项。

  • video.loop():循環(huán)播放涯雅。
  • video.volume():設(shè)置視頻音量,取值范圍是 0 ~ 1展运。



小案例

p5.js 是一個(gè)偏藝術(shù)類的 canvas 庫活逆,我們已經(jīng)掌握了 box() 基礎(chǔ)用法創(chuàng)建出立方體,接下來再理解幾個(gè)小案例應(yīng)該就有能力自己去實(shí)現(xiàn)一些特效了拗胜。非常適合在掘金整活~


案例1:Rotate Push Pop

第一個(gè)案例叫《Rotate Push Pop》划乖,是 processing 的一個(gè)例子,我把他的代碼轉(zhuǎn)成使用 p5.js 編寫挤土。

先提一嘴 processingp5.js 的關(guān)系:processing 是用 Java 編寫的琴庵,而 p5.jsprocessingJS 版。

想了解 processing 可以找 『南方者哥哥』仰美,他寫過 Processing 相關(guān)的文章迷殿。


先看看本例效果和代碼

<script>
  let a = 0
  let offset = Math.PI / 24
  let num = 12

  function setup() {
    createCanvas(440, 460, WEBGL)
    noStroke()
  }

  function draw() {
    lights()
    background(200, 200, 200)

    for(let i = 0; i < num; i++) {
      // map 映射
      let gray = map(i, 0, num - 1, 0, 255)
      push()
      fill(gray)
      rotateY(a + offset * i)
      rotateX(a / 2 + offset * i)
      box(200)
      pop()
    }

    a += 0.01
  }
</script>

這個(gè)例子用到 《p5.js 狀態(tài)管理》《p5.js map映射》 的知識(shí),工友們可以先自行理解咖杂,如果不明白的話我再在評(píng)論區(qū)留下該例子的注解庆寺。


案例2:運(yùn)動(dòng)的立方體們

<script>
  function setup() {
    createCanvas(400, 400, WEBGL)
  }

  let letter = [
    [0, 0, 0],
    [0, 0, 50],
    [25, 0, 25],
    [0, 50, 0],
    [0, 50, 50],
    [25, 25, 25],
    [50, 0, 0],
    [50, 0, 50],
    [50, 50, 0],
    [50, 50, 50]
  ]

  function draw() {
    background(200)
    rotateX(frameCount * 0.01)
    rotateY(frameCount * 0.01)
    for (let i = 0; i < letter.length; i++) {
      push()
      translate(letter[i][0], letter[i][1], letter[i][2])
      box(10)
      pop()
    }
  }
</script>

這個(gè)例子我結(jié)合了 《p5.js 變換操作》《p5.js 狀態(tài)管理》 里講到的知識(shí)。

letter 創(chuàng)建了一堆坐標(biāo)點(diǎn)诉字,他們記錄了立方體們的位置懦尝。在 draw() 里不斷的改變他們的位置。

為了讓立方體們?cè)?translate() 時(shí)不會(huì)相互影像壤圃,需要使用 push()pop() 讓它們“相互隔離開”陵霉。


案例3:排列立方體

<script>
  function setup() {
    createCanvas(400, 400, WEBGL)
  }

  function draw() {
    background(200);
    rotateX(frameCount * 0.01);
    rotateY(frameCount * 0.01);
    for (let x = -50; x <= 50; x += 10) {
      for (let y = -50; y <= 50; y += 10) {
        for (let z = -50; z <= 50; z += 10) {
          push();
          translate(x, y, z);
          box(5);
          pop();
        }
      }
    }
  }
</script>

如果你理解了“案例2”,那么“案例3”這個(gè)例子相對(duì)起來會(huì)簡單很多伍绳,它有點(diǎn)像我們剛學(xué) JS 時(shí)做的 “九九乘法表” 的練習(xí)踊挠。


案例4:還是立方體,我不知道怎么起名了

再編一個(gè)立方體案例吧冲杀,盡力了效床。睹酌。。

<script>
  function setup() {
    createCanvas(400, 400, WEBGL)
  }

  function fractalBox(size, level) {
    box(size)
    if (level > 1) {
      level--
      push()
      translate(-size/2, -size/2, -size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(-size/2, -size/2, size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(-size/2, size/2, -size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(-size/2, size/2, size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(size/2, -size/2, -size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(size/2, -size/2, size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(size/2, size/2, -size/2)
      fractalBox(size/2, level)
      pop()

      push()
      translate(size/2, size/2, size/2)
      fractalBox(size/2, level)
      pop()
    }
  }

  function draw() {
    background(200)
    rotateX(frameCount * 0.01)
    rotateY(frameCount * 0.01)
    fractalBox(200, 3)
  }
</script>

還是使用了前面例子中的方法剩檀,瞎改了一下憋沿。

我實(shí)在是沒有藝術(shù)感??



推薦閱讀

??《p5.js 光速入門》

??《p5.js 狀態(tài)管理》

??《p5.js 使用npm安裝p5.js后如何使用?》

??《p5.js map映射》


點(diǎn)贊 + 關(guān)注 + 收藏 = 學(xué)會(huì)了
代碼倉庫

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沪猴,一起剝皮案震驚了整個(gè)濱河市辐啄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌字币,老刑警劉巖则披,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異洗出,居然都是意外死亡士复,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門翩活,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阱洪,“玉大人,你說我怎么就攤上這事菠镇∪咻” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵利耍,是天一觀的道長蚌本。 經(jīng)常有香客問我,道長隘梨,這世上最難降的妖魔是什么程癌? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮轴猎,結(jié)果婚禮上嵌莉,老公的妹妹穿的比我還像新娘。我一直安慰自己捻脖,他們只是感情好锐峭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著可婶,像睡著了一般沿癞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扰肌,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天抛寝,我揣著相機(jī)與錄音,去河邊找鬼曙旭。 笑死盗舰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桂躏。 我是一名探鬼主播钻趋,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剂习!你這毒婦竟也來了蛮位?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤鳞绕,失蹤者是張志新(化名)和其女友劉穎失仁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體们何,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萄焦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冤竹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拂封。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鹦蠕,靈堂內(nèi)的尸體忽然破棺而出冒签,到底是詐尸還是另有隱情,我是刑警寧澤钟病,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布萧恕,位于F島的核電站,受9級(jí)特大地震影響肠阱,放射性物質(zhì)發(fā)生泄漏票唆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一辖所、第九天 我趴在偏房一處隱蔽的房頂上張望惰说。 院中可真熱鬧,春花似錦缘回、人聲如沸吆视。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啦吧。三九已至,卻和暖如春拙寡,著一層夾襖步出監(jiān)牢的瞬間授滓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留般堆,地道東北人在孝。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像淮摔,于是被迫代替她去往敵國和親私沮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容