1. WebGL是什么
三维绘图模拟技术,可以驱动HTML5中的Canvas渲染三维场景;
WebGL中的固定渲染管线是不存在的,所有坐标变换需要自己完成,WebGL使用两种类型的着色器完成坐标变换的工作:顶点着色器、片元着色器
2. WebGL和canvas使用上的区别
① 页面初始化一个canvas
<html> <head> <title>WebGL TEST</title> </head> <body> <canvas id="canvas"></canvas> </body> </html>
② 从canvas中获得context,获取WebGL的context,canvas是getContext('2d')
var c = document.getElementById('canvas'); c.width = 500; c.height = 300; var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
③ 画面初始化,包含了会话相关的各种处理函数、对象、常量、属性等
// 使用指定常量颜色来清空画面 gl.clear(gl.COLOR_BUFFER_BIT); // 使用颜色值(RGBA)来清空画面 gl.clearColor(0.0,0.0,0.0,1.0);
3.顶点着色器、片元着色器
顶点着色器:顶点着色器的任务是接收顶点的局部坐标,经过内部变换,输出CCV坐标;顶点着色器接受attribute变量,是逐顶点的数据,输出varying变量,也是逐定点的
内部变换包含:局部坐标(初始坐标)->世界坐标(基于世界原点的坐标)->视图坐标(基于观察者的坐标)->CCV坐标(映射在观察者的四棱台体前表面的坐标)
片元着色器:CCV坐标经过光栅化,转化为逐像素的数据,传给片元着色器,片元着色器的任务是确定每个片元的颜色;逐顶点的varying变量数据经过光栅化,成为逐片元的varying变量数据,输入片元着色器,着色器输出结果显示在canvas上
<!-- ※顶点着色器 --> <script id="vs" type="x-shader/x-vertex"> attribute vec3 position; uniform mat4 mvpMatrix; void main(void){ gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <!-- ※片段着色器 --> <script id="fs" type="x-shader/x-fragment"> void main(void){ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } </script>
4.GLSL着色器语言
WebGL无法利用固定渲染管道,所以要使用GLSL着色器语言进行编程;顶点着色器和片元着色器都使用GLSL来编写;
① 必须定义一个main函数,函数里记录要做的处理,顶点着色器必须要把顶点信息传给一个叫做gl_Position的变量。
// attibute修饰符是用来接收不同顶点传来的不同信息 // vec*表示的是向量,*部分是一个2~4的数字,vec3表示的是一个3维的向量。其元素是浮点型 // position变量定义顶点信息 attribute vec3 position; void main(void) { gl_Position = position; }
② 生成模型、视图、投影矩阵,然后合并,将最终得到的坐标传给顶点着色器
attribute vec3 position; // uniform修饰符是用来接收所有顶点一致的情报信息 // mat*表示的是方阵,可指定范围2~4,mat4表示的是4x4的方阵。其元素是浮点型 uniform mat4 mvpMatrix; void main(void) { gl_Position = mvpMatrix * position; }
③ GLSL里还有一个重要的修饰符,也就是varying修饰符,是用来连接顶点着色器和片段着色器之间的桥梁。
方法虽然有很多,但是一般的做法是,向顶点里添加颜色的情报信息,然后通过操作颜色的透明度的变化来使模型半透明或者完全透明。这时候,如果想操作顶点里的颜色信息和画面上的颜色信息的话,就需要向片段着色器里传入一些必要的信息。首先是顶点着色器部分:
attribute vec4 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor void main(void) { vColor = color; gl_Position = mvpMatrix * position; }
片段着色器接收通过varying修饰符所定义的变量vColor:
varying vec4 vColor; void main(void) { gl_FragColor = vColor; }
- 顶点的各种信息保存到数组里
- 使用WebGL的方法生成VBO
- 使用WebGL的方法将数组中的信息传给VBO
- 顶点着色器中的attribute函数和VBO结合
准备好保存顶点信息的数组之后,使用WebGL的context的方法生成VBO,当然生成的时候VBO是空的,然后将顶点信息的数组传给它。然后,比如把顶点着色器中的attribute函数和VBO关联起来。
6.用webgl在页面上画一个多边形(https://www.jianshu.com/p/45482bde6583)
① 从html中获取canvas对象
② 从canvas中获取webgl的context
③ 编译着色器
④ 准备模型数据
⑤ VBO生成合同制
⑥ 坐标变换矩阵的生成和通知
⑦ 发出绘图命令
⑧ 更新canvas并渲染
代码执行第1,2步:
<body> <canvas id="canvas"></canvas> <!-- ※顶点着色器 --> <script id="vs" type="x-shader/x-vertex"> attribute vec3 position; uniform mat4 mvpMatrix; void main(void){ gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <!-- ※片段着色器 --> <script id="fs" type="x-shader/x-fragment"> void main(void){ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } </script> <script> //首先获取canvas对象并设置其大小 var c = document.getElementById('canvas'); c.width = 500; c.height = 300; // 从canvas中获取WebGL的context var gl = c.getContext('webgl') || c.getContext('experimental-webgl'); </script> </body>
编译着色器,调用WebGL内部的函数,准备一个函数,从着色器的编译,到实际着色器的生成这一连串的流程,都在这一个函数中来完成。
function create_shader(id){ // 用来保存着色器的变量 var shader; // 根据id从HTML中获取指定的script标签 var scriptElement = document.getElementById(id); // 如果指定的script标签不存在,则返回 if(!scriptElement){return;} // 判断script标签的type属性 switch(scriptElement.type){ // 顶点着色器的时候 case 'x-shader/x-vertex': shader = gl.createShader(gl.VERTEX_SHADER); break; // 片段着色器的时候 case 'x-shader/x-fragment': shader = gl.createShader(gl.FRAGMENT_SHADER); break; default : return; } // 将标签中的代码分配给生成的着色器 gl.shaderSource(shader, scriptElement.text); // 编译着色器 gl.compileShader(shader); // 判断一下着色器是否编译成功 if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){ // 编译成功,则返回着色器 return shader; }else{ // 编译失败,弹出错误消息 alert(gl.getShaderInfoLog(shader)); } }
使用varying修饰符定义的变量,可以从顶点着色器向片段着色器中传递数据。其实,实现从一个着色器向另一个着色器传递数据的,不是别的,就是程序对象。程序对象是管理顶点着色器和片段着色器,或者WebGL程序和各个着色器之间进行数据的互相通信的重要的对象。
那么,生成程序对象,并把着色器传给程序对象,然后连接着色器,将这些处理函数化:
function create_program(vs, fs){ // 程序对象的生成 var program = gl.createProgram(); // 向程序对象里分配着色器 gl.attachShader(program, vs); gl.attachShader(program, fs); // 将着色器连接 gl.linkProgram(program); // 判断着色器的连接是否成功 if(gl.getProgramParameter(program, gl.LINK_STATUS)){ // 成功的话,将程序对象设置为有效 gl.useProgram(program); // 返回程序对象 return program; }else{ // 如果失败,弹出错误信息 alert(gl.getProgramInfoLog(program)); } }
生成VBO的时候使用WebGL的createBuffer函数,这个函数就是用来生成缓存的。但是这个函数并不是用来直接生成VBO的,它只是生成了一个缓存对象,根据它里面保存的内容不同,用途也是不用的。
要操作缓存,首先必须跟WebGL进行绑定,就是说,要向“缓存”这个“光盘”中写入数据的时候,必须连接到WebGL这个“光驱”上。
绑定了缓存之后,使用bufferData函数来向缓存中写入数据,把这些处理写成一个函数,就是下面这样:
function create_vbo(data){ // 生成缓存对象 var vbo = gl.createBuffer(); // 绑定缓存 gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // 向缓存中写入数据 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); // 将绑定的缓存设为无效 gl.bindBuffer(gl.ARRAY_BUFFER, null); // 返回生成的VBO return vbo; }
进行基本的3D渲染的时候,需要准备3个坐标变换矩阵:
模型变换矩阵、视图变换矩阵、投影变换矩阵
使用minMatrix.js等矩阵变换工具可以获得三个矩阵
6. WebGL、OpenGL、OpenGL ES、 Canvas的关系
-
OpenGL ES 没有 double 型(浮点)数据类型,而是加入了高性能的定点小数数据类型;
-
OpenGL ES 没有 glBegin/glEnd/glVertex,只能用 glDrawArrays/glDraw......;
-
没有实时将非压缩图片数据转成压缩贴图的功能,程序必须直接提供压缩好的贴图;
-
...
OpenGL是最底层的,包含GLSL语言;OpenGL ES是专门针对嵌入式设备设计的,和OpenGL有一些区别;WebGL是基于OpenGL ES2.0的一个可以用在js语言中的3D图像API;Canvas是浏览器中最终呈现WebGL做的工作的地方;ThreeJS是对WebGL进行封装后,更加简便易于使用的3D图像JS库。