3

1691649545113.jpg

一、WebGL简介

1.1 概述

WebGL(全写 Web Graphics Library)是一种 3D 绘图标准,这种绘图技术标准允许把 JavaScript 和 OpenGL ES 2.0 结合在一起,通过增加 OpenGL ES 2.0 的一个 JavaScript 绑 定,WebGL 可以为 HTML5 Canvas 提供硬件 3D 加速渲染,这样 Web 开发人员就可以借助系统 显卡来在浏览器里更流畅地展示 3D 场景和模型了,还能创建复杂的导航和数据视觉化。显然,WebGL 技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂 3D 结构 的网站页面,甚至可以用来设计 3D 网页游戏等等。

和传统的3D方案相比,WebGL具有如下一些优点:

  • WebGL 是内嵌在浏览器中的,无需安装插件和库就可以直接使用
  • 可以在多平台上运行 WebGL 程序
  • 让海量数据的三维可视化成为了可能
  • 开发环境简单,仅需文本编辑器和浏览器就可以编写三维图形程序
  • 更多

1.2 WebGL与OpenGL

OpenGL是一种用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口,是在个人计算机上使用最广泛的两种三维图形渲染技术之一,另一种是Direct3D。在某种意义上,WebGL就是“Web版的OpenGL”。

OpenGL ES则是从OpenGL中移除了许多陈旧无用的特性之后的一个轻量级的OpenGL框架,在保持轻量级的同时,OpenGL ES仍然具有足够的能力来渲染出精美的三维图形。

WebGL的技术规范继承自OpenGL ES,从2.0版本开始,OpenGL支持可编程着色器方法,这个支持可以让我们通过着色器语言编写着色器程序。下图充分的演示了WebGL、OpenGL ES和OpenGL三者之间的关系。
image.png

1.2 WebGL程序结构

相对于传统网页,支持WebGL的浏览器底层接入了OpenGL/OpenGL ES标准,WebGL通过实现标准支持着色器语言编程语言GLSL ES,在我们实际开发过程中,GLSL ES通常是以字符串的形式存在JavaScript中,我们可以通过JavaScript修改GLSL ES字符串来改变着色器程序。

二、WebGL基本知识

2.1 坐标系

就像任何其他 3D 系统一样,在 WebGL 中您将拥有 x、y 和 z 轴,其中z轴表示深度。WebGL 中的坐标仅限于 (1, 1, 1) 和 (-1, -1, - 1)。这意味着 - 如果您将投影 WebGL 图形的屏幕视为一个立方体,那么立方体的一个角将是 (1, 1, 1),而对角将是 (-1, -1, -1)。WebGL 不会显示超出这些边界绘制的任何内容。

image.png

上图描述了 WebGL 坐标系。z 轴表示深度。z 的正值表示对象靠近屏幕/查看器,而 z 的负值表示对象远离屏幕。同样,x 的正值表示对象位于屏幕右侧,负值表示对象位于屏幕左侧。类似地,y 的正值和负值表示对象是在屏幕的顶部还是底部。

3.2 WebGL 图形

获取画布对象的 WebGL 上下文后,我们就可以开始使用 JavaScript 中的 WebGL API 绘制图形元素。以下是您在开始使用 WebGL 之前需要了解的一些基本术语。

顶点

通常,绘制多边形等对象时,我们会在平面上标记点并将它们连接起来以形成所需的多边形。顶点是定义 3D 对象的边的连接点。它由三个浮点值表示,每个值分别代表 x、y、z 轴。在下面的示例中,我们正在绘制一个具有以下顶点的三角形 - (0.5, 0.5), (-0.5, 0.5), (-0.5, -0.5)。
 
image.png

数组

与 OpenGL 和 JoGL 不同,WebGL 中没有预定义的方法来直接渲染顶点。我们必须使用 JavaScript 数组手动存储它们。

var vertices = [ 0.5, 0.5, 0.1,-0.5, 0.5,-0.5] 
缓冲器

缓冲区是 WebGL 中保存数据的内存区域。有各种缓冲区,即绘图缓冲区、帧缓冲区、vetex 缓冲区和索引缓冲区。顶点缓冲区和索引缓冲区用于描述和处理模型的几何形状。

顶点缓冲区对象存储有关顶点的数据,而索引缓冲区对象存储有关索引的数据。将顶点存储到数组后,我们使用这些 Buffer 对象将它们传递给 WegGL 图形管道。

