WebGL 概念和基礎入門

這是第 110 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客: WebGL 概念和基礎入門javascript

WebGL 是什麼

對於 WebGL 百度百科給出的解釋是 WebGL 是一種 3D 繪圖協議,而對此維基百科給出的解釋倒是一種 JavaScript API。因爲 WebGL 技術旨在幫助咱們在不使用插件的狀況下在任何兼容的網頁瀏覽器中開發交互式 2D 和 3D 網頁效果,咱們能夠將其理解爲一種幫助咱們開發 3D 網頁的繪圖技術,固然底層仍是 JavaScript API。css

WebGL 發展史

WebGL 的發展最先要追溯到 2006 年,WebGL 起源於 Mozilla 員工弗拉基米爾·弗基西維奇的一項 Canvas 3D 實驗項目,並於 2006 年首次展現了 Canvas 3D 的原型。這一技術在 2007 年末在 FireFox 和 Opera 瀏覽器中實現。2009 年初 Khronos Group 聯盟建立了 WebGL 的工做組最初的工做成員包括 Apple、Google、Mozilla、Opera 等。 2011 年 3 月 WebGL 1.0 規範發佈,WebGL 2 規範的發展始於 2013 年,並於 2017 年 1 月最終完成,WebGL 2 的規範,首度在 Firefox 5一、Chrome 56 和 Opera 43 中被支持。html

WebGL 中的基本概念

WebGL 運行在電腦的 GPU 中,所以須要使用能在 GPU 上運行的代碼,這樣的代碼須要提供成對的方法,每對方法中的一個叫頂點着色器而另一個叫作片元着色器,而且使用 GLSL 語言。將頂點着色器和片元着色器鏈接起來的方法叫作着色程序。前端

  • 頂點着色器

頂點着色器的做用是計算頂點的位置,即提供頂點在裁剪空間中的座標值 頂點着色器工做原理java

此塊內容參考文章webglfundamentalsgit

  • 片元着色器

片元着色器的做用是計算圖元的顏色值,咱們能夠將片元着色器大體理解成網頁中的像素web

  • 數據獲取方式 在前面咱們提到了頂點着色器和片元着色器的概念,而頂點着色器和片元着色器這兩個方法的運行都須要有對應的數據,接下來咱們一塊兒來了解一下着色器獲取數據的四種方式:
    • 屬性和緩衝
    緩衝是發送到 GPU 的一些二進制數據序列,一般狀況下緩衝數據包括位置、方向、紋理座標、頂點顏色值等。 固然你能夠根據本身的須要存儲任何你想要的數據。 屬性用於說明如何從緩衝中獲取所需數據並將它提供給頂點着色器。
    • 全局變量
    全局變量在着色程序運行前賦值,在運行過程當中全局有效。全局變量在一次繪製過程當中傳遞給着色器的值都同樣。
    • 紋理
    紋理是一個數據序列,能夠在着色程序運行中隨意讀取其中的數據。通常狀況下咱們在紋理中存儲的大都是圖像數據,但你也能夠根據本身喜歡存放除了顏色數據之外的其它數據
    • 可變量
    可變量是一種頂點着色器給片元着色器傳值的方式

小結

WebGL 只關心兩件事:裁剪空間中的座標值和顏色值。使用 WebGL 只須要給它提供這兩個東西。 所以咱們經過提供兩個着色器來作這兩件事,一個頂點着色器提供裁剪空間座標值,一個片元着色器提供顏色值。canvas

WebGL 工做原理

瞭解完 WebGL 的一些基本概念,咱們能夠一塊兒來看看 WebGL 在 GPU 上的工做都作了些什麼。正如咱們以前瞭解到的 WebGL 在 GPU 上的工做主要分爲兩個部分,即頂點着色器所作的工做(將頂點轉換爲裁剪空間座標)和片元着色器所作的工做(基於頂點着色器的計算結果繪製像素點)。假如咱們須要繪製一個三角形,此時 GPU 上進行的工做即是先調用三次頂點着色器計算出三角形的 3 個頂點在裁剪空間座標系中的對應位置,並經過變量 gl_Position 保存在 GPU 中,而後調用片元着色器完成每一個頂點顏色值的計算,並經過變量 gl_FragColor 將對應的顏色值存儲在 GPU 中。完成這些工做後咱們已經獲得了繪製三角形所需的像素點,最後即是光柵化三角形了。數組

