Canvas;
webGL;

书摘&心得

  • Canvas是CSS中的替换元素。
  • 书本中对WebGL只是基础介绍
  • 基于react hook和webGL基础教程开发的示例,本文所涉及所有知识点可运行源码均可在此项目中找到:

  • 设置width和height

    • A drawing of sth
    • width和height表示水平和垂直两个方向上可用的像素数目
  • getContext()
    • 取得绘图上下文对象的引用
    • 检测支持性
      • image.png
  • toDataURL()

    • 导出在元素上绘制的图像
    • const imgURL = drawing.toDataURL(“image/png”)

      2 2D上下文

      2D上下文的坐标开始于元素的左上角,原点坐标是(0,0)

      2.1 填充和描边

  • 填充:fillStyle

  • 描边:strokeStyle
  • image.png

    2.2 绘制矩形

  • 所涉及方法接收4个参数:矩形的x坐标、矩形的y坐标、矩形的宽度、矩形的高度

  • fillRect():在画布上绘制的矩形会填充指定的颜色
    • image.png
  • strokeRect()绘制空心矩形,与strokeStyle连用
  • clearRect()清除画布上的矩形区域

    2.3 绘制路径

  • 一种主要的绘图方式

  • 绘图流程
    • 首先调用beginPath()方法
    • 然后调用下列方法来实际绘制路径
      • image.png
    • 创建路径后,接下来有几种可能的选择:
      • closePath():闭合当前路径
      • fill():填充路径
        • 所有没有闭合的形状都会自动闭合
      • stroke():描边路径
        • 不会自动闭合
      • clip():在路径上创建一个剪切区域
  • isPointInPath()

    • 用于在路径被关闭之前确定画布上的某一点是否位于路径上
      • image.png

        3 WebGL

  • WebGL 是针对 Canvas 的 3D 上下文。

  • WebGL 涉及的复杂计算需要提前知道数值的精度
    • javascript的数值系统无法满足该精度
    • WebGL使用类型化数组保存数据
  • 使用时要先创建WebGL上下文

    1. var drawing = document.getElementById("drawing");
    2. //确定浏览器支持<canvas>元素
    3. if (drawing.getContext){
    4. var gl = drawing.getContext("experimental-webgl");
    5. if (gl){
    6. //使用 WebGL
    7. }
    8. }

    核心概念

    (1)常量

    如GL_COLOR_BUFFER_BIT 常量,指WebGL中的一些系统内置的量。

    (2)方法

    WebGL中内置的方法,例如,gl.uniform4f()意味着要接收 4 个浮点数,而 gl.uniform3i()则表示要接收 3 个整数。

    (3)清除画布

    在实际操作 WebGL 上下文之前,一般都要使用某种实色清除,为绘图做好准备。

    1. gl.clearColor(0,0,0,1); //black
    2. gl.clear(gl.COLOR_BUFFER_BIT);

    (4)定义视口

    1. //视口是<canvas>左下角的四分之一区域
    2. gl.viewport(0, 0, drawing.width/2, drawing.height/2);
    3. //视口是<canvas>左上角的四分之一区域
    4. gl.viewport(0, drawing.height/2, drawing.width/2, drawing.height/2);
    5. //视口是<canvas>右下角的四分之一区域
    6. gl.viewport(drawing.width/2, 0, drawing.width/2, drawing.height/2);

    (5)缓冲区

  • 顶点信息保存在 JavaScript 的类型化数组中,使用之前必须转换到 WebGL 的缓冲区。

  • 要创建缓冲区,可以调用 gl.createBuffer(),然后使用 gl.bindBuffer()绑定到 WebGL 上下文。

    (6)着色器

  • WebGL 中有两种着色器:顶点着色器和片段(或像素)着色器。

  • 顶点着色器用于将 3D 顶点转换为需要渲染的 2D 点。
  • 片段着色器用于准确计算要绘制的每个像素的颜色。
  • WebGL 着色器的独特之处也是其难点在于,着色器是使用 GLSL(OpenGL Shading Language,OpenGL 着色语言)写的

    (7)着色器语言

  • 每个着色器都有一个 main()方法,该方法在绘图期间会重复执行。

  • 着色器传递数据的方式有两种:Attribute 和 Uniform。

    • 通过 Attribute 可以向顶点着色器中传入顶点信息
    • 通过 Uniform 可以向任何着色器传入常量值。

      (8)错误

      WebGL 操作一般不会抛出错误。
      必须在调用某个可能出错的方法后,手工调用 gl.getError()方法。

      (9)绘图

  • 执行绘图操作要调用 gl.drawArrays()或 gl.drawElements()方法

    • gl.drawArrays()用于数组缓冲区
    • gl.drawElements()用于元素数组缓冲区。
  • 参数都是一个常量,表示要绘制的形状。

    • image.png

      (10)纹理

  • WebGL 的纹理可以使用 DOM 中的图像。

  • 要创建一个新纹理,可以调用 gl.createTexture(),然后再将一幅图像绑定到该纹理。
  • 必须在图片的 load 事件触发后才能设置纹理。
  • 图像、加载到

    元素中的视频,甚至其他元素都可以用作纹理。

    • 跨域资源限制同样适用于视频。

      (11)实例

  • 实现绘制一个多彩立方体

  • 结合缓冲区+着色器+着色器语言的一个示例: ```javascript // 绘制一个多彩立方体 import React, { useState, useEffect } from ‘react’;

//顶点着色器源码 var vertexShaderSource = ‘’ + //attribute声明vec4类型变量apos ‘attribute vec4 apos;’ + // attribute声明顶点颜色变量 ‘attribute vec4 a_color;’ + //varying声明顶点颜色插值后变量 ‘varying vec4 v_color;’ +

‘void main() {‘ + ‘ float radian = radians(30.0);’ + //求解旋转角度余弦值 ‘ float cos = cos(radian);’ + //求解旋转角度正弦值 ‘ float sin = sin(radian);’ + //顶点坐标apos赋值给内置变量gl_Position //逐顶点处理数据 //创建旋转矩阵x y z //cos -sin 0 0 //sin cos 0 0 //0 0 1 0 //0 0 0 1 ‘ mat4 m1 = mat4(cos,sin,0,0, -sin,cos,0,0, 0,0,1,0, 0,0,0,1);’ + ‘ mat4 m2 = mat4(1,0,0,0, 0,cos,sin,0, 0,-sin,cos,0, 0,0,0,1);’ + ‘ mat4 m3 = mat4(cos,0,-sin,0, 0,1,0,0, sin,0,cos,0, 0,0,0,1);’ + // ‘ gl_Position = apos;’ + ‘ gl_Position = m1m2m3*apos;’ + // 顶点坐标apos赋值给内置变量gl_Position // ‘ gl_Position = apos;’ + //顶点颜色插值计算 ‘ v_color = a_color;’ + ‘}’;

//片元着色器源码 var fragShaderSource = ‘’ + // 所有float类型数据的精度是lowp ‘precision lowp float;’ + // 接收顶点着色器中v_color数据 ‘varying vec4 v_color;’ + ‘void main(){‘ + // 插值后颜色数据赋值给对应的片元 ‘gl_FragColor = v_color;’ + ‘}’;

//声明初始化着色器函数 function initShader(gl: any,vertexShaderSource: string,fragmentShaderSource: string){ //创建顶点着色器对象 var vertexShader = gl.createShader(gl.VERTEX_SHADER); //创建片元着色器对象 var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

//引入顶点、片元着色器源代码 gl.shaderSource(vertexShader,vertexShaderSource); gl.shaderSource(fragmentShader,fragmentShaderSource); //编译顶点、片元着色器 gl.compileShader(vertexShader); gl.compileShader(fragmentShader);

//创建程序对象program var program = gl.createProgram(); //附着顶点着色器和片元着色器到program gl.attachShader(program,vertexShader); gl.attachShader(program,fragmentShader); //链接program gl.linkProgram(program); //使用program gl.useProgram(program); //返回程序program对象 return program; }

// 创建一个缓冲器来存储它的顶点 function initBuffers(gl: any, program: any) { / 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略 / var data=new Float32Array([ .5,.5,.5,-.5,.5,.5,-.5,-.5,.5,.5,.5,.5,-.5,-.5,.5,.5,-.5,.5, //面1 .5,.5,.5,.5,-.5,.5,.5,-.5,-.5,.5,.5,.5,.5,-.5,-.5,.5,.5,-.5, //面2 .5,.5,.5,.5,.5,-.5,-.5,.5,-.5,.5,.5,.5,-.5,.5,-.5,-.5,.5,.5, //面3 -.5,.5,.5,-.5,.5,-.5,-.5,-.5,-.5,-.5,.5,.5,-.5,-.5,-.5,-.5,-.5,.5,//面4 -.5,-.5,-.5,.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,.5,//面5 .5,-.5,-.5,-.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,.5,-.5 //面6 ]); / 创建顶点颜色数组colorData / var colorData = new Float32Array([ 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, //红色——面1 .9,0,0, .9,0,0, .9,0,0, .9,0,0, .9,0,0, .9,0,0,//R=0.9——面2 .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0,//R=0.8——面3 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, //黄色——面4 .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0,//R=0.8——面3 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, //R=1——面6 ]); / 创建缓冲区buffer,传入顶点位置数据data / var buffer = gl.createBuffer(); var aposLocation = gl.getAttribLocation(program,’apos’); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(aposLocation); / 创建缓冲区colorBuffer,传入顶点颜色数据colorData / var colorBuffer=gl.createBuffer(); var a_color = gl.getAttribLocation(program,’a_color’); gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer); gl.bufferData(gl.ARRAY_BUFFER,colorData,gl.STATIC_DRAW); gl.vertexAttribPointer(a_color,3,gl.FLOAT,false,0,0); gl.enableVertexAttribArray(a_color); }

function GLSL(props: { gl: any }) { const { gl } = props;

useEffect(() => { //初始化着色器 var program = initShader(gl, vertexShaderSource, fragShaderSource); initBuffers(gl, program); // 打开GPU深度测试,避免色块覆盖 gl.enable(gl.DEPTH_TEST); // 第2个参数代表从第几个点开始绘制 // 第3个参数代表一共绘制几个点 // gl.drawArrays(gl.LINES, 0, 2); // gl.drawArrays(gl.LINE_LOOP, 0, 3); gl.drawArrays(gl.TRIANGLES, 0, 6); gl.drawArrays(gl.TRIANGLES, 6, 6); gl.drawArrays(gl.TRIANGLES, 12, 6); gl.drawArrays(gl.TRIANGLES, 18, 6); gl.drawArrays(gl.TRIANGLES, 24, 6); gl.drawArrays(gl.TRIANGLES, 30, 6); // LINES 两两相连 // LINE_LOOP 顺序相连并收尾相连成环 // LINE_STRIP 顺序相连,不成环 }, []); return (

); }

export default GLSL;

``` 更多实例详见:https://github.com/sdhr27/webGL-in-hook