帧缓冲区是保存场景数据的图形内存的一部分。该缓冲区包含表面的宽度和高度(以像素为单位)、每个像素的颜色、深度和模板缓冲区等详细信息。
 

为了绘制 2D 或 3D 对象,WebGL API 提供了两种方法,即drawArrays()和drawElements()。这两个方法接受一个名为mode的参数,您可以使用该参数选择要绘制的对象。此字段提供的选项仅限于点、线和三角形。

要使用这两种方法绘制 3D 对象,我们必须使用点、线或三角形构造一个或多个原始多边形。此后,使用这些原始多边形,我们可以形成一个网格。使用原始多边形绘制的 3D 对象称为网格。WebGL 提供了多种绘制 3D 图形对象的方法,但用户通常更喜欢绘制网格。

例如,在下面的示例中,您可以观察到我们使用两个三角形 → {1, 2, 3} 和 {4, 1, 3} 绘制了一个正方形。

image.png
 

OpenGL ES变量

OpenGL ES SL的完整形式是 OpenGL 嵌入式系统着色语言。为了处理着色器程序中的数据,ES SL 提供了三种类型的变量:

  • Attributes:这些变量保存顶点着色器程序的输入值。属性指向包含每个顶点数据的顶点缓冲区对象。每次调用顶点着色器时,属性指向不同顶点的VBO。
  • Uniforms:这些变量保存顶点和片段着色器通用的输入数据,例如光照位置、纹理坐标和颜色。
  • Varyings: 这些变量用于将数据从顶点着色器传递到片段着色器。
     

三、着色器

3.1 两种着色器

着色器是WebGL依赖的实现图像渲染的一种绘图机制。WebGL在GPU中运行,因此需要使用能够在GPU上运行的代码,这样的代码需要提供成对的方法,分别对应顶点着色器和片元着色器。

  • 顶点着色器:用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点。
  • 片元着色器:进行逐片源处理过程如光照的程序。片元是一个WebGL术语,可以将其理解为像素(图像的单元)。

3.2 顶点着色器

顶点着色器的作用是计算顶点的位置。根据计算出的一系列顶点位置,WebGL可以对点, 线和三角形在内的一些图元进行光栅化处理。