原生 WebGL API 繪製三角形

前面咱們已經學習了 WebGL 的發展史、基本概念和工做原理等內容,接下來咱們就該實踐出真知了,因此咱們來看看如何經過 WebGL 在網頁中繪製一個簡單的三角形。咱們知道 WebGL 做爲一種 3D 繪圖技術自己就是依託於 HTML5 中的 canvas 元素而存在的,因此再正式開始繪製以前咱們須要進行一系列的準備工做:瀏覽器

  • 首先咱們須要建立一個 canvas 元素做爲繪製三角形所需的畫布,並完成瀏覽器對 canvas 元素兼容性的測試。

    function webglInit () {
      const canvasEl = document.createElement('canvas'); // canvas 元素建立
      canvasEl.width = document.body.clientWidth; // 設置 canvas 畫布的寬度
      canvasEl.height = document.body.clientHeight; // 設置 canvas 畫布的高度
      document.body.append(canvasEl); // 將建立好的 canvas 畫布添加至頁面中的 body 元素下
      // 接下來咱們須要判斷瀏覽器對於 WebGL 的兼容性,若是瀏覽器不支持 WebGL 那麼咱們就不須要再進行下去了
      if(!canvasEl.getContext("webgl") && !canvasEl.getContext("experimental-webgl ")) {
        alert("Your Browser Doesn't Support WebGL");
        return;
      }
      // 若是瀏覽器支持 WebGL,那麼咱們就獲取 WebGL 的上下文對象並複製給變量 gl
      const context = (canvasEl.getContext("webgl"))
      ? canvasEl.getContext("webgl") 
      : getContext("experimental-webgl");
      /* 設置視口 context.viewport(x, y, width, height); x: 用來設定視口的左下角水平座標。默認值:0 y: 用來設定視口的左下角垂直座標。默認值:0 width: 用來設定視口的寬度。默認值:canvas 的寬度 height: 用來設定視口的高度。默認值:canvas 的高度 當你第一次建立 WebGL 上下文的時候,視口的大小和 canvas 的大小是匹配的。然而,若是你從新改變了canvas的大小,你須要告訴 WebGL 上下文設定新的視口,所以這裏做爲初次建立這行代碼能夠省略 */
      context.viewport(0, 0, context.canvas.width, context.canvas.height);
      return context;
    }
    複製代碼
  • 準備好了 canvas 畫布下一步就能夠開始畫三角形了,正如咱們日常畫畫通常,咱們須要準備畫三角形所需的頂點即頂點着色器,以及三角形對應的填充色即片元着色器

    const gl =  webglInit();
    // 建立頂點着色器 語法 gl.createShader(type) 此處 type 爲枚舉型值爲 gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER 二者中的一個
    const vShader = gl.createShader(gl.VERTEX_SHADER) 
    // 編寫頂點着色器的 GLSL 代碼 語法 gl.shaderSource(shader, source); shader - 用於設置程序代碼的 webglShader(着色器對象) source - 包含 GLSL 程序代碼的字符串
    gl.shaderSource(vShader, ` attribute vec4 v_position; void main() { gl_Position = v_position; // 設置頂點位置 } `)
    gl.compileShader(vShader) // 編譯着色器代碼
    
    const fShader = gl.createShader(gl.FRAGMENT_SHADER) 
    gl.shaderSource(fShader, ` precision mediump float; uniform vec4 f_color; void main() { gl_FragColor = f_color; // 設置片元顏色 } `) // 編寫片元着色器代碼 
    gl.compileShader(fShader) // 編譯着色器代碼
    複製代碼
  • 前面咱們已經完成了頂點着色器和片元着色器的配置,作好了一切繪製前的準備工做接下來,接下來咱們就須要建立一個程序用來鏈接咱們的頂點着色器和片元着色器完成最終的三角形繪製工做。

