前言

p5.js 的世界非常有趣,我們還有很多 基礎的語法 能豐富我們的作品,但這就留待讀者們自行探索了。

那接下來我們要講什麼呢?我們要開始提升一個層級,介紹另一個可以和 p5.js 互相結合,卻能讓作品提升更高檔次的語言,叫做 OpenGL Shading Language(簡稱 GLSL)。

介紹 GLSL

怎麼描述 GLSL 和 p5.js 的差別呢?

我們要先從顯卡說起,知道電腦的顯卡嗎?顯卡又稱 GPU,你可以把 GPU 認為是好多好多的小 CPU,每個小 CPU 都只能進行簡單的運算,而這些小 CPU 主要的功能就是計算螢幕上的每一個像素點要呈現什麼樣的顏色。

GLSL 就是可以運行在 GPU 上每個小 CPU 的程式語言!因此 GLSL 顯而易見的優點就是:

  • 因為有非常非常多的小 CPU 一起跑,所以有些用 p5.js 跑起來會非常卡頓的程式,用 GLSL 改寫會非常快。
  • 因為精細到每個像素的層級,所以可以做出很多非常玄幻的效果。

比如說看看某些大師的作品:

https://openprocessing.org/sketch/2143598

Imgur

這個效果根本沒辦法依靠簡單的 p5.js 函數生成出來,就連人腦都很難想像這效果是怎麼計算出來的。

但有優點就有缺點:

GLSL 的學習曲線非常高,除了需要著色器相關的基本知識,還有更好的程式能力,畢竟要想像出怎麼控制每個像素點的顏色來呈現自己想要的效果。

因為 GLSL 是直接運作在每個 pixel 的計算上,因此每個 pixel 彼此之間是獨立的,完全不知道彼此狀態,所以有些效果注定很難用 GLSL 實現。

GLSL 非常難 debug,很多時候程式寫錯了,他就只是不顯示畫面,根本不會給你吐出什麼有用的 error。

像素層級繪製原理

光看文字還是霧煞煞,到底寫程式控制每個像素的計算是什麼意思,p5.js 不也是這樣的原理嗎?比如說:

function setup() {
    createCanvas(600, 600);
    background(200);
    circle(300, 300, 25);
}

你看!我也是叫每個像素呈現出一個圓給我看。

但如果想用 GLSL 來呈現類似的效果,程式需要這樣寫:

#version 300 es

precision highp float;

uniform vec2 u_resolution; uniform float u_diameter; uniform float u_edge_width;

out vec4 FragColor;

#define PI 3.14159265358979323846

void main() { vec2 uv = gl_FragCoord.xy / u_resolution; float radius = u_diameter / 2.0 / u_resolution.x; float edge = u_edge_width / u_resolution.x; vec2 circleCenter = vec2(0.5, 0.5); float dist = distance(uv, circleCenter); if (dist width) this.vel.x *= -1; if (this.pos.y height) this.vel.y *= -1; } }

以下是動畫連結:
https://openprocessing.org/sketch/2333795

  • glsl(主要由三個程式檔組成):
  • 主程式
let blurShader;
let particles = [];

function preload() { blurShader = loadShader('shader.vert', 'shader.frag'); }

function setup() { pixelDensity(1); createCanvas(600, 600, WEBGL); noStroke(); for (let i = 0; i width) this.vel.x *= -1; if (this.pos.y height) this.vel.y *= -1; } }

  • 頂點著色器(vertex shader)程式:
#version 300 es

precision highp float;

in vec3 aPosition;

void main() { vec4 positionVec4 = vec4(aPosition, 1.0); positionVec4.xy = positionVec4.xy * 2.0 - 1.0; gl_Position = positionVec4; }

  • 片段著色器(fragment shader)程式:
#version 300 es

precision highp float;

uniform vec2 u_resolution; uniform float particleSize; uniform vec2 positions[200]; const int numParticles = 200;

out vec4 FragColor;

void main() { vec2 uv = gl_FragCoord.xy / u_resolution.xy;

float blur = 0.0; for (int i = 0; i < numParticles; i++) { vec2 pos = positions[i] / u_resolution.xy; float dist = distance(uv, pos); float s = particleSize / u_resolution.x; blur += exp(-3.0 dist dist / (s * s)); } blur = clamp(blur, 0.0, 1.0); FragColor = vec4(vec3(blur) * 0.75, 1.0); }

以下是動畫連結:
https://openprocessing.org/sketch/2333805

之所以不用 gif 動圖呈現動畫效果,而是附上連結,就是為了讓讀者可以直接感受這兩種寫法在自己的電腦上跑起來效果如何,假設很不幸的,你的電腦 硬體非常好,可能這兩個程式跑起來都很順,那也可以用手機打開這兩個連結,相信可以看出兩者的運行速度有相當大的差別。

今天稍微展示了 p5.js 和 glsl 兩者寫法的差別與優缺點比較,明天會繼續講解 glsl 的程式結構和細節原理。