【零基礎】充分理解WebGL(三)
接上篇:http://juejin.cn/post/7100864973332545550
在上一篇中,我們瞭解了基本的距離場構圖法,在這一篇,我們來進一步利用這個方法來繪製更多圖形。
距離場構圖法,最核心的思路是要定義一個形狀的距離場,通俗來説,就是定義整個畫布空間中每個像素點的距離值。
最簡單的例子就是圓,定義圓的距離場,只需要定義像素點到圓心的距離,在上一篇中我們畫圓,就是用了這個定義。
http://code.juejin.cn/pen/7100853943923638280
與圓比較類似,也很簡單的距離場是矩形:
```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); st = abs(st - center); float d = max(st.x, st.y); FragColor.rgb = smoothstep(d - 0.015, d, 0.2) * vec3(1.0); FragColor.a = 1.0; } ```
http://code.juejin.cn/pen/7103401255023673358
接下來我們來求稍微複雜一點的,任意直線的距離場。
點到直線的距離
要計算點到任意直線的距離。
最簡單的辦法是用向量來計算,我們用兩點A、B來確定一條直線AB,那麼任意一點P到直線AB的距離為:
$$d = \dfrac{|\overrightarrow{PA} \times \overrightarrow{AB}|}{|\overrightarrow{AB}|}$$
因為如圖所示:
向量$\overrightarrow{PA}$與向量$\overrightarrow{AB}$的叉積模的幾何意義是平行四邊形APP'B
的面積,除以底邊AB的長度,就是P到直線AB的距離。
所以我們可以實現直線的距離場:
glsl
float sdf_line(vec2 a, vec2 b, vec2 st) {
vec2 ap = st - a;
vec2 ab = b - a;
return ((ap.x * ab.y) - (ab.x * ap.y)) / length(ab);
}
並用以繪製直線:
glsl
void main() {
vec2 st = gl_FragCoord.xy / resolution;
float d = sdf_line(vec2(0), vec2(1.0), st);
float d2 = sdf_line(vec2(1.0, 0.0), vec2(0.0, 1.0), st);
FragColor.rgb = (stroke(d, 0.0, 0.03, 0.2) + stroke(d2, 0.0, 0.03, 0.2)) * vec3(1.0);
FragColor.a = 1.0;
}
http://code.juejin.cn/pen/7103412310877995039
點到線段的距離
前面我們計算的是點到直線的距離,稍微修改一下,考慮兩個端點,就可以計算點到線段的距離。
點到線段的距離,當點P在向量$\overrightarrow{AB}$的投影落在線段AB之間時(如下圖左邊),點P到線段AB的距離就等於P到AB所在直線的距離,而點P在向量$\overrightarrow{AB}$的投影落在線段AB之外時(如下圖右邊),那麼點P到線段AB的距離為線段AP和PB中較短的一條的長度。
所以我們得到如下的線段距離場計算函數:
```glsl
float sdf_seg(vec2 a, vec2 b, vec2 st) { vec2 ap = st - a; vec2 ab = b - a; vec2 bp = st - b; float l = length(ab); float proj = dot(ap, ab) / l; if(proj >= 0.0 && proj <= l) { return sdf_line(a, b, st); } return min(length(ap), length(bp)); } ```
注意這裏面我們用向量$\overrightarrow{AP}$和向量$\overrightarrow{AB}$的點乘除以線段AB的長度來得到點P在AB上的投影,通過投影的長度和方向來判斷點是否落在線段AB之間。
最終我們就可以用距離場繪製線段了:
http://code.juejin.cn/pen/7103420502697803790
採樣與曲線繪製
如果要繪製一條連續曲線,我們可以取相鄰的三個點A、B、C採樣,計算P點到這三個點構成的兩條線段AB和AC的距離,取距離短的作為P到曲線的距離。
```glsl float sdf_plot(vec2 a, vec2 b, vec2 c, vec2 st) { float d1 = sdf_seg(a, b, st); float d2 = sdf_seg(b, c, st);
return min(d1, d2); } ```
我們定義一個宏,來對曲線方程進行採樣:
```glsl
ifndef PLOT
define PLOT(f, st, step) sdf_plot(vec2(st.x - step, f(st.x - step)), vec2(st.x, f(st.x)), vec2(st.x + step, f(st.x + step)), st)
endif
```
有了採樣方法,我們就可以繪製各種曲線了:
```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); }
float sdf_line(vec2 a, vec2 b, vec2 st) { vec2 ap = st - a; vec2 ab = b - a; return ((ap.x * ab.y) - (ab.x * ap.y)) / length(ab); }
float sdf_seg(vec2 a, vec2 b, vec2 st) { vec2 ap = st - a; vec2 ab = b - a; vec2 bp = st - b; float l = length(ab); float proj = dot(ap, ab) / l; if(proj >= 0.0 && proj <= l) { return sdf_line(a, b, st); } return min(length(ap), length(bp)); }
float sdf_plot(vec2 a, vec2 b, vec2 c, vec2 st) { float d1 = sdf_seg(a, b, st); float d2 = sdf_seg(b, c, st);
return min(d1, d2); }
ifndef PLOT
define PLOT(f, st, step) sdf_plot(vec2(st.x - step, f(st.x - step)), vec2(st.x, f(st.x)), vec2(st.x + step, f(st.x + step)), st)
endif
float fx(in float x) { return 0.0; }
float fy(in float x) { return 9999999.99 * x; }
float f1(in float x) { return floor(x); }
float f2(in float x) { return sin(2.0 * x) / x; }
float f3(in float x) { return sqrt(1.0 - x * x); // return 0.0; }
float f4(in float x) { return -x - sin(x); }
float f5(in float x) { return log(x); }
void main() { vec2 st = gl_FragCoord.xy / resolution; st = mix(vec2(-10, -10), vec2(10, 10), st);
float stp = 0.1; float thick = 0.4; float smth = 0.2;
// PLOT func, field, step float px = PLOT(fx, st, stp); float py = PLOT(fy, st, stp);
float p1 = PLOT(f1, st, stp); float p2 = PLOT(f2, st, stp); float p3 = PLOT(f3, st, stp); float p4 = PLOT(f4, st, stp); float p5 = PLOT(f5, st, stp);
vec3 cx = stroke(px, 0.0, 0.2, 0.2) * vec3(1.0, 1.0, 1.0); vec3 cy = stroke(py, 0.0, 0.2, 0.2) * vec3(1.0, 1.0, 1.0);
vec3 c1 = stroke(p1, 0.0, thick, smth) * vec3(0, 1.0, 0); vec3 c2 = stroke(p2, 0.0, thick, smth) * vec3(0, 1.0, 1.0); vec3 c3 = stroke(p3, 0.0, thick, smth) * vec3(1.0, 1.0, 0); vec3 c4 = stroke(p4, 0.0, thick, smth) * vec3(1.0, 0, 1.0); vec3 c5 = stroke(p5, 0.0, thick, smth) * vec3(1.0, 0, 0);
FragColor.rgb = cx + cy + c1 + c2 + c3 + c4 + c5; FragColor.a = 1.0; } ```
http://code.juejin.cn/pen/7103428660606009374
注意,在上面代碼裏,我們通過st = mix(vec2(-10, -10), vec2(10, 10), st);
來擴大座標系的區間,將座標系從(0,0),(1,1)
擴大到了(-10,-10),(10,10)
,這也是一種常用的數學技巧,可以牢記。
在下一講裏面,我們將繼續深入,利用距離場構圖轉換不同座標系來構造出更加有趣的圖形。
- 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 ?