// 建立一個程序用於鏈接頂點着色器和片元着色器
const program = gl.createProgram() 
gl.attachShader(program, vShader) // 添加頂點着色器
gl.attachShader(program, fShader) // 添加片元着色器
gl.linkProgram(program) // 鏈接 program 中的着色器

gl.useProgram(program) // 告訴 WebGL 用這個 program 進行渲染

const color = gl.getUniformLocation(program, 'f_color') 
// 獲取 f_color 變量位置
gl.uniform4f(color, 0.93, 0, 0.56, 1) // 設置它的值

const position = gl.getAttribLocation(program, 'v_position') 
// 獲取 v_position 位置
const pBuffer = gl.createBuffer() 
// 建立一個頂點緩衝對象,返回其 id,用來放三角形頂點數據,
gl.bindBuffer(gl.ARRAY_BUFFER, pBuffer) 
// 將這個頂點緩衝對象綁定到 gl.ARRAY_BUFFER
// 後續對 gl.ARRAY_BUFFER 的操做都會映射到這個緩存
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0, 0.5,
    0.5, 0,
    -0.5, -0.5
]),  // 三角形的三個頂點
     // 由於會將數據發送到 GPU,爲了省去數據解析,這裏使用 Float32Array 直接傳送數據
gl.STATIC_DRAW // 表示緩衝區的內容不會常常更改
)
// 將頂點數據加入的剛剛建立的緩存對象

gl.vertexAttribPointer( // 告訴 OpenGL 如何從 Buffer 中獲取數據
    position, // 頂點屬性的索引
    2, // 組成數量,必須是 1,2,3 或 4。咱們只提供了 x 和 y
    gl.FLOAT, // 每一個元素的數據類型
    false, // 是否歸一化到特定的範圍,對 FLOAT 類型數據設置無效
    0, // stride 步長 數組中一行長度,0 表示數據是緊密的沒有空隙,讓 OpenGL 決定具體步長
    0 // offset 字節偏移量,必須是類型的字節長度的倍數。
)
gl.enableVertexAttribArray(position);
// 開啓 attribute 變量額,使頂點着色器可以訪問緩衝區數據

gl.clearColor(0, 1, 1, 1) // 設置清空顏色緩衝時的顏色值
gl.clear(gl.COLOR_BUFFER_BIT) // 清空顏色緩衝區,也就是清空畫布
// 語法 gl.drawArrays(mode, first, count); mode - 指定繪製圖元的方式 first - 指定從哪一個點開始繪製 count - 指定繪製須要使用到多少個點
gl.drawArrays( gl.TRIANGLES, 0, 3 )
複製代碼

配合 HTML 文件運行上述代碼後咱們能夠在網頁中看到如圖所示的三角形,且三角形大小根據瀏覽器窗口大小自適應。 webgl triangle 能夠看到僅僅是繪製一個簡單的三角形咱們就已經寫了一大長串的 JS 代碼,若是真的用原生 WebGL API 編寫一個動態的 3D 交互式網頁,那麼開發成本可見是極其昂貴的。

WebGL 原生 API 開發的不足

上面原生 WebGL API 繪製三角形的例子,充分向咱們展現了使用原生 WebGL API 開發 3D 交互式網頁存在的問題。儘管從功能上而言原生 WebGL API 能夠知足咱們任意場景的開發須要可是,其開發和學習的成本極其昂貴。對於 WebGL 的初學者而言是極度不友好的,咱們須要配置頂點着色器用於計算繪製頂點所在的位置,而這對於開發者而言須要必定的數學基礎熟悉矩陣的運算,同時也要有空間幾何的概念熟悉 3D 物體的空間分佈。而場景的光照,紋理等的設計也都須要對顏色的配置有本身的看法。因此爲了給初學者下降難度,下面我將介紹一些 WebGL 開發的經常使用框架。

