【零基礎】充分理解WebGL(二)
接上一篇: https://juejin.cn/post/7098256201661546532
在繼續深入之前,我們先來解決上一篇中的遺留問題:
我們預設不設定頂點的時候,繪製的圖形是整個canvas範圍,但是WebGL並不支援四邊形圖元,那麼我們原本的繪製範圍是如何界定的呢?
因為三角形是基本圖元,而Canvas畫布本身是一個四邊形,所以我們需要使用兩個三角形的頂點進行繪製,這也是gl-renderer預設的頂點資料,它相當於:
js
renderer.setMeshData([
{
positions: [[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]],
cells: [[0, 1, 3], [3, 1, 2]],
},
]);
如下圖所示,我們用兩個三角形來完成四邊形的繪製。
實際上,任意二維簡單多邊形都可以剖分成若干個三角形,然後進行繪製,這個過程在數學上叫做三角剖分。
用WebGL繪製平面圖形,三角剖分是一種基本的方法。不過這個問題我們可以留待後續的文章詳細講解。在這一講我們先回到片段著色器部分,來談談利用著色器或者說利用GPU進行造型繪圖的基本原理和方法。
與上一講一樣,我們通過動手程式碼實踐來理解,先從簡單的開始。
最簡單的單色繪製,在上一講已經說過了,比如下面的程式碼,將整個繪圖區繪製為黑色:
```js const canvas = document.querySelector('canvas'); const renderer = new GlRenderer(canvas, {webgl2: true});
const fragment = #version 300 es
precision highp float;
out vec4 FragColor;
void main() {
FragColor = vec4(0, 0, 0, 1);
}
;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.render();
```
下面我們稍微修改一下程式碼:
```js const canvas = document.querySelector('canvas'); const renderer = new GlRenderer(canvas, {webgl2: true});
const fragment = #version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
vec2 st = gl_FragCoord.xy / resolution;
FragColor = vec4(0, 0, 0, 1);
if(st.x > 0.5) {
FragColor = vec4(1, 1, 1, 1);
}
}
;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.uniforms.resolution = [canvas.width, canvas.height];
renderer.render();
```
上面的程式碼很好理解,我們判斷x座標大於0.5時,輸出顏色白色,否則為黑色。
這樣我們得到如下的效果:
上面的程式碼,我們用if(st.x > 0.5)
來判斷黑白分界線,實際上我們有更簡單的辦法:
```glsl
version 300 es
precision highp float; out vec4 FragColor; uniform vec2 resolution; void main() { vec2 st = gl_FragCoord.xy / resolution; FragColor.rgb = step(0.5, st.x) * vec3(1.0); FragColor.a = 1.0; } ```
https://code.juejin.cn/pen/7100850170283163656
這裡我們用step
函式來代替if
語句,step(x, y)
是階梯函式,當y小於x時值為0,y大於等於x時值為1。
在著色器中,step
是一個非常好用的函式,可以使用它來繪製不同的圖形。
比如下面這個例子通過step
繪製一個圓形:
```glsl
version 300 es
precision highp float; out vec4 FragColor; uniform vec2 resolution; void main() { vec2 st = gl_FragCoord.xy / resolution; vec2 center = vec2(0.5); FragColor.rgb = step(length(st - center), 0.2) * vec3(1.0); FragColor.a = 1.0; } ```
https://code.juejin.cn/pen/7100852428756484103
消鋸齒
直接用step
繪製曲線,容易產生鋸齒,我們可以通過smoothstep
來消除鋸齒:
```glsl
version 300 es
precision highp float; out vec4 FragColor; uniform vec2 resolution; void main() { vec2 st = gl_FragCoord.xy / resolution; vec2 center = vec2(0.5); float d = length(st - center); FragColor.rgb = smoothstep(d - 0.015, d, 0.2) * vec3(1.0); FragColor.a = 1.0; } ```
https://code.juejin.cn/pen/7100853943923638280
smoothstep
對階梯函式進行了平滑處理,它在範圍的上下限之間進行插值。
通過兩個step相減或者兩個smoothstep相減的技巧,可以用來畫線,例如我們修改一下上面的程式碼:
```glsl
version 300 es
precision highp float; out vec4 FragColor; uniform vec2 resolution; void main() { vec2 st = gl_FragCoord.xy / resolution; vec2 center = vec2(0.5); float d = length(st - center); FragColor.rgb = (smoothstep(d - 0.015, d, 0.2) - smoothstep(d, d + 0.015, 0.18)) * vec3(1.0); FragColor.a = 1.0; } ```
就可以繪製一個圓環:
我們可以將這個技巧封裝成一個通用函式:
glsl
float stroke(float d, float d0, float w, float smth) {
float th = 0.5 * w;
smth = smth * w;
float start = d0 - th;
float end = d0 + th;
return smoothstep(start, start + smth, d) - smoothstep(end - smth, end, d);
}
它的第一個引數接受一個距離量,第二個引數在指定距離的等距線附近繪製,第三個引數表示繪製寬度,第四個引數是平滑比率。
這樣我們就可以用stroke
來畫線了,只要我們能把距離定義出來,比如下面的程式碼繪製了一條x=0.5的直線:
glsl
void main() {
vec2 st = gl_FragCoord.xy / resolution;
float d = stroke(st.x, 0.5, 0.02, 0.1);
FragColor.rgb = d * vec3(1.0);
FragColor.a = 1.0;
}
https://code.juejin.cn/pen/7100857056361447454
這種利用距離來構圖的思路叫做距離場構圖法。下面的程式碼繪製了y=x
的直線和y=4*(x-0.5)**2
的拋物線:
https://code.juejin.cn/pen/7100862005245902884
在這一講的最後,留給大家一個作業,用距離場構圖法來繪製一條正弦曲線,要求至少繪製3個週期,你知道如何繪製嗎?如果你做出來了,可以把程式碼分享到評論區。
- Day1:用原生JS把你的裝置變成一臺架子鼓!
- 【零基礎】充分理解WebGL(七)
- 【零基礎】充分理解WebGL(六)
- 【零基礎】充分理解WebGL(五)
- 冷知識:不起眼但有用的String.raw方法
- 【零基礎】充分理解WebGL(四)
- 【零基礎】充分理解WebGL(三)
- 【零基礎】充分理解WebGL(二)
- 【零基礎】充分理解WebGL(一)
- css-doodle:如何讓CSS成為藝術?
- 建立合輯,將【碼上掘金】作為開源專案的demo庫使用
- 使用 babel 外掛來打造真正的“私有”屬性
- 使用 Node.js 對文字內容分詞和關鍵詞抽取
- 用訊號來控制非同步流程
- 設計 Timeline 時間軸來更精確地控制動畫
- 簡單構建 ThinkJS Vue2.0 前後端分離的多頁應用
- 冷門函式之Math.hypot
- 你還在用charCodeAt那你就out了
- 巧用 currentColor 屬性來實現自定義 checkbox 樣式
- 在什麼情況下 a === a - 1 ?