webgl介绍(webgl 系列 —— 初识 WebGL)
初识 WebGL
什么是 WebGL
webgl 在支持 canvas 的浏览器中进行 2d 或 3d 渲染 。
webgl 程序除了有 Html 、javascript ,还需要加入着色器语言(GLSL ES) 。
WebGL 使得网页在支持 HTML <canvas> 标签的浏览器中 ,不需要使用任何插件 ,便可以使用基于 OpenGL ES 2.0 的 API 在 canvas 中进行 3D 渲染 —— MDN WebGL 教程
通过 caniuse 得知 webgl(98.15%) 和 webgl 2.0(94.12%) 的支持情况 。请看下图:
Tip:个人计算机上 ,绘制三维最广泛使用的技术有 Direct3D 和 OpenGL ,前者是微软的 ,后者是开源免费的 。OpenGL 有个特殊版本 OpenGL ES 专门用于嵌入式计算机 、手机 ,而 WebGL 就是从 OpenGL ES 派生出来的 。下图是 OpenGL、OpenGL ES 、WebGL 三者之间的关系 。其中 webgl 2.0 基于 OpenGL ES 3.0 未画出来:
canvas
Canvas_API 提供了一个通过JavaScript 和 HTML的 <canvas>元素来绘制图形的方式 。它可以用于动画 、游戏画面 、数据可视化 、图片编辑以及实时视频处理等方面 。
Canvas API 主要聚焦于 2D 图形 。而同样使用<canvas>元素的 WebGL API 则用于绘制硬件加速的 2D 和 3D 图形。
示例:
// canvas.html <body> <canvas id="canvas" > 抱歉 ,您的浏览器不支持 canvas 元素 (这些内容将会在不支持<canvas>元素的浏览器或是禁用了 JavaScript 的浏览器内渲染并展现) </canvas> <script> var canvas = document.getElementById(canvas); // getContext - 方法返回canvas 的上下文 ,如果上下文没有定义则返回 null var ctx = canvas.getContext(2d); // 设置填充颜色 ctx.fillStyle = green; // 绘制矩形 ctx.fillRect(10, 10, 100, 100); </script> </body>效果如下:
Tip:不管绘制二维还是三维都是这三步:
获取 canvas 请求绘图上下文 调用绘图上下文中的绘图函数第一个webgl示例
需求:清空绘图区 。也就是使用背景色清空 canvas 的绘图区
实现如下:
// webgl01.html <body> <canvas id="canvas" > 抱歉,您的浏览器不支持 canvas 元素 (这些内容将会在不支持<canvas>元素的浏览器或是禁用了 JavaScript 的浏览器内渲染并展现) </canvas> <script> var canvas = document.getElementById(canvas); const gl = canvas.getContext("webgl"); // 使用完全不透明的蓝色清除所有图像 gl.clearColor(0.0, 0.0, 1.0, 1.0); // 用上面指定的颜色清除缓冲区 gl.clear(gl.COLOR_BUFFER_BIT); </script> </body>效果如下:
仍旧是3步:
获取 canvas 请求绘图上下文 调用绘图上下文中的绘图函数在 canvas 绘制矩形之前需要指定颜色(ctx.fillStyle = green;) ,在 webgl 中类似 ,清空绘图区之前也得指定背景色,一旦指定背景色 ,背景色就会在 webgl 系统中存留 ,将来还需要使用同样的颜色清空绘图区, ,就不需要再次指定背景色 。
clearColor 和 clear语法如下:
// WebGLRenderingContext.clearColor() 方法用于设置清空颜色缓冲时的颜色值。指定调用 clear() 方法时使用的颜色值 void gl.clearColor(red, green, blue, alpha) // WebGLRenderingContext.clear() 方法使用预设值来清空缓冲 。 void gl.clear(mask); mask gl.COLOR_BUFFER_BIT // 颜色缓冲区 gl.DEPTH_BUFFER_BIT // 深度缓冲区 - 三维世界中使用 gl.STENCIL_BUFFER_BIT // 模板缓冲区 - 很少使用如果没有指定背景色 ,默认值如下:
颜色缓冲区 - (0.0, 0.0, 0.0, 0.0) 深度缓冲区 - 1.0绘制一个点
需求需求:在 canvas 中心画一个 10px 红色的点 。
效果如下:
思路用 canvas 绘制一个矩形很简单 ,先指定颜色 ,在绘制矩形。就像这样:
// canvas绘制矩形 ctx.fillStyle = green; ctx.fillRect(10, 10, 100, 100);但 webgl 需要使用着色器 ,着色器提供了灵活且强大的绘制二维或三维的方法 ,也更加复杂 。
我们先看代码 ,有一个具体的感受后 ,在分析其中细节 。
代码共3个文件 。重点关注 point01.js 即可 。
新建入口文件 point01.html: <!-- point01.html --> <script src="https://www.cnblogs.com/pengjiali/archive/2023/02/27/cuon-utils.js"></script> <script src="https://www.cnblogs.com/pengjiali/archive/2023/02/27/point01.js"></script> <body onload="main()"> <canvas id="webgl" > 抱歉 ,您的浏览器不支持 canvas 元素</canvas> </body>Tip:以上这段代码在 chrome 中运行通过,浏览器会自动补全格式 ,例如把 script 标签放入 head 中 。
新建 point01.js: // point01.js // 顶点着色器 const VSHADER_SOURCE = ` void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); gl_PointSize = 10.0; } ` // 片元着色器 const FSHADER_SOURCE = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` function main() { const canvas = document.getElementById(webgl); const gl = canvas.getContext("webgl"); // 初始化着色器 if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { console.log(Failed to intialize shaders.); return; } gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.POINTS, 0, 1); }文档加载后运行 main() 方法 ,相对第一个 webgl 示例,这里增加了初始化着色器 。
Tip:现在只需要把初始化着色器的方法(initShaders() - 请看本篇 cuon-utils.js 章节)作为一个库中的辅助方法看待 ,后续文章将介绍其中原理 。
新建 cuon-utils.js(内容见本篇扩展) ,主要提供初始化着色器的方法 代码解析 总体流程文档加载后执行 main() 方法,有如下5个阶段:
获取canvas 取得 webgl 上下文 初始化着色器 清除绘图区 调用 drawArrays 绘图下面我们主要讲一下第三步和最后一步 。
齐次坐标齐次坐标就是将一个原本是 n 维的向量用一个 n+1 维向量来表示 。齐次坐标能提高处理三维数据的有效率 ,所以在三维系统中大量使用。齐次坐标(x, y, z, w) 等价于三维坐标 (x/w, y/w, z/w)
顶点着色器顶点着色器(Vertex Shader) - 用来描述顶点特征的程序 。例如这里的位置和大小 。顶点指二维(x, y)或三维(x, y, z)空间中的一个点 ,例如端点或交点。
内置变量:
gl_Position - 用于描述顶点位置 ,必传 ,类型是 vec4(即4个float) gl_PointSize - 用户描述顶点的尺寸(像素) ,如果不传 ,默认 1.0 ,类型是 float关于位置 ,我们只有 (x, y, z) 三个变量 ,但 vec4 是 4 个,所以需要使用内置函数 vec4() 帮忙创建 vec4 类型的变量 。
代码中 vec4(0.0, 0.0, 0.0, 1.0) ,这里第四个分量是 1.0 ,使用的是齐次坐标 。
Tip:先记着 (0.0, 0.0, 0.0) 就是绘图区的中心,本篇 坐标系统 中会详细讲解。
片元着色器片元着色器(Fragment Shader) - 进行逐片元处理过程如光照的程序 。片元是 webgl 的一个术语 ,暂时可以将其理解成像素 。
内置变量:
gl_FragColor - 指定片元颜色(RGBA格式) ,类型是 vec4 初始化着色器webgl 需要两种着色器:顶点着色器(Vertex Shader) 、片元着色器(Fragment Shader) 。
在三维场景中,仅仅用线条和颜色把图画出来不够 ,还需要考虑光照上去或者观察者的视角发生变化 ,对场景有什么影响 。着色器可以灵活的完成这些工作 。
初始化着色器之前 ,顶点着色器和片元着色器都是空白 ,把着色器程序作为字符串形式传给 initShaders(initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE))之后 ,webgl 系统中的着色器就建立好 。
下图是执行 initShaders() 前后的情形:
Tip: 先执行顶点着色器 ,然后把 gl_Position 和 gl_PointSize 传给片元着色器 。实际上片元着色器接收到的是经过栅格化处理后的片元(栅格化在画三角形时在讲解) 。
绘图建立着色器之后 ,首先清空绘图区域 ,然后使用 gl.drawArrays() 进行绘制 。
gl.drawArrays(mode, first, count) 执行顶点着色器 ,按照 mode 指定的参数绘制图形。first 指定从哪个点开始绘制,count 指绘制需要几个点 。
Tip:mode 类型有:
gl.POINTS: 绘制一系列点 。 gl.LINE_STRIP: 绘制一个线条。即 ,绘制一系列线段 ,上一点连接下一点 。 gl.LINE_LOOP: 绘制一个线圈 。即,绘制一系列线段 ,上一点连接下一点 ,并且最后一点与第一个点相连。 gl.LINES: 绘制一系列单独线段 。每两个点作为端点,线段之间不连接 。 gl.TRIANGLE_STRIP:绘制一个三角带 。 gl.TRIANGLE_FAN:绘制一个三角扇 。 gl.TRIANGLES: 绘制一系列三角形 。每三个点作为顶点 。例如我们这里是:gl.drawArrays(gl.POINTS, 0, 1) ,绘制图形(点) ,需要一个点 ,从第一个点开始绘制 。后续画多个点时会对 first 和 count 有更清晰的理解 。
代码注释 // point01.js // 顶点着色器 const VSHADER_SOURCE = ` // 和 C 语言一样 ,必须包含一个 main() 函数 ,void 表示没有返回值 // 注:不能给 main() 指定参数 void main() { // 顶点着色器内置变量: gl_Position 顶点位置 、gl_PointSize 顶点尺寸 gl_Position = vec4(0.0, 0.0, 0.0, 1.0); gl_PointSize = 10.0; } ` // 片元着色器 const FSHADER_SOURCE = ` void main() { // 片元着色器内置变量: gl_FragColor 指定片元颜色 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` function main() { const canvas = document.getElementById(webgl); const gl = canvas.getContext("webgl"); // 初始化着色器 if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { console.log(初始化着色器失败); return; } // 清空绘图区 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 绘制图形(点) ,需要一个点 ,从第一个点开始绘制 gl.drawArrays(gl.POINTS, 0, 1); }扩展
坐标系统webgl 的坐标系(x, y, z)和 canvas 的坐标系(x, y)不同 。
canvas 的原点(0, 0)在左上角。webgl 处理的是三维 ,所以使用三维坐标系统(笛卡尔坐标系) ,可用(x, y, z) 表示 。也可认为是右手坐标系 。请看下图:
webgl 坐标和 canvas 坐标对应关系如下(可对照上面中间那张图):
cuon-utils.js // cuon-utils.js (c) 2012 kanda and matsuda /** * Create a program object and make current * @param gl GL context * @param vshader a vertex shader program (string) * @param fshader a fragment shader program (string) * @return true, if the program object was created and successfully made current */ function initShaders(gl, vshader, fshader) { var program = createProgram(gl, vshader, fshader); if (!program) { console.log(Failed to create program); return false; } gl.useProgram(program); gl.program = program; return true; } /** * Create the linked program object * @param gl GL context * @param vshader a vertex shader program (string) * @param fshader a fragment shader program (string) * @return created program object, or null if the creation has failed */ function createProgram(gl, vshader, fshader) { // Create shader object var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader); var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader); if (!vertexShader || !fragmentShader) { return null; } // Create a program object var program = gl.createProgram(); if (!program) { return null; } // Attach the shader objects gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); // Link the program object gl.linkProgram(program); // Check the result of linking var linked = gl.getProgramParameter(program, gl.LINK_STATUS); if (!linked) { var error = gl.getProgramInfoLog(program); console.log(Failed to link program: + error); gl.deleteProgram(program); gl.deleteShader(fragmentShader); gl.deleteShader(vertexShader); return null; } return program; } /** * Create a shader object * @param gl GL context * @param type the type of the shader object to be created * @param source shader program (string) * @return created shader object, or null if the creation has failed. */ function loadShader(gl, type, source) { // Create shader object var shader = gl.createShader(type); if (shader == null) { console.log(unable to create shader); return null; } // Set the shader program gl.shaderSource(shader, source); // Compile the shader gl.compileShader(shader); // Check the result of compilation var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (!compiled) { var error = gl.getShaderInfoLog(shader); console.log(Failed to compile shader: + error); gl.deleteShader(shader); return null; } return shader; }创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!