幾種 WebGL 開發的框架

  • Three.js
    • Three.js 是 WebGL 的綜合庫,其應用範圍比較普遍,美中不足的一點是,Three.js 庫沒有比較全面詳細的官方文檔,對於使用者而言不是特別友好
  • Cesium.js
    • Cesium.js 是專用於 3D 地圖開發的 WebGL 庫,其擁有較爲全面的 3D 地圖開發 API,對於須要開發 3D 地圖的開發者而言是一個不錯的選擇,但針對其餘場景的應用開發覆蓋的就不是很全面了
  • Babylon.js
    • Babylon.js 是一款國外應用較普遍的 WebGL 庫,感興趣的小夥伴能夠本身去了解一下,這裏就不作詳細介紹了

Three.js 是一款運行在瀏覽器中的 3D 引擎,你能夠用它建立各類三維場景,同時 Three.js 也是一個綜合性的 WebGL 庫。若是你須要進行 3D 地圖網頁的開發那就能夠用到 Cesium.js 了,Cesium.js 是一款專用於地圖開發的 WebGL 庫。而 Babylon.js 則是國外較火的 WebGL 庫。

基於 Three.js 繪製旋轉立方體

  • 運用 Three.js 繪製旋轉立方體的第一步同原生 WebGl 同樣,首先即是要準備 Three.js 運行所需的環境。

    // 建立 renderer 變量用於存儲渲染器對象
    var renderer;
    // initThree 函數用來初始化 Three.js 運行所需的環境
    function initThree() {
      // 同原生 WebGL 環境搭建過程同樣,Three.js 也須要先設置畫布 canvas 元素的大小
      width = document.getElementById('canvas-frame').clientWidth; // 設置寬度屬性爲瀏覽器窗口寬度
      height = document.getElementById('canvas-frame').clientHeight; // 設置高度屬性爲瀏覽器窗口高度
      // 新建一個 WebGL 渲染器並賦值給 renderer 變量
      renderer = new THREE.WebGLRenderer({
        antialias: true
      });
      // 設置畫布大小爲瀏覽器窗口大小
      renderer.setSize(width, height);
      // 將畫布元素掛載到頁面
      document.getElementById('canvas-frame').appendChild(renderer.domElement);
      // 設置清空畫布的顏色爲白色
      renderer.setClearColor(0xFFFFFF, 1.0);
    }
    複製代碼
  • 接下來不一樣於原生 WebGL 須要準備頂點着色器和片元着色器,Three.js 須要準備的是相機。Three.js 繪製 3D 網頁所需的 3 大基本要素即是 相機、場景和物體,固然若是有須要設置明暗效果咱們還須要加入第 4 要素光源,光源並不必定須要設置,可是相機、場景和物體是必定有的。

    // 建立 camera 變量用於存儲相機對象
    var camera;
    // 初始化相機函數 Three.js 中相機的類型有好幾種能夠根據具體須要進行選擇這裏咱們要建立的是一個旋轉的立方體因此採用的是透視相機,而若是須要建立 3D 陰影效果的場景則須要使用正交相機
    function initCamera() {
      /* 建立透一個視相機的實例語法 PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number ) fov - 視角 aspect - 物體的長寬比 near - 相機近點截圖 far - 相機遠點截圖 */
      camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
      camera.position.x = 0; // 設置相機在三維空間座標中 x 軸的位置
      camera.position.y = 10; // 設置相機在三維空間座標中 y 軸的位置
      camera.position.z = 5; // 設置相機在三維空間座標中 z 軸的位置
      camera.up.x = 0;
      camera.up.y = 0;
      camera.up.z = 1;
      camera.lookAt(new THREE.Vector3(0,0,0));// 設置相機的觀察點
    }
    複製代碼
  • 上一步咱們完成了相機的設置,下面咱們來準備 Three.js 繪製 3D 網頁所需的第二要素場景。

    // 建立 scene 變量用於存儲場景對象
    var scene;
    // initScene 函數建立一個場景並賦值給 scene 變量
    function initScene() {
      scene = new THREE.Scene();
    }
    複製代碼
  • 準備好了相機和場景下面咱們就須要設置拍攝的物體了,完成物體的繪製後將其添加到場景中。

    // 建立一個 cube 變量用於存放幾何立方體
    var cube;
    
    // initObject 函數就是咱們建立場景的核心了
    function initObject() {
      // 首先建立一個一個幾何類的實例並賦值給 geometry 變量
      var geometry = new THREE.BoxGeometry(1, 1, 1); 
      // 而後建立一種材質的實例 MeshBasicMaterial 材質的構造函數可以建立一種簡單的不受場景燈光效果影響的材質
      var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
      // Mesh 是一種三角形網格基本單元的構造函數,相似於咱們原生 WebGL 中的片元着色器它用於鏈接幾何體和材質
      cube = new THREE.Mesh( geometry, material );
      // 最後將建立好的幾何立方體添加到場景中
      scene.add(cube);
    }
    複製代碼
  • 到這裏咱們已經完成了 Three.js 繪製 3D 網頁所需的基本配置,固然若是有須要對 3D 網頁的明暗效果,燈光顏色作處理的咱們還能夠在場景中加入燈光的配置,這裏因爲咱們的旋轉立方體對於燈光並未有什麼特殊的要求,因此咱們便直接進入最後一步場景的渲染。

    // render 函數提供了瀏覽器的循環渲染功能
    function render() {
      cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    renderer.render(scene, camera);
    requestAnimationFrame(render);
    }
    // 最後將 Threee.js 環境初始化,場景建立,相機建立渲染器建立以及渲染初始化等函數合成到一塊兒執行咱們就完成了一個旋轉立方體的繪製
    function threeStart() {
    initThree();
    initCamera();
    initScene();
    initObject();
    render();
    }
    document.addEventListener('DOMContentLoaded',function(){
      threeStart();
    });
    複製代碼
  • Three.js 的旋轉立方體的繪製還須要配合 HTML 文件使用才能看到效果

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script type="text/javascript" src="../utils/three.js"></script>
      <style type="text/css"> div#canvas-frame { border: none; cursor: pointer; width: 100%; height: 600px; background-color: #EEEEEE; } </style>
    </head>
    <body>
      <div id="canvas-frame"></div>
    </body>
    </html>
    複製代碼

    配合 HTML 文件運行上述代碼後咱們能夠在網頁中看到,一個旋轉的綠色立方體

