對實現動畫的前端同窗們來講,canvas
能夠說是最自由,最能全面控制的一個動畫實現載體。不但能經過javascript
控制點、線、面的繪製,使用圖片資源填充;還能改變輸入參數做出交互動畫,徹底控制動畫過程當中的動做軌跡、速度、彈性等要素。javascript
但使用canvas
開發過較複雜一點的動畫的同窗,可能會發現,徹底使用javascript
繪製、控制的動畫,某些效果不太好實現(這篇文章只討論2D),像模糊,光照,水滴等效果。雖然用逐像素處理的方法也能夠實現,但javascript
對這類型大量數據的計算並不擅長,實現出來每一幀繪製的時間十分感人,用他實現動畫並不現實。前端
但canvas
除了最經常使用的javascript
API繪製方式(getContext('2d')
),還有WebGL的方式(getContext(webgl)
),對前面說到的大量數據計算的場景,能夠說是最適合發揮的地方。WebGL對不少同窗來講就是實現3D場景的,其實對2D繪圖來講,也有很大的發揮場景。java
咱們來看看javascript
API繪製和webGL繪製原理上的不一樣之處:web
若是使用javascript
對畫布的逐個像素進行處理,那這部分處理工做就須要在javascript
的運行環境裏進行,咱們知道javascript
的執行是單線程的,因此只能逐個逐個像素進行計算和繪製。就像一個細長的漏斗,一滴一滴水的往下漏。canvas
而WebGL的處理方式,是用GPU驅動的,對每個像素的處理,都是在GPU上執行,而GPU有許多渲染管道,這些處理能夠在這些管道中並行執行,這就是WebGL擅長這種大量數據計算場景的緣由。瀏覽器
WebGL雖然有上面說的優勢,但也有個致命的缺點:很差學,想要簡單畫根線也要費一番力氣。ide
GPU並行管道之間是不知道另外一個管道輸出的是什麼,只知道本身管道的輸入和須要執行的程序;並且不保留狀態,管道本身並不知道在此次任務以前執行過什麼程序,有什麼輸入輸出值,相似如今純函數的概念。這些觀念上的不一樣就提高了使用WebGL繪圖的門檻。函數
另外這些跑在GPU裏的程序不是javascript
,是一種類C語言,這也須要前端同窗們另外再學習。工具
那門檻再高也總有須要跨過去的一天的,下面一步一步控制WebGL去畫
一點圖案,你們也能夠體會一下,適合在何時使用這一門技術。學習
爲儘快進入GLSL着色器的階段,這裏基礎WebGL環境搭建用了Three.js
,你們能夠研究下這個基礎環境的搭建,不用第三方庫其實也用不了多少代碼量。
如下是基礎環境的搭建:
function init(canvas) { const renderer = new THREE.WebGLRenderer({canvas}); renderer.autoClearColor = false; const camera = new THREE.OrthographicCamera( -1, // left 1, // right 1, // top -1, // bottom -1, // near, 1, // far ); const scene = new THREE.Scene(); const plane = new THREE.PlaneGeometry(2, 2); const fragmentShader = '............' const uniforms = { u_resolution: { value: new THREE.Vector2(canvas.width, canvas.height) }, u_time: { value: 0 } }; const material = new THREE.ShaderMaterial({ fragmentShader, uniforms, }); scene.add(new THREE.Mesh(plane, material)); function render() { material.uniforms.u_time.value++; renderer.render(scene, camera); requestAnimationFrame(render); } render() }
解釋一下上面這段代碼作了什麼:建立了一個3D場景(說好的2D呢?),把一個矩形平面糊在攝像機前面,佔滿攝像機視覺範圍,就像看IMAX坐最前排,你能看到的就只有面前的屏幕的感受,屏幕上的畫面就是你的整個世界。咱們的繪圖就在這個屏幕上。
再說明一下,着色器分爲頂點着色器VERTEX_SHADER
和片斷着色器FRAGMENT_SHADER
。
頂點着色器對3D場景裏物體的每一個頂點計算值,如顏色、法線向量等,在這裏咱們只討論2D畫面,頂點着色器的部分就由Three.js
代勞了,實現的做用就是固定了場景中鏡頭和屏幕的位置。
而片斷着色器的做用就是計算平面上每個片斷(在這裏是屏幕上每個像素)輸出的顏色值,也是這篇文章研究的對象。
片斷着色器入參有varying
和uniform
兩種,varying
簡單說一下是由頂點着色器傳入的,每一個片斷輸入的值由相關的頂點線性插值獲得,因此每一個片斷上的值不同,本文先不討論這部分(否則寫不完了)。uniform
是統一值,由着色器外部傳入,每一個片斷獲得的值是同樣的,在這裏就是咱們從javascript
輸入變量的入口。上面的代碼咱們就爲片斷着色器傳入了u_resolution
,包含畫布的寬高值。
fragmentShader
爲着色器的程序代碼,通常的構成爲:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }
在前3行檢查了是否認義了GL_ES
,這一般在移動端或瀏覽器下會定義,第2行指定了浮點數float
的精度爲中等,也能夠指定爲低精度lowp
或高精度highp
,精度越低執行速度越快,但質量會下降。值得一提的是,一樣的設置在不一樣的執行環境下可能會表現不同,例如某些移動端的瀏覽器環境,須要指定爲高精度才能得到和PC端瀏覽器裏中等精度同樣的表現。
第5行指定了着色器能夠接收哪些入參,這裏就只有一個入參:類型爲vec2的u_resolution
。
最後3行描述了着色器的主程序,其中能夠對入參和其餘信息做處理,最後輸出顏色到gl_FragColor
,表明這個片斷顯示的顏色,其中4個數值表明RGBA
(紅、綠、藍、透明度),數值範圍爲0.0 ~ 1.0
。
爲何要寫0.0
而不是0
呢,由於GLSL
裏不像javascript
數字只有一個類型,而是分紅整形(int
)和浮點數(float
),而浮點數必須包含小數點,當小數點前是0的時候,寫成.0
也能夠。
那你們看完這段解說,應該能猜到上面的着色器會輸出什麼吧,對,就是全屏的紅色。
這就是最基礎的片斷着色器。
你們應該注意到上面的例子沒有用到傳入的uniform值,下面來講一下這些值怎麼用。
看以前搭建基礎環境的javascript
代碼能夠看到,u_resolution
存儲了畫布的寬高,這個值在着色器有什麼用呢?
這要說到片元着色器的另外一個內建的值gl_FragCoord
,這個值存儲的是片斷(像素)的座標x
,y
值,使用這兩個值就能夠知道當前着色器計算的是畫布上哪一個位置的顏色。舉個例子:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; void main() { vec2 st = gl_FragCoord.xy / u_resolution; gl_FragColor = vec4(st, 0.0, 1.0); }
能夠看到這樣的圖像:
上面的着色器代碼,使用歸一化後的x
、y
座標輸出到gl_FragColor
的紅、綠色部分。
從圖中能夠看出,gl_FragCoord
的(0, 0)
點在左下角,x軸和y軸方向分別爲向右和向上。
另外一個uniform值u_time
就是一個隨着時間不斷增長的值,利用這個值可使圖像隨時間變化,實現動畫的效果。
上面的着色器再改寫一下:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; void main() { vec2 st = gl_FragCoord.xy / u_resolution; gl_FragColor = vec4(st, sin(u_time / 100.0), 1.0); }
能夠看到下圖的效果:
http://storage.360buyimg.com/element-video/QQ20210330-195823.mp4
着色器中使用三角函數sin
,在顏色輸出的藍色通道作一個從0到1的週期變化。
掌握基本的原理後,就是開始從大師的做品中學習了。shadertoy是一個相似codepen的着色器playgroud,上面的着色器都是利用上面的基本工具,還有一些造型函數,造出各類眼花繚亂的特效、動畫。
歡迎關注凹凸實驗室博客:aotu.io
或者關注凹凸實驗室公衆號(AOTULabs),不定時推送文章。