反正不管你信不信,我觉得WebGL是接下来一个时代的流量入口啦,谁不喜欢酷炫的东西,所以本着跟着时代步伐的精神,终于开始进行WebGL的学习啦(饶过我JS的学习还没有结束,JS学习笔记的剩余内容会在后期更新啦!)
1. WebGL历史
WebGL是基于OpenGL ES进行开发的,WebGL1.0版本基于OpenGL ES2.0,而WebGL2.0会基于OpenGL ES3.0。
那么OpenGL ES是撒?OpenGL ES是OpenGL的一个子库,主要是针对嵌入式计算机,智能手机和游戏设备等的子库。
最后OpenGL又是什么呢?OpenGL是计算机三维图形渲染的两大技术之一,另一个技术是大家都很熟悉了Directive3D(微软DirectiveX的一部分,反正经常会报错),Directive主要是针对Window系统的渲染,而OpenGL则被设计在跨平台的操作上了。
总的来说借鉴一下书上的OpenGL世代图,大致也就是这样:
OpenGL 1.0 ——> OpenGL ES 1.1
OpenGL 2.0 ——> OpenGL ES 2.0 ——> WebGL 1.0
OpenGL 3.3 ——> OpenGL ES 3.0 ——> WebGL 2.0
OpenGL 4.3
最后补充一点,在OpenGL2.0版本以后我们才可以对着色器(Shader)进行操作,操作着色器的语言是GLSL语言/GLSL ES语言(和C语言一样)
2. WebGL程序组成
先来看一个最简单的例子(在画布上画一个红色对点):
html部分:
<body>
<canvas width="400" height="400" id="myCanvas">不支持Canvas</canvas>
</body>
script部分:
<script>
main();
// WebGL主程序部分
function main() {
// ---- Canvas ----
// 获取canvas节点
let canvas = document.querySelector('#myCanvas');
// 获取canvas上下文
let gl = canvas.getContext('webgl');
// ----GLSL程序部分----
// Vertex Shader(顶点着色器)
const vertex_source = `
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 10.0;
}
`
// Fragment Shader(片元着色器)
const fragment_source = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
// ----WebGL API调用部分----
program = initProgram(gl, vertex_source, fragment_source);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArray(gl.Point, 0, 1.0);
}
// 辅助方法:初始化着色器程序
function initProgram(gl, vsource, fsource) {
let vShader = initShader(gl, gl.VERTEX_SHADER, vsource);
let fShader = initShader(gl, gl.FRAGMENT_SHADER, fsource);
// 创建WebGL程序
let program = gl.createProgram();
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
gl.linkProgram(program);
// 判断是否创建成功
if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
alert('unable to initialize!');
return;
}
gl.useProgram(program);
return program;
}
// 辅助方法:初始化着色器
function initShader(gl, type, source) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('error occured compiling the shaders:' + gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
</script>
根据这个例子,我将整个WebGL程序分成了以下对几个部分
2.1 Javascript主程序
包含两个主要部分Canvas和WebGL API,WebGL利用了HTML5的Canvas来绘制3D图形
2.1.1 Canvas
Canvas是HTML5引入,用于构建2D,3D的图形,可以在<canvas>
标签中增加文本,来判断浏览器是否支持<canvas>
,因为不支持的浏览器将显示这些文本信息,而支持的将默认显示一个空的画布。
要使用WebGL,首先要利用Canvas获取到上下文,主要按照三步进行操作
- 获取页面的Canvas元素
- 利用Canvas元素获取上下文(WebGL现在可以直接使用getContext('webgl')来获取,对于2d的我们常用getContext('2d')方法获取Canvas上下文)
- 利用WebGL API来操作绘制图形
2.2.2 WebGL API
WebGL API是WebGL编程的最主要部分,因为包含的API太多了,我也是新入门,所以目前暂时提及几个在例子中用到的主要函数:
// 清空缓冲区相关
gl.clearColor: 设置webgl颜色缓冲区清除后画布颜色
gl.clear: 清除webgl的缓冲区,可以有三个参数,分别为颜色缓冲(COLOR_BUFFER_BIT),深度缓冲区(DEPTH_BUFFER_BIT),模板缓冲区(STENCIL_BUFFER_BIT)
gl.drawArrays:利用缓冲区数据绘制图形
// 构建程序相关
gl.createProgram: 创建一个program对象
gl.attachShader: 将程序对象和shader进行绑定
gl.linkProgram: 编译程序?
gl.useProgram:设置使用的webgl program对象
// Shader构建相关
gl.createShader: 根据类型构建Shader
gl.shaderSource: 加载Shader的GLSL资源
gl.compileShader: 编译Shader返回shader对象
// 操作GLSL中变量相关,通常命名为:<方法名><变量个数><变量类型>
gl.getAttribLocation: 获取程序中的attribute变量的位置
gl.vertex[1,2,3,4]f: 设置attribute的值的函数族
gl.getUniformLocation: 获取程序中uniform变量的位置
gl.uniform[1,2,3,4]f: 设置uniform的值的函数族
2.2 GLSL
构成WebGL的第二部分是GLSL程序,WebGL构建需要两个着色器Vertex Shader(顶点着色器)和Fragment Shader(片元着色器)
注:我其实比较喜欢用英文而不喜欢用中文来表述这两个着色器,因为现在英文文档较多,知道英文名比中文名要好。
2.2.1 Vertex Shader
中文译名顶点着色器,顾名思义,主要是用来描述顶点信息(位置,尺寸等)可以看到例子中描述了gl_Position
(顶点位置)和gl_PointSize
(顶点大小,颜色?),这里的gl_Position
和gl_PointSize
是GLSL中的内置变量。
2.2.2 Fragment Shader
中文译名片元着色器,用途是用来描述绘制的图形过程和光照等信息,例子中指定了整个片段的颜色信息,gl_FragColor
也是GLSL中的内置变量。
2.2.3 GLSL中的变量
按照前面例子的写法,我们绘制的点的位置和大小,颜色,硬编码写到GLSL程序中,而没办法通过页面操作进行改变了,那么如果我们想要GLSL和web页面惊醒交互,按么我们就要使用GLSL变量。
GLSL中变量的声明包含三部分:
<存储限定符><变量类型><变量名>
其中存储限定符有三个attribute
(只用于vertex source中),uniform
(可用于vertex和fragment source中),以及varying
(用于vertex和fragment之间的数据传递);
变量类型有float
,vec[1,2,3,4]
(分别代表有多少个矢量组成的数据结构);
变量名通常根据存储限定符来进行定义attribute
就是a_
,uniform
就是u_
于是代码就可以根据以下进行调整,来设置变量:
let vertex_source = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`
let fragment_source = `
precision mediump float; // 必须要,指定精度
uniform vec4 u_FragColor;
void main() {
gl_FragColor = u_FragColor;
}
`
3. WebGL其他
3.1 坐标系转换
为了完成接下来的例子,我们还需要说明关于WebGL中的坐标系转换。
首先,WebGL使用的是笛卡尔坐标系,也就是说右手坐标系。就是x轴的正向是大拇指方向,y轴正向是四指方向,z轴方向为穿过掌背到掌心;
其次,WebGL中的坐标系的值是从[-1.0, 1.0]
的范围,原点为canvas图形中心点;
最后,对于我们使用的web页面坐标系,最左上角为原点位置,面对屏幕时,x正方向往右,y轴正方向往下
所以,我们要设置正确的WebGL点的坐标位置时,我们要将web页面的坐标信息转换为WebGL的坐标信息,转换过程如下:
let rect = canvas.getBoundingClientRect();
x = (x - rect.left - rect.width/2) / (rect.width/2);
y = (rect.height/2 - (y - rect.top)) / (rect.height/2);
3.2 根据鼠标点击绘制点的完整例子
最后附上一个完整的入门例子:
<!DOCTYPE html>
<html>
<head>
<script src="./shader-util.js"></script>
</head>
<body>
<canvas id="glCanvas" width="640" height="480"></canvas>
</body>
<script>
// 清空颜色缓冲区
main();
function main() {
const canvas = document.querySelector('#glCanvas');
const gl = canvas.getContext('webgl');
// 着色器程序
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
gl_Position = a_Position;
gl_PointSize = a_PointSize;
}
`;
const FSHADER_SOURCE = `
precision mediump float;
uniform vec4 u_FragColor;
void main() {
gl_FragColor = u_FragColor;
}
`
let program = initProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
let a_Position = gl.getAttribLocation(program, 'a_Position');
if (a_Position < 0) {
console.log('Cant find the position');
return;
}
gl.vertexAttrib3f(a_Position, 0.9, 0.0, 0.0);
let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
if (a_PointSize < 0) {
console.log('Cant find the pointsize');
return;
}
gl.vertexAttrib1f(a_PointSize, 10.0);
let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
// 绑定canvas上的点击事件
canvas.onmousedown = (event) => {
click(gl, event, canvas, a_Position, u_FragColor)
}
}
let g_points = [];
let g_colors = [];
function click(gl, event, canvas, a_Position, u_FragColor) {
let x = event.clientX;
let y = event.clientY;
let rect = canvas.getBoundingClientRect();
x = ((x - rect.left) - canvas.width/2 )/ (canvas.width/2);
y = (canvas.height/2 - (y - rect.top)) / (canvas.height/2);
g_points.push({x, y});
if (x>0 && y>0) {
g_colors.push([1.0, 0.0, 0.0, 1.0]);
} else if (x<0 && y<0){
g_colors.push([0.0, 1.0, 0.0, 1.0]);
} else {
g_colors.push([1.0, 1.0, 1.0, 1.0]);
}
gl.clear(gl.COLOR_BUFFER_BIT);
g_points.forEach((point, index) => {
gl.vertexAttrib3f(a_Position, point.x, point.y , 0.0);
gl.uniform4f(u_FragColor, g_colors[index][0], g_colors[index][1], g_colors[index][2], g_colors[index][3]);
gl.drawArrays(gl.POINTS, 0, 1);
})
}
</script>
</html>
4. 总结
因为还是入门,所以能做的东西很有限,只能绘制简单的二维图形,还完全没有发挥WebGL三维图形构建的技术,之后会持续更新学习进度的。
5. 参考
《WebGL编程指南》
MDN-WebGL-Start