• WebGL第一步


    什么是WebGL?

    WebGL使用了GLSL ES(OpenGL ES)着色器语言,通过配合调用js相关的绘制接口来实现3D效果。

    采用页面中的<canvas>元素来定义绘图区域,canvas支持三维图形的绘制,但它不直接提供绘图方法,而是提供一种叫上下文(context)机制来绘制图形。

    绘制流程

    获取画布canvas和3D绘制上下文

    1 var canvas = document.getElementById('canvas');
    2 var gl = canvas.getContext('webgl');

    具体的参数可以参考这里:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext

    编写顶点着色器和片段着色器

    1 // 着色器是字符串形式的glsl代码
    2 var VSHADER_SOURCE = "...";
    3 var FSHADER_SOURCE = "...";

    编译着色器并提交到GPU

    1.创建Shader对象

    createShader方法,需要传递参数指明是顶点着色器还是片段着色器;

    2.将写好的着色器附加到Shader上

    shaderSource方法,可以指定着色器代码添加到指定的Shader上;

    3.编译着色器

    compileShader方法,可以将附加了着色器的shader对象进行编译;

    4.创建程序对象

    createProgram方法,可以创建一个程序对象,一个可用的WebGLProgram对象由两个编译过后的WebGLShader组成,即顶点着色器和片段着色器;

    5.添加着色器

    attachShader方法,可以把一个编译好的着色器对象添加到一个程序对象上;

    6.连接程序对象

    linkProgram方法,连接一个程序对象,连接之后的程序对象可以使用;

    7.使用程序对象

    useProgram方法,设定一个程序对象为当前使用的程序对象;

    8.调用绘制方法继续绘制

    根据需要调用对应的方法进行绘制;

    示例代码点击这里;

    绘制三角形

    绘制图像需要向GPU传递多个顶点,WebGL提供了一种很方便的机制,缓冲区对象(buffer object),它是WebGL系统中的一块内存区域,它可以一次性向着色器传入多个顶点的数据,然后将这些数据保存在其中,供顶点着色器使用。

    绘制图像除了要完成上面说的步骤之外,还有下面的步骤需要完成:

    1.使用缓冲区对象向GPU传入多个顶点数据

    创建缓冲区对象

    createBuffer方法,创建缓冲区对象;

    将缓冲区对象绑定到目标

    bindBuffer方法,绑定缓冲区对象,同时表明缓冲区的类型,如下:

    • gl.ARRAY_BUFFER: 包含顶点属性的Buffer,如顶点坐标,纹理坐标数据或顶点颜色数据。
    • gl.ELEMENT_ARRAY_BUFFER: 用于元素索引的Buffer。

    向缓冲区写入数据

    bufferData方法,需要表明缓冲数据的类型,同上,还要表明数据是否会变动,如下:

    • gl.STATIC_DRAW: 缓冲区的内容可能经常使用,而不会经常更改。内容被写入缓冲区,但不被读取。
    • gl.DYNAMIC_DRAW: 缓冲区的内容可能经常被使用,并且经常更改。内容被写入缓冲区,但不被读取。
    • gl.STREAM_DRAW: 缓冲区的内容可能不会经常使用。内容被写入缓冲区,但不被读取。

    获取属性下标位置

    getAttribLocation方法,获取指定属性的下标;

    将缓冲区对象分配给指定的名称变量

    vertexAttribPointer方法,会告知到GPU的属性怎么获取传入的数据,参数分别如下:

    • 第一个参数:指定待分配attribute变量的存储位置
    • 第二个参数:指定缓存区中每个顶点的分量个数(1~4)
    • 第三个参数:类型有,gl.UNSIGNED_BYTE无符号字节,gl.SHORT短整数,gl.UNSIGNED_SHORT无符号短整数,gl.INT整型,gl.UNSIGNED_INT无符号整型,gl.FLOAT浮点型。
    • 第四个参数:表示是否将非浮点型的数据归到[0,1][-1,1]区间
    • 第五个参数:相邻两个顶点的字节数。默认为0
    • 第六个参数:表示缓存区对象的偏移量(以字节为单位),就是attribute变量从缓冲区中的何处开始存储。

    开启attribute变量

    enableVertexAttribArray方法,就是开启指定的变量,使缓存区对attribute变量分配生效,以便顶点着色器能够访问缓冲区内的数据。

    2.着色器获取外部传入的数据

    使用attribute定义的属性都是可以接收外部传入的数据的属性,接收到的数据可以直接在着色器中使用;

    1 attribute vec4 a_Position;
    2 
    3 void main() {
    4 gl_Position = a_Position;
    5 }

    3.绘制图像

    drawArrays方法,用来绘制图像,参数分别如下:

    第一个参数:绘制类型

    • 点 gl.POINTS 一系列点,绘制v0,v1……。
    • 线段 gl.LINES 一系列单独的线段,绘制(v0,v1),(v2,v3)……如果是奇数,最后一个省略。
    • 线条 gl.LINE_STRIP 一系列连接的线段,绘制(v0,v1),(v1,v2),(v2,v3)……除了第一个和最后一个,其他的点点即是起点又是终点。
    • 回路 gl.LINE_LOOP 一系列连接的线段,绘制(v0,v1),(v1,v2),(v2,v3)……(vn,v0),最后一个点会连接起点。

    三角形 gl.TRIANGLES 一系列单独的三角形,绘制(v0,v1,v2),(v3,v4,v5)……如果不是3的倍数,剩下的将会被忽略。

    三角带 gl.TRIANGLES_STRIP 一系列连接的三角形,绘制(v0,v1,v2),(v2,v1,v3),(v2,v3,v4)……以此类推,第二个是(v2,v1,v3)而不是(v1,v2,v3)是为了保持绘制按照逆时针绘制

    第二个参数:开始绘制的顶点索引

    第三个参数:绘制的顶点数量

    示例代码点击这里;

    矩阵转换

    我们希望修改传输到GPU中的顶点的位置时(比如放大缩小旋转等),需要从外部传入一个矩阵来进行运算,外部传入的矩阵是一个包含16个数字的带数据类型的数组对象;

    关于矩阵的相关教程可以看这里;

    1.先在代码中定义好需要参与运算的矩阵:

    1 // 控制缩放的矩阵
    2 var Sx = 1.5; Sy = 1.5; Sz = 1.0;
    3 var xformMatrix = new Float32Array([
    4 Sx, 0.0, 0.0, 0.0,
    5 0.0, Sy, 0.0, 0.0,
    6 0.0, 0.0, Sz, 0.0,
    7 0.0, 0.0, 0.0, 1.0
    8 ]);

    2.将矩阵数据传入GPU:

    uniformMatrix4fv方法,传输到GPU,参数如下:

    • 第一个参数:代表uniform变量的存储位置;
    • 第二个参数:在WebGL中指定为false;
    • 第三个参数:待传输的类型化数组;

    代码如下:

    1 // 获取矩阵变量的下标
    2 var u_xformMatarix = gl.getUniformLocation(shaderProgram, 'u_xformMatarix');
    3 // 提交矩阵数据到GPU
    4 gl.uniformMatrix4fv(u_xformMatarix, false, xformMatrix);

    3.在着色器中获取矩阵并进行运算

    1 attribute vec4 a_Position;
    2 uniform mat4 u_xformMatarix;
    3 
    4 void main() {
    5 // 设置坐标
    6 gl_Position = u_xformMatarix * a_Position;
    7 }

    示例代码点击这里;

    设定颜色

    我们可以通过顶点数据向着色器添加颜色信息。

    1.为顶点数据添加颜色的信息

    1 //顶点坐标和颜色
    2 var vertices = new Float32Array([
    3 0.0, 0.5, 1.0, 0.0, 0.0,
    4 -0.5, -0.5, 0.0, 1.0, 0.0,
    5 0.5, -0.5, 0.0, 0.0, 1.0,
    6 ]);

    2.编写着色器

    颜色是在片段着色器中处理的,但是顶点数据只在顶点着色器中可以使用,所以我们需要将顶点着色器中的部分数据传递到片段着色器中,使用varying定义的属性可以将顶点着色器中的数据传递到片段着色器中;

     1 // 顶点着色器
     2 attribute vec4 a_Position;
     3 attribute vec4 a_Color;
     4 // 声明需要传递到片段着色器的属性
     5 varying vec4 v_Color;
     6 
     7 void main() {
     8 // 设置坐标
     9 gl_Position = a_Position;
    10 // 传递颜色数据
    11 v_Color = a_Color;
    12 }
    13 
    14 
    15 // 片段着色器
    16 // 必须在片段着色器中为float指定一个默认精度
    17 precision mediump float;
    18 // 声明从顶点着色器接收数据的属性
    19 varying vec4 v_Color;
    20 
    21 void main() {
    22 gl_FragColor = v_Color;
    23 }

    3.设置传递的数据规则

     1 //获取单个字节
     2 var FSIZE = vertices.BYTES_PER_ELEMENT;
     3 
     4 //获取坐标点
     5 var a_Position = gl.getAttribLocation(shaderProgram, 'a_Position');
     6 
     7 //将缓冲区对象分配给a_Position变量
     8 gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0);
     9 
    10 //连接a_Position变量与分配给它的缓冲区对象
    11 gl.enableVertexAttribArray(a_Position);
    12 
    13 //获取Color坐标点
    14 var a_Color = gl.getAttribLocation(shaderProgram, 'a_Color');
    15 
    16 //将缓冲区对象分配给a_Position变量
    17 gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
    18 
    19 //连接a_Position变量与分配给它的缓冲区对象
    20 gl.enableVertexAttribArray(a_Color);

    示例代码点击这里;

    绘制图片

    这里我们看看怎么使用webgl来绘制图片;

    1.加载图片

    通过创建Image对象,可以动态加载指定的图片,保存这个Image对象,后期直接使用这个对象提交到GPU即可;

    2.提交到GPU

     1 //对纹理图像进行Y轴反转,因为WebGL纹理坐标系统的t轴(分为t轴和s轴)的方向和图片的坐标系统Y轴方向相反。因此将Y轴进行反转。
     2 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
     3 
     4 //开启0号纹理单元,WebGL通过纹理单元的机制来同时使用多个纹理,gl.TEXTURE0~gl.TEXTURE7是管理纹理图像的8个纹理单元
     5 gl.activeTexture(gl.TEXTURE0);
     6 
     7 //向target绑定纹理对象
     8 gl.bindTexture(gl.TEXTURE_2D, texture);
     9 
    10 //配置纹理参数
    11 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    12 
    13 //配置纹理图像
    14 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
    15 
    16 //获取u_Sampler的存储位置
    17 var u_Sampler = gl.getUniformLocation(shaderProgram, 'u_Sampler');
    18 
    19 //将0号纹理图像传递给着色器
    20 gl.uniform1i(u_Sampler, 0);
    21 
    22 // 清空 <canvas>
    23 gl.clear(gl.COLOR_BUFFER_BIT);
    24 
    25 // 绘制矩形
    26 gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);

    3.UV坐标

    Webgl中,是使用UV坐标来标记图像的采样点,UV的详情点击这里;

    4.着色器修改

     1 // 顶点着色器
     2 attribute vec4 a_Position;
     3 attribute vec2 a_TextCoord;
     4 varying vec2 v_TexCoord;
     5 
     6 void main() {
     7 // 设置坐标
     8 gl_Position = a_Position;
     9 // 传递uv坐标
    10 v_TexCoord = a_TextCoord;
    11 }
    12 
    13 // 片段着色器
    14 precision mediump float;
    15 // 声明采样器
    16 uniform sampler2D u_Sampler;
    17 varying vec2 v_TexCoord;
    18 
    19 void main() {
    20 // 设置颜色,通过采样获得指定坐标的颜色
    21 gl_FragColor = texture2D(u_Sampler, v_TexCoord);
    22 }

    示例代码点击这里;

    drawArrays和drawElements

    我们之前是提交顶点数据,然后通过drawArrays来绘制三角形,我们知道一个矩形是由两个三角形组合而成的,所以可以用下面的两种方式来进行绘制:

    1 // 3个顶点为一组进行绘制,绘制一个矩形需要6个点
    2 gl.drawArrays(gl.TRIANGLES, 0, 6);
    3 
    4 // 传入4个顶点,按照(v0,v1,v2),(v2,v1,v3),(v2,v3,v4)...的顺序进行绘制
    5 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    这里,第一种方法,会导致大量的重复顶点数据存在,第二种方法,虽然可以节省顶点数据,但是是规定好的顺序,不够自由;

    为了解决这个问题,WebGL还引入了另外一种绘制方法drawElements,这种方法需要传递两种数据,一种是顶点数据,一种是索引数据;

    顾名思义,顶点数据定义一堆不同的顶点,而索引数据定义这些顶点的绘制顺序;

    1.定义索引数组

    1 //定义索引数组
    2 var indexDatas =
    3 [
    4 0, 1, 2,
    5 2, 1, 3,
    6 ];

    2.提交到GPU

    1 //创建缓冲区对象
    2 indexBuffer = gl.createBuffer();
    3 //将缓冲区对象绑定到目标,并设定该缓冲区是索引缓冲
    4 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    5 //向缓冲区写入数据
    6 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexDatas), gl.STATIC_DRAW);

    3.着色器代码

    不需要做任何的修改;

    4.提交绘制

    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

    • 第一个参数:绘制模式,可选:gl.LINE_LOOP、gl.LINES、gl.TRIANGLES等
    • 第二个参数:绘制顶点个数
    • 第三个参数:索引数据类型,gl.UNSIGNED_BYTE对应Uint8Array,gl.UNSIGNED_SHORT对应Uint16Array
    • 第四个参数:从第几个点开始绘制

    示例代码点击这里;

  • 相关阅读:
    JS数组去重
    正则表达式验证邮箱手机号等
    js中的事件委托
    c++刷题(6/100)最长上升子序列
    c++刷题(3/100)数独,栈和队列
    在express中HMR(合并express和webpack-dev-server)
    面试整理(3)js事件委托
    面试整理(2)跨域:jsonp与CORS
    面试整理(1):原生ajax
    styled-components真的好吗?
  • 原文地址:https://www.cnblogs.com/hammerc/p/10957067.html
Copyright © 2020-2023  润新知