cube

小結

經過對比咱們發現儘管咱們經過 Three.js 建立了更爲複雜的場景,可是代碼量相對 WebGL 原生 API 繪製三角形時反而要少了。因而可知對於初學者而言,直接使用 WebGL 原生 API 進行 3D 網頁的開發,顯然是不合適的。這時候咱們就能夠藉助像 Three.js 這樣的 WebGL 封裝庫進行開發。相較之原生 API 的開發,這類第三方封裝好的 WebGL 庫大大下降了咱們的開發成本,同時也能幫助咱們開發出更加炫酷的頁面效果。固然也不是說原生 API 很差,畢竟若是有能力學透 WebGL 原生 API 的開發仍是可以幫助咱們在開發 3D 網頁的時候實現更加爲所欲爲的功能,且 Three.js 自己的文檔並非特別完善因此想要順利的使用一樣須要摸透 WebGL 原生 API。

總結

WebGL 技術出現的時間並不算短,然而儘管可以開發出擁有炫酷效果的 3D 網頁卻一直未能大火,現今應用的最多的也不過是 3D 網頁遊戲的開發。這其中很大一部分的緣由即是受到網速發展的制約,在當今這個快節奏的社會中人們對於網頁加載速度的忍耐度是極低的,一個 WebGL 開發的 3D 網頁動輒須要三四秒的打開時間對用戶而言無疑是極度不友好的。可是相信隨着 5G 通訊技術的發展,網絡通訊技術飛速發展下,WebGL 技術的明天可能會迎來新的發展契機。

參考文獻

webglfundamentals

推薦閱讀

最熟悉的陌生人rc-form

Vite 特性和部分源碼解析

我在工做中是如何使用 git 的

如何搭建適合本身團隊的構建部署平臺

開源做品

  • 政採雲前端小報

開源地址 www.zoo.team/openweekly/ (小報官網首頁有微信交流羣)

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索