现如今的电脑硬件过剩,密集的计算过程都是CPU负责,渲染过程则是通过GPU,如何最大程度的利用GPU减少CUP的负载,提升页面的流畅度和速度?是否你会偶尔想到还有一个叫做canvas的东西.
canvas
用于触发WebGL(Web Graphics Library, 执行2D或3D绘画的API, 基于OpenGL ES 2.0),可以提供一个渲染表面(rendering surface).
测试是否支持WebGL
See the Pen <p&rt;[ Here would go the result of WebGL feature detection ]</p> body { text-align : center; } button { display : block; font-size : inherit; margin : auto; padding : 0.6em; by Jiang Lirui (@JiangLiruii) on CodePen.
核心代码
var canvas = document.createElement("canvas");
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
开始作画
先画一个画板, 通过clearColor设置画板颜色, clear()根据画板颜色重置画板.
// 设置矩形画板大小, 参数为画板左上角和右下角的x,y坐标
gl.viewport(0,0, gl.drawingBufferWidth, gl.drawingBufferHeight);
// 设置画板颜色, 按照RGBA设置
gl.clearColor(0, 0.1, 0, 1.0);
// 初始化画板
gl.clear(gl.COLOR_BUFFER_BIT)
webgl的上下文有记忆功能,可以将上一次绘画的状态保存下来,下面示例是只设置一次colorMask,和每次设置clearColor的对比
// 参数类型都是布尔值,将那个色值设为true时显示,false不显示
gl.colorMask(red, green, blue, alpha)
// 获取当前遮罩
gl.getParameter(gl.COLOR_WRITEMASK)
上面演示了如何设置画板,以及初始化画板颜色等,但是可以看到, 所有的设置都是一整个画板,如何只设置一部分? scissor()
解释一下 pixel
和 fragment
的区别:
pixel
是屏幕上的的图片元素(一个点), 或者称为drawing buffer
中的单个元素fragment
是被WebGL 管道(pipeline)处理时的pixel
之所以要提这两者的区别是因为在图像(Graphic)被最终显示到屏幕上时,fragment可能会被改变很多次(比如fragment color, fragment depth),之前我们通过color mask看过了fragment color在图像处理中是如何改变的.
Scissoring
是在color mask和color clear之间的过程, 在实际的像素点被更新之前,fragments必须通过scissors测试,通过的才进入pipeline,不通过的则被忽略
// 开启剪刀测试
gl.enable(gl.SCISSOR_TEST)
// 矩形scissors的左上角和右下角
gl.scissor(x1,y1,x2,y2)
在使用scissors的时候要注意canvas的宽高,到目前为止高度heigh和宽度width都是使用canvas的默认值,没有显式的设置过.
canvas的设置有两个地方
- canvas标签的css属性(height,width) –> 设置canvas对象的clientHeight和clientWidth
- canvas对象的属性 –> 设置canvas对象的height和width
如果不设置对象的属性会导致canvas对象的 drawingBufferWidth
和 drawingBufferHeight
使用默认值,然后canvas会根据css的宽高进行缩放,比如:
canvas {
display : inline-block;
width : 120px;
height : 80px;
margin : auto;
padding : 0;
border : none;
background-color : black;
}
那么就会缩放至原来的 min(120/300, 80/150) = 0.4
所以在css设置了宽高后,一定要执行以下操作:
canvas.height = canvas.clientHeight
canvas.width = canvas.clientWidth
GLSL (OpenGL Shader Language)
<!--顶点着色器-->
<script type="x-shader/x-vertex" id="vertex-shader">
#version 100
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 64.0;
}
</script>
<!--片段着色器-->
<script type="x-shader/x-fragment" id="fragment-shader">
#version 100
void main() {
gl_FragColor = vec4(0.18, 0.54, 0.34, 1.0);
}
</script>
"use strict"
window.addEventListener("load", setupWebGL, false);
var gl,program;
function setupWebGL (evt) {
// 移除监听
window.removeEventListener(evt.type, setupWebGL, false);
// 获取render上下文(如果没有直接返回)
if (!(gl = getRenderingContext()))
return;
// 顶点源
var source = document.querySelector("#vertex-shader").innerHTML;
// 顶点着色器
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 定义顶点着色器源
gl.shaderSource(vertexShader,source);
// 编译着色器
gl.compileShader(vertexShader);
// 片段源
source = document.querySelector("#fragment-shader").innerHTML
// 片段着色器
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 定义片段着色器源
gl.shaderSource(fragmentShader,source);
// 编译着色器
gl.compileShader(fragmentShader);
// 创建着色程序
program = gl.createProgram();
// program添加着色器
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 连接program
gl.linkProgram(program);
// program解除着色器
gl.detachShader(program, vertexShader);
gl.detachShader(program, fragmentShader);
// 删除着色器
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
var linkErrLog = gl.getProgramInfoLog(program);
cleanup();
document.querySelector("p").innerHTML =
"Shader program did not link successfully. "
+ "Error log: " + linkErrLog;
return;
}
initializeAttributes();
gl.useProgram(program);
gl.drawArrays(gl.POINTS, 0, 1);
cleanup();
}
var buffer;
// 初始化
function initializeAttributes() {
// 使能顶点属性数组
gl.enableVertexAttribArray(0);
// 创建一个buffer
buffer = gl.createBuffer();
// 绑定buffer为gl.ARRAY_BUFFER
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 告诉WebGL如何解释数据:gl.vertexAttribPointer(attrib, index, gl.FLOAT, false, stride, offset);
// for attribute `attrib` there are `index` components of type `gl.FLOAT` that are `not normalized` starting at `offset` and `stride` apart in the currently bound gl.ARRAY_BUFFER.
gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 0, 0);
}
// 清理
function cleanup() {
gl.useProgram(null);
if (buffer)
gl.deleteBuffer(buffer);
if (program)
gl.deleteProgram(program);
}
// 如前文所示的获取context并设置画板颜色和大小
function getRenderingContext() {
var canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var gl = canvas.getContext("webgl")
|| canvas.getContext("experimental-webgl");
if (!gl) {
var paragraph = document.querySelector("p");
paragraph.innerHTML = "Failed to get WebGL context."
+ "Your browser or device may not support WebGL.";
return null;
}
gl.viewport(0, 0,
gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
return gl;
}