1. webgl是什么
getElementById首先看一个例子:
const div = document.getElementById('happy');
div.style.color = "#FFFFFF";
getElementById是浏览器提供给开发者操作DOM的api接口,通过这个方法,我们可以更改div的样式,属性甚至改变它的类型。类似其他的还有getElementsByClassName, getElementByTagName等等。这些方法统称为DOM API。
类似地,我们看另外一个方法drawArrays:
const canvas = document.getElementById('canvas');
const gl = canvas.getContext("webgl");
...
//开始绘制图形
gl.drawArrays(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
这个方法的作用是绘制一个三角形。类似的方法还有很多很多:
- gl.clearColor
- gl.getAttribLocation
- gl.getUniformLocation
这些方法的集合,我们就称之为webgl。浏览器提供这些方法让我们可以通过javascript传输数据到GPU,从而绘制——2D或者3D——图形. 所以webgl其实本质上就是用来与Gpu通信的API。
图:webgl -> 传输信息 -> Gpu绘图案 -> 显示在Canvas上。
简单来说webgl就做一件事情,将数据信息处理然后传递给GPU进行图形绘制。学习webgl就是学习如何使用这些方法,
2.基础实践
2D:
代码演示1:绘制基本的图形
绘制一个点
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);
// 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;
}
/**
* Initialize and get the rendering for WebGL
* @param canvas <cavnas> element
* @param opt_debug flag to initialize the context for debugging
* @return the rendering context for WebGL
*/
function getWebGLContext(canvas, opt_debug) {
// Get the rendering context for WebGL
var gl = WebGLUtils.setupWebGL(canvas);
if (!gl) return null;
// if opt_debug is explicitly false, create the context for debugging
if (arguments.length < 2 || opt_debug) {
gl = WebGLDebugUtils.makeDebugContext(gl);
}
return gl;
}
// HelloPoint1.js (c) 2012 matsuda
function main() {
// 获取canvas元素,后续的绘制都需要在它上面进行
var canvas = document.getElementById('canvas');
// 获取canvas中的上下文
var gl = canvas.getContext("webgl")
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// 创建初始化程序
var program = gl.createProgram();
// 创建顶点着色器
var vShader = gl.createShader(gl.VERTEX_SHADER);
// 创建片元着色器
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
// 顶点着色器
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
' gl_PointSize = 10.0;\n' + // Set the point size
'}\n';
// 片元着色器
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
'}\n';
// shader容器与着色器绑定
gl.shaderSource(vShader, VSHADER_SOURCE);
gl.shaderSource(fShader, FSHADER_SOURCE);
// 将GLSE语言编译成浏览器可用代码
gl.compileShader(vShader);
gl.compileShader(fShader);
// 将着色器添加到程序上
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
// 链接程序,在链接操作执行以后,可以任意修改shader的源代码,
//对shader重新编译不会影响整个程序,除非重新链接程序
gl.linkProgram(program);
// 加载并使用链接好的程序
gl.useProgram(program);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0 ,1);
// 初始化着色器语言
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 指定canvas的刷新颜
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用之前设置的颜色刷新canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 告诉GPU在Canvas上绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);
}
gl.drawArrays(gl.POINTS, 0, 1); 意思是告诉GPU绘制一个点,第一个参数标识的是绘制类型。这个参数的值基本上只有三种:POINTS 点,LINE线和TRIANGLES三角形。其他的图像都是由这个三种图形组成的。这个是计算机图形的基本概念。
例如我画以下的图形:
不管是基础的图形还是复杂的图形,都能用三角形拼凑而成。重点在于三角形的数量的多少。
3D:
代码演示2:绘制复合的图形
绘制一个立方体
// ColoredCube.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_MvpMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' v_Color = a_Color;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Set the vertex information
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// Set the clear color and enable the depth test
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// Get the storage location of u_MvpMatrix
var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
if (!u_MvpMatrix) {
console.log('Failed to get the storage location of u_MvpMatrix');
return;
}
// Set the eye point and the viewing volume
var mvpMatrix = new Matrix4();
mvpMatrix.setPerspective(30, 1, 1, 100);
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
// Pass the model view projection matrix to u_MvpMatrix
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
// Clear color and depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Draw the cube
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}
function initVertexBuffers(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var vertices = new Float32Array([ // Vertex coordinates
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
]);
var colors = new Float32Array([ // Colors
0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, // v0-v1-v2-v3 front(blue)
0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, // v0-v3-v4-v5 right(green)
1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, // v0-v5-v6-v1 up(red)
1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, // v1-v6-v7-v2 left
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v7-v4-v3-v2 down
0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0 // v4-v7-v6-v5 back
]);
var indices = new Uint8Array([ // Indices of the vertices
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);
// Create a buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer)
return -1;
// Write the vertex coordinates and color to the buffer object
if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position'))
return -1;
if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color'))
return -1;
// Write the indices to the buffer object
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
function initArrayBuffer(gl, data, num, type, attribute) {
var buffer = gl.createBuffer(); // Create a buffer object
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);
return true;
}
shader着色器
webgl除了告诉GPU需要画什么,还要告诉它怎么去画。所以webgl包含了将一种shaer着色器语言传递给gpu的过程。
顶点着色器:绘制所有的点
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_MvpMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' v_Color = a_Color;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
片元着色器:补充每个店之间的颜色
2. 动画
西洋镜:
所有的计算机和电影技术的基本原理和一百多年前的西洋镜的原理是一样的,因为人的眼睛的延迟效应,所以,以一定的速度切换连续的画面,会造成动画的效果,切换的速度越高,动画效果就越是流畅。这个速度在电脑上表现为屏幕的刷新率,它有一个单位来计算它:帧率。
帧(frame):帧是一个动画概念,指的是在单位时间内屏幕的刷新速率。例如60帧表示的就是一秒内显示器有60次刷新。
webgl绘制动画的工作方式:
webgl绘制动画的工作方式和GPU的这种方式类似。就是说没一段很短的世界就会将最新的数据传递给GPU,显示。例如:一个点要从A运动到B,那么这个过程就是webgl多次地将点的运动信息传递GPU以更新点的位置信息,最终形成了我们看到的动画。所以我们在webgl应用程序中经常会使用requestAnimationFrame,跟踪电脑的刷新速度更新动画,来传递信息。
3D,帧,requestAnimationFrame,矩阵,变化,电脑如何绘制3D图形,人的眼睛,webgl的显示原理
webgl运动的分解图
如果动画的帧率是60的话,那么已在位置每隔16.6毫秒左右,webgl会计算一次点的信息,然后传递给GPU,实现动画效果。这样就实现了动画的效果。原理和js动画一样。
3. webgl的代码演示
立体正方形移动并且旋转
4. webgl的应用
游戏,
丰富的交互
5. 相关的框架
由于webgl的语法非常底层,而且调试webgl极其繁琐,直接用webgl做项目是非常需要耗费时间和精力的。因此,很多框架应运而生其中几个主要的有:
three.js
three绘制一个上面的运动整体比较:
code
babylon.js
babylon绘制一个运动的正方体比较:
code
4. 更多的深入的知识
webgl性能优化
变化矩阵
电脑自动计算颜色插值
笛卡尔坐标系
颜色和纹理