• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#  使用WebGL绘制图形
2<!--Kit: ArkGraphics 2D-->
3<!--Subsystem: Graphics-->
4<!--Owner: @samhu1989-->
5<!--Designer: @shi-yang-2012-->
6<!--Tester: @zhaoxiaoguang2-->
7<!--Adviser: @ge-yafang-->
8## 场景介绍
9
10WebGL的全称为Web Graphics Library(网页图形库),主要用于交互式渲染2D图形。目前OpenHarmony中使用的WebGL是基于OpenGL裁剪的OpenGL ES,可以在HTML5的Canvas元素对象中使用,无需使用插件,支持跨平台。WebGL程序是由JavaScript代码组成的,其中使用的API可以利用用户设备提供的GPU硬件完成图形渲染和加速。更多信息请参考[WebGL™标准](https://www.khronos.org/registry/webgl/specs/latest/1.0/)11
12> **说明:**
13>
14> 目前该功能仅支持使用兼容JS的类Web开发范式开发。
15
16## 基本概念
17
18### 着色器程序
19
20将缓冲区中的数据推送到着色器中还需涉及“着色器程序”,一个负责关联着色器和缓冲区的JavaScript对象。一个WebGLProgram对象由两个编译过后的WebGLShader组成,即顶点着色器和片元着色器(均由GLSL语言所写)。
21
22###  着色器
23
24着色器可以理解为运行在显卡中的指令和数据。在WebGL中,着色器是用OpenGL ES着色语言(GLSL)编写的。
25
26完整的着色器包括顶点着色器和片元着色器。顶点着色器和片元着色器的交互则涉及到图片光栅化。
27
28- 顶点着色器:最基本的任务是接收三维空间中点的坐标,将其处理为二维空间中的坐标并输出。
29
30- 片元着色器:最基本的任务是对需要处理的屏幕上的每个像素输出一个颜色值。
31
32###  图片光栅化
33
34将顶点着色器输出的二维空间中的点坐标,转化为需要处理的像素并传递给片元着色器的过程。
35
36### 帧缓冲区对象
37
38帧缓冲区对象为绘图缓冲区提供替代呈现目标。它们是颜色、深度和模板缓冲区的集合,通常用于渲染图像。
39
40###  纹理
41
42纹理是一种图像,可以应用到3D模型的表面上。WebGL中的纹理有许多属性,包括宽度、高度、格式和类型。在使用纹理时,需要将其加载到WebGL中,并将其绑定到一个纹理单元上。
43
44
45## 变量与接口说明
46
47### 变量类型
48
49| 类型         | 对应Web IDL类型         | 描述                                                         |
50| ------------ | -------------------- | ------------------------------------------------------------ |
51| GLenum     | unsigned long     | 用于枚举。        |
52| GLboolean  | boolean            | true或者false。 |
53| GLbitfield | unsigned long      | 无符号整数,可以包含多个位标志。每个位标志都代表一个特定的选项。|
54| GLbyte     | byte               | 纹理八位(一个字节),2的补码表示的有符号整数。                |
55| GLshort    | short              | 16位2的补码表示的有符号整数。                             |
56| GLint      | long               | 32位2的补码表示的有符号整数。                           |
57| GLsizei    | long               | 用来描述尺寸(例如:绘画缓冲drawing buffer 的宽和高)。      |
58| GLintptr   | long long          | 用来表示指针的特殊类型,通常用于指定缓冲区对象的偏移量。       |
59| GLsizeiptr | long long          | 用来表示指针的特殊类型,通常用于指定缓冲区对象的大小。         |
60| GLubyte    | octet              | 八位(一个字节)2的补码表示的无符号整数。                 |
61| GLushort   | unsigned short     | 16位2的补码表示的无符号整数。                          |
62| GLuint    | unsigned short     | 32位2的补码表示的有符号整数。                        |
63| GLfloat   | unrestricted float | 32位的IEEE标准的浮点数。                             |
64| GLclampf   | unrestricted float | 限值32位IEEE浮点数。                                      |
65
66### 接口说明
67
68| 接口名                                                       | 描述                                                   |
69| ------------------------------------------------------------ | ------------------------------------------------------ |
70| canvas.getContext                                            | 获取canvas对象上下文。                                 |
71| webgl.createBuffer(): WebGLBuffer \| null                    | 创建与初始化WebGL数据缓冲区。                          |
72| webgl.bindBuffer(target: GLenum, buffer: WebGLBuffer \| null): void | 将WebGL数据缓冲区与目标进行绑定。                      |
73| webgl.bufferData(target: GLenum, srcData: ArrayBufferView, usage: GLenum, srcOffset: GLuint, length?: GLuint): void | 创建并初始化WebGL的数据存储区。                        |
74| webgl.getAttribLocation(program: WebGLProgram, name: string): GLint | 从给定WebGL着色程序中获取着色器中attribute变量的地址。 |
75| webgl.vertexAttribPointer(index GLuint, size: GLint, type: GLenum, normalized: GLboolean, stride: GLsizei, offset: GLintptr): void | 将缓冲区对象分配给变量。                               |
76| webgl.enableVertexAttribArray(index: GLuint): void           | 连接变量与分配给它的缓冲区对象。                       |
77| webgl.clearColor(red: GLclampf, green:GLclampf, blue: GLclampf, alpha: GLclampf): void | 清空&lt;canvas&gt;指定的颜色。                         |
78| webgl.clear(mask: GLbitfield): void                          | 清空&lt;canvas&gt;。                                   |
79| webgl.drawArrays(mode: GLenum, first:;GLint, count: GLsizei): void | 执行数据绘制。                                         |
80| webgl.flush(): void                                          | 刷新数据至GPU,清空缓冲区。                            |
81| webgl.createProgram(): WebGLProgram \| null                  | 创建着色器程序对象。                                   |
82
83## 开发步骤
84
85 如下以实现一个彩色正方形为例,来演示使用WebGL绘制2D图形的过程。
86
871. 使用WebGL进行3D渲染前,首先需要一个Canvas元素。以下示例创建了一个Canvas元素并设置一个onclick事件处理程序来初始化WebGL上下文。
88
89   ```hml
90    <div class="container">
91        <canvas ref="canvas1" style="width : 400px; height : 400px; background-color : lightyellow;"></canvas>
92        <button class="btn-button" onclick="BtnColorTriangle">BtnColorTriangle</button>
93    </div>
94   ```
95
962. 设置WebGL的上下文。
97
98   - JavaScript 代码中的 main() 函数将会在文档加载完成之后被调用。它的任务是设置WebGL上下文并开始渲染内容。
99
100   - 当获取到canvas之后,会调用getContext函数并向它传递 "webgl" 参数,来尝试获取WebGLRenderingContext。如果浏览器不支持WebGL,getContext将会返回null,如果WebGL上下文成功初始化,变量'gl'会用来引用该上下文。
101
102   ```js
103   function main() {
104     const canvas = document.querySelector("#glcanvas");
105     // 初始化WebGL上下文
106     const gl = canvas.getContext("webgl");
107
108     // 确认WebGL支持性
109     if (!gl) {
110       alert("你的浏览器、操作系统或硬件等可能不支持WebGL。");
111       return;
112     }
113     // 使用完全不透明的黑色清除所有图像
114     gl.clearColor(0.0, 0.0, 0.0, 1.0);
115     // 用上面指定的颜色清除缓冲区
116     gl.clear(gl.COLOR_BUFFER_BIT);
117   }
118   ```
1193. 定义顶点着色器。
120
121   顶点着色器需要对顶点坐标进行必要的转换,在每个顶点基础上进行其他调整或计算,然后通过将其保存在由GLSL提供的特殊变量中来返回变换后的顶点。
122
123   在矩阵计算之前需要先引入gl-matrix开源工具库,可以从[gl-matrix官网](https://glmatrix.net/)下载,也可以使用npm命令下载:
124   `npm install gl-matrix`
125   ```js
126   // 引入mat4
127   import { mat4 } from 'gl-matrix'
128   const vsSource = `
129       attribute vec4 aVertexPosition;
130       uniform mat4 uModelViewMatrix;
131       uniform mat4 uProjectionMatrix;
132       void main() {
133         gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
134       }
135     `;
136   ```
137
1384. 定义片段着色器。
139
140   片段着色器在顶点着色器处理完图形的顶点后,会被要绘制的每个图形的每个像素点调用一次。
141
142   ```js
143   const fsSource = `
144       void main() {
145         gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
146       }
147    `;
148   ```
1495. 将着色器传递给WebGL。
150
151   定义顶点着色器与片段着色器之后,需要将它们传递给WebGL,并将其编译连接在一起。
152
153   如下代码通过调用 loadShader(),为着色器传递类型和来源。创建了两个着色器。然后创建一个附加着色器的程序,将它们连接在一起。如果编译或链接失败,代码将弹出alert。
154
155   ```js
156   // 初始化着色器程序,让WebGL知道如何绘制数据
157   function initShaderProgram(gl, vsSource, fsSource) {
158     const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
159     const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
160     // 创建着色器程序
161     const shaderProgram = gl.createProgram();
162     gl.attachShader(shaderProgram, vertexShader);
163     gl.attachShader(shaderProgram, fragmentShader);
164     gl.linkProgram(shaderProgram);
165     // 如果创建失败,将会弹出alert
166     if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
167       alert(
168         "无法初始化着色器程序: " +
169        gl.getProgramInfoLog(shaderProgram),
170       );
171       return null;
172     }
173     return shaderProgram;
174   }
175   // 创建指定类型的着色器,上传source源码并编译
176   function loadShader(gl, type, source) {
177     const shader = gl.createShader(type);
178     // 将资源发送到着色器对象
179     gl.shaderSource(shader, source);
180     // 编译着色器程序
181     gl.compileShader(shader);
182     // 查看是否编译成功
183     if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
184       alert(
185      "编译着色器时出错:" + gl.getShaderInfoLog(shader),
186       );
187       gl.deleteShader(shader);
188       return null;
189     }
190     return shader;
191   }
192   ```
1936. 查找WebGL返回分配的输入位置。
194
195   - 在创建着色器程序之后,需要查找WebGL返回分配的输入位置。上述有一个属性和两个Uniform。
196
197   - 属性从缓冲区接收值。顶点着色器的每次迭代都从分配给该属性的缓冲区接收下一个值。
198
199   - Uniform类似于JavaScript全局变量。它们在着色器的所有迭代中保持相同的值。由于属性的位置是特定于单个着色器程序的,因此将它们存储在一起以易于传递。
200
201   ```js
202   const programInfo = {
203     program: shaderProgram,
204     attribLocations: {
205       vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
206     },
207     uniformLocations: {
208       projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
209       modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
210     },
211   };
212   ```
213
2147. 创建缓冲器对象。
215
216   - 在画正方形前,需要创建一个缓冲器来存储它的顶点。
217
218   - 首先调用gl的成员函数createBuffer()得到缓冲对象并存储在顶点缓冲器。然后调用 bindBuffer() 函数绑定上下文。
219
220   - 创建一个Javascript数组去记录每一个正方体的每一个顶点。然后将其转化为WebGL浮点型类型的数组,并将其传到gl对象的bufferData()方法来建立对象的顶点。
221
222   ```js
223   function initBuffers(gl) {
224     const positionBuffer = initPositionBuffer(gl);
225     return {
226       position: positionBuffer,
227     };
228   }
229   function initPositionBuffer(gl) {
230     // 为正方形的位置创建一个缓冲区。
231     const positionBuffer = gl.createBuffer();
232     // 选择positionBuffer作为应用缓冲区的位置。
233     gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
234     // 创建一个正方形的位置数组。
235     const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
236     //将位置列表传递给WebGL以构建形状。
237     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
238     return positionBuffer;
239   }
240   export { initBuffers };
241   ```
242
2438. 渲染场景。
244
245   - 用背景色擦除画布,然后建立摄像机透视矩阵。设置45度的视图角度,并且设置一个适合实际图像的宽高比。指定在摄像机距离0.1到100单位长度的范围内的物体可见。
246
247   - 加载特定位置,并把正方形放在距离摄像机6个单位的位置。然后,绑定正方形的顶点缓冲到上下文,并配置好,再通过调用drawArrays()方法来画出对象。
248
249   ```js
250   function drawScene(gl, programInfo, buffers) {
251     gl.clearColor(0.0, 0.0, 0.0, 1.0);
252     gl.clearDepth(1.0); // 清除所有内容。
253     gl.depthFunc(gl.LEQUAL);
254     // 清除画布。
255      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
256     //创建透视矩阵用于模拟相机中的透视变形。
257     const fieldOfView = (45 * Math.PI) / 180;
258     const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
259     const zNear = 0.1;
260     const zFar = 100.0;
261     const projectionMatrix = mat4.create();
262     mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
263     // 将绘制位置设置为标识点,即场景的中心。
264     const modelViewMatrix = mat4.create();
265     // 开始绘制正方形。
266     mat4.translate(
267       modelViewMatrix, // 目标矩阵
268       modelViewMatrix, // 要转换的矩阵
269       [-0.0, 0.0, -6.0],
270     );
271     {
272       const numComponents = 2;
273       const type = gl.FLOAT;
274       const normalize = false;
275       const stride = 0; // 从一组值到下一组值需要多少字节
276       const offset = 0;
277       gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
278       gl.vertexAttribPointer(
279         programInfo.attribLocations.vertexPosition,
280         numComponents,
281         type,
282         normalize,
283         stride,
284         offset,
285       );
286       gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
287     }
288     gl.useProgram(programInfo.program);
289     gl.uniformMatrix4fv(
290       programInfo.uniformLocations.projectionMatrix,
291       false,
292       projectionMatrix,
293     );
294     gl.uniformMatrix4fv(
295       programInfo.uniformLocations.modelViewMatrix,
296       false,
297       modelViewMatrix,
298     );
299     {
300       const offset = 0;
301       const vertexCount = 4;
302       gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
303     }
304   }
305   // 告诉WebGL如何从位置中拉出位置缓冲到vertexPosition属性中。
306   function setPositionAttribute(gl, buffers, programInfo) {
307     const numComponents = 2;
308     const type = gl.FLOAT;
309     const normalize = false;
310     const stride = 0; // 从一组值到下一组值需要多少字节
311     const offset = 0;
312     gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
313     gl.vertexAttribPointer(
314       programInfo.attribLocations.vertexPosition,
315       numComponents,
316       type,
317       normalize,
318       stride,
319       offset,
320     );
321     gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
322   }
323   export { drawScene };
324   ```
325
326最终实现效果示意如下:
327
328![实现效果](figures/square.png)