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

相关文章
相关标签/搜索