初识 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" width="300" height="300">
抱歉,您的浏览器不支持 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:不管绘制二维还是三维都是这三步:

  1. 获取 canvas
  2. 请求绘图上下文
  3. 调用绘图上下文中的绘图函数

第一个webgl示例

需求:清空绘图区。也就是使用背景色清空 canvas 的绘图区

实现如下:

// webgl01.html
<body>
<canvas id="canvas" width="300" height="300">
抱歉,您的浏览器不支持 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步:

  1. 获取 canvas
  2. 请求绘图上下文
  3. 调用绘图上下文中的绘图函数

在 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="./cuon-utils.js"></script>
<script src="./point01.js"></script> <body onload="main()">
<canvas id="webgl" width="300" height="300"> 抱歉,您的浏览器不支持 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_Positiongl_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;
}

webgl 系列 —— 初识 WebGL的更多相关文章

  1. WebGL入门教程(一)-初识webgl

    一.WebGL和传统网页的区别: 普通网页组成成分:HTML.JavaScript: WebGL网页组成成分:HTML5.JavaScript和GLSL ES(着色器语言 OpenGL ES): 二. ...

  2. WebGL自学教程——WebGL演示样本:开始

    最终开始WebGL样品演示,...... 开始 使用WebGL步骤,非常easy: 1. 获得WebGL的渲染环境(也叫渲染上下文). 2. 发挥你的想象力,利用<WebGL參考手冊>中的 ...

  3. WebGL自学教程——WebGL演示样例:開始

    最终開始WebGL的演示样例了,...... 開始 使用WebGL的步骤,非常easy: 1. 获得WebGL的渲染环境(也叫渲染上下文). 2. 发挥你的想象力,利用<WebGL參考手冊> ...

  4. 初识WEBGL

    WEBGL (全写Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一 ...

  5. WEBGL学习【一】初识WEBGL

    <html lang="zh-CN"> <head> <title>NeHe's WebGL</title> <meta ch ...

  6. [WebGL] Setting Up WebGL

    In this lesson we cover setting up WebGL for use, including creating a canvas, getting the WebGL ren ...

  7. 【WebGL】《WebGL编程指南》读书笔记——第6章

    一.前言        最近重感冒发烧,妈蛋好难受,请假了3天,驾校也没去,简直僵硬!今天继续WebGL的学习. 二.正文        A. GLSL支持两种数据值类型: 整数型(int)与浮点型( ...

  8. 【WebGL】《WebGL编程指南》读书笔记——第5章

    一.前言        终于到了第五章了,貌似开始越来越复杂了. 二.正文         Example1:使用一个缓冲区去赋值多个顶点数据(包含坐标及点大小) function initVerte ...

  9. 【WebGL】《WebGL编程指南》读书笔记——第3章

    一.前言 根据前面一章的内容,继续第三章的学习. 二.正文       一起绘制三个点,这里要使用到缓存了 var n = initVertexBuffers(gl); //返回绘制点的个数 n ) ...

  10. 【WebGL】《WebGL编程指南》读书笔记——第2章

    一.前言 最近看了<WebGL编程指南>这本书,发现还是很有意思的,故每章阅读后做个笔记. 二.正文 Example1:在canvas中绘制矩形 <!DOCTYPE html> ...

随机推荐

  1. 【每日一题】【链表.next.next判空条件】141. 环形链表/NC4 判断链表中是否有环-211120/220123

    给你一个链表的头节点 head ,判断链表中是否有环. 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环. 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链 ...

  2. CORS与CSRF在Spring Security中的使用

    背景 在项目使用了Spring Security之后,很多接口无法访问了,从浏览器的网络调试窗看到的是CORS的报错和403的报错 分析 我们先来看一下CORS是什么,和它很相似的CSRF是什么,在S ...

  3. python中函数教程

    函数的基本概念 1.什么是函数? 函数相当于一种工具,就是把一串代码装到一起,我们下次需要用的这个功能的时候可以直接使用 函数相当于是工具(具有一定功能) 不用函数 修理工需要修理器件要用锤子 原地打 ...

  4. java调用python脚本 并传参(根据配置文件获取python文件地址)

    方式一: Java代码 package com.mybatis.plus.utils; import cn.hutool.core.lang.Console; import java.io.Buffe ...

  5. 在CentOS8中安装gitlab

    安装 docker 及 docker-compose centos8 更新源 cd /etc/yum.repos.d/ sed -i 's/mirrorlist/#mirrorlist/g' /etc ...

  6. VS2022,VS2019最新安裝方法

    直接参照:  https://www.bilibili.com/read/cv12364240/ 2022年5月30浩,亲测可用 1.下载notepad++ (必须) 2. 去微软官网下载 对应的安装 ...

  7. last-child可能你也会踩的坑

    旧文章从语雀迁移过来,原日期为2021-07-14 问题 当时写在写一个列表,列表每一项需要下面加下划线,最后一项不加下划线.第一时间,想到使用 :``last-child 这个伪类来实现. 当时的代 ...

  8. [OpenCV实战]12 使用深度学习和OpenCV进行手部关键点检测

    目录 1 背景 2 实现 3 结果和代码 4 参考 手部关键点检测是在手指上找到关节以及在给定图像中找到指尖的过程.它类似于在脸部(面部关键点检测)或身体(人体姿势估计)上找到关键点.但是手部检测不同 ...

  9. Java8时间日期处理新特性

    Java8时间日期处理新特性 简介 伴随lambda表达式.streams以及一系列小优化,Java 8 推出了全新的日期时间API.Java处理日期.日历和时间的不足之处:将 java.util.D ...

  10. node设置下载源

    // 设置镜像源 npm config set registry https://registry.npm.taobao.org // 查看当前源 npm config get registry