const VERTEX_SHADER_SOURCE = `
    //所有着色器都有一个main方法
    void main() {
      gl_Position = vec4(0.0, 0.0, 0.0, 1.0);//设置坐标
      gl_PointSize = 10.0;//设置尺寸
}  

其中,gl_Position和gl_PointSize是着色器的内置变量,分别代表顶点的位置和大小,因此这段代码的作用是设置顶点的位置和大小。在着色器内,一般命名以gl_开头的变量是着色器的内置变量。

3.3 片元着色器

网格由多个三角形组成,每个三角形的表面称为片段。片段着色器的作用是计算出当前绘制图元中每个像素的颜色值,逐片元控制片元的颜色和纹理等渲染。

image.png

例如,下面是一段片段着色器的示例:

const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);//设置颜色
}

内置变量gl_FragColor来确定顶点像素颜色,vec4是一个四维向量,用来表示一个 RGBA 颜色值,它与 CSS 的颜色区别是,CSS 的 RGB 值是 0 到 255,Alpha 值是 0 到 1,但是在着色器里面,RGBA 的值都是从 0 到 1。

3.4 初始化着色器

WebGL会有大量的重复性前置工作,也就是创建着色器 -> 传入着色器代码 -> 编译着色器 -> 创建着色器程序 -> 绑定、连接、启用着色器 -> 进行绘制,整个绘制的流程如下图。

image.png

下图显示了辅助函数initShaders() 的执行效果,我们将在后面的文章中重点剖析这个函数的内部细节,现在只需要知道它在WebGL系统中初始化了着色器,供我们使用即可。

image.png

由上图可以知道,WebGL系统由两部分组成,即顶点着色器和片元着色器。在初始化着色器之前,顶点着色器和片元着色器都是空白的,我们需要将字符串形式的着色器代码从js传给WebGL系统,并建立着色器,这就是initShaders()所做的事情。注意,着色器运行在WebGL系统中,而不是js程序中。

 

同时,图的下半部分展示了initShaders()执行后的情形,着色器程序以字符串的形式传给initShaders(), 然后在WebGL系统中, 着色器就建立好了并随时可以使用。总的来说,就是先运行JavaScript程序,调用WebGL相关方法,然后顶点着色器和片元着色器就会执行,在颜色缓冲区内进行绘制,并且清空绘图区,最后颜色缓冲区的内容会自动在浏览器的canvas上显示出来。

四、示例

WebGL 应用程序代码其实就是 JavaScript 和 OpenGL 着色器语言的组合。记住以下内容:需要执行JavaScript 时才会与CPU 通信,需要执行 OpenGL 着色器语言时才会与GPU通信。

下面通过一个简单的例子来说明如何使用WebGL绘制一个简单的二维坐标三角形,代码如下:

<!doctype html>
<html>
   <body>
      <canvas width = "300" height = "300" id = "my_Canvas"></canvas>
          
      <script>
         /* Step1: Prepare the canvas and get WebGL context */
         var canvas = document.getElementById('my_Canvas');
         var gl = canvas.getContext('experimental-webgl');
         /* Step2: Define the geometry and store it in buffer objects */
         var vertices = [-0.5, 0.5, -0.5, -0.5, 0.0, -0.5,];
         // Create a new buffer object
         var vertex_buffer = gl.createBuffer();
         // Bind an empty array buffer to it
         gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
         
         // Pass the vertices data to the buffer
         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
         // Unbind the buffer
         gl.bindBuffer(gl.ARRAY_BUFFER, null);
         /* Step3: Create and compile Shader programs */
         // Vertex shader source code
         var vertCode =
            'attribute vec2 coordinates;' + 
            'void main(void) {' + ' gl_Position = vec4(coordinates,0.0, 1.0);' + '}';
         //Create a vertex shader object
         var vertShader = gl.createShader(gl.VERTEX_SHADER);
         //Attach vertex shader source code
         gl.shaderSource(vertShader, vertCode);
         //Compile the vertex shader
         gl.compileShader(vertShader);
         //Fragment shader source code
         var fragCode = 'void main(void) {' + 'gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);' + '}';
         // Create fragment shader object
         var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
         // Attach fragment shader source code
         gl.shaderSource(fragShader, fragCode);
         // Compile the fragment shader
         gl.compileShader(fragShader);
         // Create a shader program object to store combined shader program
         var shaderProgram = gl.createProgram();
         // Attach a vertex shader
         gl.attachShader(shaderProgram, vertShader); 
         
         // Attach a fragment shader
         gl.attachShader(shaderProgram, fragShader);
         // Link both programs
         gl.linkProgram(shaderProgram);
         // Use the combined shader program object
         gl.useProgram(shaderProgram);
         /* Step 4: Associate the shader programs to buffer objects */
         //Bind vertex buffer object
         gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
         //Get the attribute location
         var coord = gl.getAttribLocation(shaderProgram, "coordinates");
         //point an attribute to the currently bound VBO
         gl.vertexAttribPointer(coord, 2, gl.FLOAT, false, 0, 0);
         //Enable the attribute
         gl.enableVertexAttribArray(coord);
         /* Step5: Drawing the required object (triangle) */
         // Clear the canvas
         gl.clearColor(0.5, 0.5, 0.5, 0.9);
         // Enable the depth test
         gl.enable(gl.DEPTH_TEST); 
         
         // Clear the color buffer bit
         gl.clear(gl.COLOR_BUFFER_BIT);
         // Set the view port
         gl.viewport(0,0,canvas.width,canvas.height);
         // Draw the triangle
         gl.drawArrays(gl.TRIANGLES, 0, 3);
      </script>
   </body>
</html>
  • 第 1 步 - 准备画布并获取 WebGL 渲染上下文,我们获取当前的 HTML canvas 对象并获取其 WebGL 渲染上下文。
  • 第 2 步 - 定义几何并将其存储在缓冲区对象中,我们定义几何的属性,如顶点、索引、颜色等,并将它们存储在 JavaScript 数组中。然后,我们创建一个或多个缓冲区对象并将包含数据的数组传递给相应的缓冲区对象。在示例中,我们将三角形的顶点存储在 JavaScript 数组中,并将该数组传递给顶点缓冲区对象。
  • 第 3 步 - 创建和编译着色器程序,我们编写顶点着色器和片段着色器程序,编译它们,并通过链接这两个程序来创建组合程序。
  • 第 4 步 - 将着色器程序与缓冲区对象相关联,我们关联缓冲区对象和组合着色器程序。
  • 第 5 步 - 绘制所需对象(三角形),此步骤包括清除颜色、清除缓冲区位、启用深度测试、设置视口等操作。最后,您需要使用方法之一绘制所需的图元 - drawArrays()或drawElements()。

最终,我们得到了如下一个底色为灰色的三角形,如下图。
image.png


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》