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 | 清空<canvas>指定的颜色。 | 78| webgl.clear(mask: GLbitfield): void | 清空<canvas>。 | 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