Canvas

WebGL的载体

Posted by Lorry on July 3, 2018

文章字数:5194, 阅读全文大约需要:14 分钟

现如今的电脑硬件过剩,密集的计算过程都是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()

解释一下 pixelfragment 的区别:

  • 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对象的 drawingBufferWidthdrawingBufferHeight 使用默认值,然后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;
}