WebGL+Three.js—第二章 多图形绘制和动画

2.1 使用缓冲区对象

2.1.1 什么是缓冲区对象

    缓冲区对象是WebGL系统中的一块内存区域,可以一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。

    缓冲区对象解决的实际上就是多个顶点绘制的问题。

2.1.2 缓冲区对象流程

image.png

2.1.3 缓冲区执行过程

image.png

2.1.4 使用缓冲区对象

    1、创建一组顶点数据

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5



]);



        在webgl中,需要处理大量的相同类型数据,所以引入类型化数组,这样程序就可以预知到数组中的数据类型,提高性能。这个类型化数组有点类似于js使用ts来定义类型。

image.png

image.png

    2、创建缓冲区对象

        使用gl的createBuffer方法来创建一个内存区域,用来存储顶点数据。

// 创建缓冲区对象
const buffer = gl.createBuffer();

    3、webgl关联缓冲区对象

        内存区域创建成功之后,就可以通过bindBuffer(target, buffer)将缓冲区对象绑定到webgl上。

        (1)target:

            1.gl.ARRAY_BUFFER:表示缓冲区存储的是顶点的数据

            2.gl.ELEMENT_ARRAY_BUFFER:表示缓冲区存储的是顶点的索引值

        (2)buffer:已经创建好的缓冲区对象

// webgl关联缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

    4、顶点数据写入缓冲区对象

        使用gl的bufferData(target,data,type)方法来写入顶点数据。

        (1)target:类型同gl.bindBuffer中的target

        (2)data:写入缓冲区的顶点数据,如程序中的points

        (3)type:表示如何使用缓冲区对象的数据,分为以下几类

image.png

// 顶点数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

    5、attribute变量赋值

        目前缓冲区的一系列操作已经准备就绪,但由于顶点渲染需要执行顶点着色器的main函数,也就是说需要给attribute变量赋值才能生效。

        之前使用的是gl.vertexAttrib系列的方法进行赋值,这次使用的是gl的vertexAttribPointer(location,size,type,normalized,stride,offset)方法。

        (1)location:attribute变量的存储位置

        (2)size:指定每个顶点所使用数据的个数

        (3)type:指定数据格式,也就是数据类型

image.png

image.png

        (4)normalized:表示是否将数据归一化到[0,1][-1,1]这个区间

        (5)stride:两个相邻顶点之间的字节数

        (6)offset:数据偏移量

const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);




const aPosition = gl.getAttribLocation(program, 'aPosition');


// 创建一组顶点数据
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0.0, 0.5
]);

// 创建缓冲区对象
const buffer = gl.createBuffer();

// webgl关联缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

// 顶点数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);


// 给attribute变量赋值
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

        解析:

            1.attribute变量名叫aPosition,它作为第一个参数

            2.创建一组顶点数据的时候,用的是new Float32Array的方式传入了6个值,它没有区分每个顶点的数据,而是一次性把所有顶点的数据都写入进去。所以需要指定一个顶点需要读取Float32Array的多少个值,现在一个顶点由两个值就可以确定,所以第二个参数传2;

            3.Float32Array里的每个数据都是浮点型,因此第三个参数为gl.FLOAT;

            4.顶点数据已经在区间内,因此第四个参数为false;

            5.相邻顶点字节数为0,第五个参数为0;

            6.因为在Float32Array创建的顶点数据中,里面有6个数据,正好指定每个顶点读取2个数据,没有产生偏移量,第六个参数为0。

    6、激活/禁用attribute变量状态

        attribute变量目前属于未激活的状态,需要通过gl.enableVertexAttribArray(location)激活才能使用。禁用的方法是gl.disableVertexAttribArray(location)

    7、修改绘制数量

        gl.drawArrays的3个参数分别是绘制的图形、开始绘制的顶点下标、绘制多少个顶点,现在要绘制3个顶点,因此要把第三个参数设置为3。

gl.drawArrays(gl.POINTS, 0, 3);

image.png

2.1.5 代码示例

2.2 多缓冲区和数据偏移

    我们可以通过缓冲区绘制多个点,如果想控制不同的属性,例如3个点的大小分别为10、20、30,我们同样可以通过缓冲区来实现。

2.2.1 创建多个缓冲区

    1、顶点着色器使用多个变量

        之前顶点着色器使用aPosition变量控制点坐标,现在也可以设置aPointSize变量来控制点大小。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float aPointSize;
  void main() {









    // 要绘制的点的坐标






    gl_Position = aPosition;    // vec4(0.0,0.0,0.0,1.0)

    // 点的大小






    gl_PointSize = aPointSize;
  }









`;








    2、创建并使用控制顶点大小的缓冲区

        这里创建和使用缓冲区的流程,跟上文”2.1.4使用缓冲区对象”完全一致。

const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);




const aPosition = gl.getAttribLocation(program, 'aPosition');

const aPointSize = gl.getAttribLocation(program, 'aPointSize');

// 创建一组顶点数据
const points = new Float32Array([
  -0.5, -0.5,
  0.5, -0.5,
  0.0, 0.5
]);



// 创建顶点坐标缓冲区对象
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

gl.enableVertexAttribArray(aPosition);



// 创建一组顶点大小数据
const size = new Float32Array([
  10.0,
  20.0,
  30.0
]);

// 创建顶点大小缓冲区对象
const sizeBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, size, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPointSize);

gl.drawArrays(gl.POINTS, 0, 3);

image.png

2.2.2 多缓冲区流程

image.png

2.2.3 整合数据并共用缓冲区

    目前我们声明了两个数据,创建了两个缓冲区,相当于控制一个属性就得创建一套缓冲区流程,如果控制的属性很多,那么代码会变得非常冗余。

    我们可以将两组数据整合在一起,共用一个缓冲区对象,通过数据偏移的方式区分读取的数据。

    1、整合两组数据

        把顶点位置和大小这两组数据整合在一起。

// 创建一组顶点数据

const points = new Float32Array([

  -0.5, -0.5, 10,

  0.5, -0.5, 20,

  0.0, 0.5, 30

]);

    2、共用同一个缓冲区

        只需要创建一个缓冲区,然后各自对自己的attribute属性赋值和激活即可。

const aPosition = gl.getAttribLocation(program, 'aPosition');
const aPointSize = gl.getAttribLocation(program, 'aPointSize');




// 创建一组顶点数据
const points = new Float32Array([
  -0.5, -0.5, 10,
  0.5, -0.5, 20,
  0.0, 0.5, 30
]);


// 创建缓冲区对象
const buffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);



gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

gl.enableVertexAttribArray(aPosition);



gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPointSize);

gl.drawArrays(gl.POINTS, 0, 3);

    3、设置顶点数据间距

        gl.vertexAttribPointer方法的第5个参数,代表的是两个数据之间的间距(字节数)是多少,简单的理解为多少个数据是一组的。

// 创建一组顶点数据

const points = new Float32Array([

  -0.5, -0.5, 10,

  0.5, -0.5, 20,

  0.0, 0.5, 30

]);

        例如这里有3组数据,-0.5,-0.5,10是第一组,0.5,-0.5,20是第二组,0.0, 0.5, 30是第三组,那么一组由3个数据组成的,因此间距就是3,但是它这里需要传入的是字节数,因此先获取字节数,再用字节数 * 3即可。

// 获取数据对应的字节数

const BYTES = points.BYTES_PER_ELEMENT;





gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0);

gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, BYTES * 3, 0);

    4、设置偏移量

        gl.vertexAttribPointer方法的第6个参数,代表的是数据偏移量,简单的理解为在组内从第几个数据开始读取。

        例如顶点数据是-0.5,-0.5,10,顶点坐标是-0.5,-0.5,顶点大小是10,所以顶点坐标从组内的第一个数据开始读取,它没有产生偏移量,而顶点大小是从组内的第三个数据开始读取,因此它的偏移量为2,因为它要略过前面两个数据。同样它这里需要传入的是字节数,因此传入字节数 * 偏移量即可。

// 获取数据对应的字节数

const BYTES = points.BYTES_PER_ELEMENT;





gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0);

gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, BYTES * 3, BYTES * 2);

2.2.4 数据偏移执行过程

image.png

2.2.5 代码示例

2.3 实现多种图形绘制

image.png

2.3.1 线段系列

    1、LINES:线段

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5



]);



gl.drawArrays(gl.LINES, 0, 3);

image.png

        注意:LINES类型它只接收两个点的数据,超过两个它会自动忽略,像这里传了3也只会读取前两个。

    2、LINE_STRIP:折线

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5



]);



gl.drawArrays(gl.LINE_STRIP, 0, 3);

image.png

    3、LINE_LOOP:多边形

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5



]);



gl.drawArrays(gl.LINE_LOOP, 0, 3);

image.png

2.3.2 三角形系列

    三角形系列的顶点数至少需要有3个。

    1、TRIANGLES:三角形

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5,



  0.5, 0.5



]);



gl.drawArrays(gl.TRIANGLES, 0, 4);

image.png

        注意:TRIANGLES类型它可以绘制多个三角形,但顶点数必须是3的倍数,否则不生效,例如以下传入6个顶点数据。

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5,



  0.5, 0.5,
  0.5, 0.8,
  0.8, 0.5
]);
gl.drawArrays(gl.TRIANGLES, 0, 6);

image.png

    2、TRIANGLES_FAN:飘带状三角形

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5,



  0.5, 0.5



]);



gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);

image.png

    3、TRIANGLES_STRIP:条带状三角形

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  0.0, 0.5,



  0.5, 0.5



]);



gl.drawArrays(gl.TRIANGLES_STRIP, 0, 4);

image.png

        注意:可以通过TRIANGLES_STRIP类型来绘制一个正方形,不过这个正方形实际上是由两个三角形拼接起来的。

const points = new Float32Array([








  -0.5, -0.5,








  0.5, -0.5,








  -0.5, 0.5,
  0.5, 0.5



]);



gl.drawArrays(gl.TRIANGLES_STRIP, 0, 4);

image.png

2.3.3 图形总结

    1、顶点着色器的gl_PointSize只有在渲染点的时候才生效,绘制图形的时候是不生效的

    2、绘制的图形都以三角形为基础

        (1)三角形是最简单的多边形,顶点数量最少

        (2)三角形可以作为很多多边形的基础图形,可以理解为任何的多边形都可以拆分成不同的三角形组合

        (3)三个顶点能确定唯一的三角形,而且属于唯一的平面

        (4)三角形的内外状态十分明确,可以通过它的一些计算来确定某一个点对三角形的内外情况

        (5)确定了三角形的三个顶点,可以容易计算出从一个顶点逐渐变化到另一个顶点的过程

2.3.4 代码示例

2.4 图形平移-着色器

    之前我们可以通过不停地修改它的位置数据来实现图形的平移,如果是多个顶点就得通过循环来实现,还要判断数据里有哪些是x轴的数据,有哪些是y轴的数据,这样会让平移的操作变得非常的复杂。

    以下使用着色器的方式来实现图形的平移。

    1、创建attribute变量代表偏移量

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float aTranslate;         // 偏移量


  void main() {









    // 要绘制的点的坐标






    gl_Position = aPosition;    // vec4(0.0,0.0,0.0,1.0)

    // 点的大小






    gl_PointSize = 10.0;






  }









`;








    2、修改gl_Position的赋值方式

        因为平移需要改变坐标的x、y、z的其中一项,因此要先将gl_Position变成aPosition的x、y、z分别传入到vec4()里,方便后续计算。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float aTranslate;         // 偏移量


  void main() {









    // 要绘制的点的坐标






    gl_Position = vec4(aPosition.x, aPosition.y, aPosition.z, 1.0);


    // 点的大小






    gl_PointSize = 10.0;






  }









`;








    3、坐标添加偏移量的变量值

        在哪个方向需要平移就对应在aPosition的x/y/z上加上aTranslate,这里实现在x轴平移,那么坐标的x轴坐标即为aPosition.x + aTranslate。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float aTranslate;         // 偏移量


  void main() {









    // 要绘制的点的坐标






    gl_Position = vec4(aPosition.x + aTranslate, aPosition.y, aPosition.z, 1.0);
    // 点的大小






    gl_PointSize = 10.0;






  }









`;








    4、添加定时器改变偏移值

        在哪个方向需要平移就对应在aPosition的x/y/z上加上aTranslate,这里实现在x轴平移,那么坐标的x轴坐标即为aPosition.x + aTranslate。

const aTranslate = gl.getAttribLocation(program, 'aTranslate');
let x = -1;

setInterval(() => {

  x += 0.01;

  if (x > 1) {

    x = -1;

  }



  gl.vertexAttrib1f(aTranslate, x);
  gl.drawArrays(gl.TRIANGLES, 0, 3);




}, 60);

    5、代码示例

2.5 图形缩放-着色器

    跟图形平移类似,图形的缩放也可以使用attribute变量,通过坐标与缩放变量相乘来实现。

    1、创建attribute变量代表缩放比例

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float aScale;         // 缩放比例
  void main() {









    // 要绘制的点的坐标






    gl_Position = vec4(aPosition.x, aPosition.y, aPosition.z, 1.0);


    // 点的大小






    gl_PointSize = 10.0;






  }









`;








    2、坐标与缩放变量相乘

        在哪个方向需要缩放就对应在aPosition的x/y/z上乘以aScale,这里实现在x轴缩放,那么坐标的x轴坐标即为aPosition.x * aScale。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float aScale;
  void main() {









    // 要绘制的点的坐标






    gl_Position = vec4(aPosition.x * aScale, aPosition.y, aPosition.z, 1.0);
    // 点的大小






    gl_PointSize = 10.0;






  }









`;








    3、添加定时器改变缩放比例

        在哪个方向需要平移就对应在aPosition的x/y/z上加上aTranslate,这里实现在x轴平移,那么坐标的x轴坐标即为aPosition.x + aTranslate。

const aScale = gl.getAttribLocation(program, 'aScale');
let x = -1;

setInterval(() => {

  x += 0.01;

  if (x > 1) {

    x = -1;

  }



  gl.vertexAttrib1f(aScale, x);
  gl.drawArrays(gl.TRIANGLES, 0, 3);




}, 60);

    4、代码示例

2.6 图形旋转-着色器

    之前我们可以通过不停地修改它的位置数据来实现图形的平移,如果是多个顶点就得通过循环来实现,还要判断数据里有哪些是x轴的数据,有哪些是y轴的数据,这样会让平移的操作变得非常的复杂。

    以下使用着色器的方式来实现图形的平移。

    1、创建attribute变量代表旋转角度

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float deg;

  void main() {









    // 要绘制的点的坐标






    gl_Position = vec4(aPosition.x, aPosition.y, aPosition.z, 1.0);


    // 点的大小






    gl_PointSize = 10.0;






  }









`;








    2、设置旋转公式

        这里需要根据旋转的角度,并运用三角函数的计算来实现。这里实现围绕z轴进行旋转,那么坐标的x轴和y轴坐标都要套入计算公式。

        关于旋转的计算公式,在2.9.1会详细介绍。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  attribute float deg;

  void main() {









    gl_Position.x = aPosition.x * cos(deg) - aPosition.y * sin(deg);
    gl_Position.y = aPosition.x * sin(deg) + aPosition.y * cos(deg);
    gl_Position.z = aPosition.z;
    gl_Position.w = aPosition.w;
  }









`;








    3、添加定时器改变旋转角度

        定时器不需要判断条件,因为当角度超过360的时候就等于回到了原点。

const deg = gl.getAttribLocation(program, 'deg');



let x = 1;
setInterval(() => {
  x += 0.1;
  gl.vertexAttrib1f(deg, x);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
}, 60);

    4、使用requestAnimationFrame代替setInterval

        使用requestAnimationFrame来实现动画,比setInterval要流畅得多。

let x = 1;
function amination() {
  x += 0.01;
  gl.vertexAttrib1f(deg, x);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(amination)
}
amination();

    5、代码示例

2.7 图形平移-平移矩阵

2.7.1 矩阵简介

    矩阵就是纵横排列的数据表格(m行n列),它就类似于二维数组,作用是把一个点转换到另一个点。

    矩阵可以分为行主序和列主序,行主序就是元素按行来存储,列主序按列来存储。

image.png

2.7.2 平移矩阵

    1、使用矩阵表示转换关系

image.png

        如果把黄色的三角形平移到虚线的三角形,它需要改变x、y、z三个坐标。如果使用4*4的伪矩阵来代表它们的转换关系:

image.png

    2、转换公式

        (1)ax + by + cz + d = x + x1:只有当a = 1, b = c = 0, d = x1的时候,等式左右两边成立

        (2)ex + fy + gz + h = y + y1:只有当f = 1, e = g = 0, h = y1的时候,等式左右两边成立

        (3)ix + jy + kz + l = z + z1:只有当k = 1, i = j = 0, l = z1的时候,等式左右两边成立

        (4)mx + ny + oz + p = 1:只有当m = n = o = 0, p = 1的时候,等式左右两边成立

    3、获得平移矩阵

        代入公式之后得到左边的矩阵,它是按行存储的,但webgl是按列存储的,因此需要将它转换为列存储。这个矩阵是通过计算得到的,然后可以将它运用到程序里面来实现图形的平移。

image.png

2.7.3 通过矩阵实现图形平移

    1、声明矩阵变量

        首先声明一个接收矩阵的变量,而且使用uniform来声明,数据类型是mat4。uniform变量是针对的顶点生效的,因为我们平移三角形是需要平移所有顶点的,所以用uniform。

        声明变量之后,就可以把当前的矩阵应用在位置信息上,我们就通过mat * aPosition实现矩阵对于位置坐标的转换。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  uniform mat4 mat;

  void main() {









    gl_Position = mat * aPosition

    gl_PointSize = 10.0;

  }



`;

    2、获取矩阵变量

        可以通过getUniformLocation(program, location)方法获取矩阵变量。

const mat = gl.getUniformLocation(program, 'mat');

    3、创建平移矩阵函数

        这个矩阵函数,返回的数据其实就是上述的平移矩阵的数据,需要我们手动将x、y、z的参数传入,它们这3个参数默认值为0,也就是说这3个方向的偏移量统一为0。

image.png

function getTranslateMatrix(x = 0, y = 0, z = 0) {
  return new Float32Array([

    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    x,   y,   z,   1
  ])


}



    4、实现动画函数

        (1)获取矩阵

            通过矩阵函数来获得矩阵,这里实现x和y轴的方向平移,因此传入x和y的值作为参数。

let x = -1;

function amination() {



  x += 0.01;



  if (x > 1) {

    x = -1;

  }


  const matrix = getTranslateMatrix(x, x);

  gl.uniformMatrix4fv(mat, false, matrix);


  gl.drawArrays(gl.TRIANGLES, 0, 3);




  requestAnimationFrame(amination)


}


amination();


        (2)对矩阵变量赋值

            之前对vec2/3/4类型用的是vertexAttrib1/2/3/4f方法进行赋值。目前对mat4类型进行赋值,使用gl.uniformMatrix4fv()方法。

image.png

let x = -1;

function amination() {



  x += 0.01;



  if (x > 1) {

    x = -1;

  }


  const matrix = getTranslateMatrix(x, x);

  gl.uniformMatrix4fv(mat, false, matrix);


  gl.drawArrays(gl.TRIANGLES, 0, 3);




  requestAnimationFrame(amination)


}


amination();


2.7.4 代码示例

2.8 图形缩放-缩放矩阵

2.8.1 缩放矩阵

    1、使用矩阵表示转换关系

image.png

        如果把蓝色的三角形缩放到虚线的三角形,它跟平移矩阵的区别在于,平移是相加,缩放是相乘。如果使用4*4的伪矩阵来代表它们的转换关系,这个矩阵的映射公式跟平移矩阵是一样的:

image.png

    2、转换公式

        (1)ax + by + cz + d = Tx * x:只有当a = Tx, b = c = d = 0的时候,等式左右两边成立

        (2)ex + fy + gz + h = Ty * y:只有当f = Ty, e = g = h = 0的时候,等式左右两边成立

        (3)ix + jy + kz + l = Tz * z:只有当k = Tz, i = j = l = 0的时候,等式左右两边成立

        (4)mx + ny + oz + p = 1:只有当m = n = o = 0, p = 1的时候,等式左右两边成立

    3、获得缩放矩阵

        代入公式之后得到左边的矩阵,因为它是对称的,所以不管是行还是列主序都是一样的。

image.png

2.8.2 通过矩阵实现图形缩放

    第1步声明矩阵变量和第2步获取矩阵变量,跟平移矩阵是一样的,可以参考2.7.3。

    3、创建缩放矩阵函数

        这个矩阵函数,返回的数据其实就是上述的缩放矩阵的数据,需要我们手动将x、y、z的参数传入,它们这3个参数默认值为1,也就是说这3个方向的缩放比例统一为1。

image.png

function getScaleMatrix(x = 1, y = 1, z = 1) {
  return new Float32Array([

    x, 0.0, 0.0, 0.0,
    0.0, y, 0.0, 0.0,
    0.0, 0.0, z, 0.0,
    0.0, 0.0, 0.0, 1
  ])


}



    4、实现动画函数

        步骤跟平移矩阵完全一样,可以参考2.7.3。在x的初始值和范围稍作调整,从0.1开始到1.5,也就是缩放比例从0.1倍到1.5倍。

let x = 0.1;
function amination() {



  x += 0.01;



  if (x > 1.5) {
    x = 0.1;
  }


  const matrix = getScaleMatrix(x, x);
  gl.uniformMatrix4fv(mat, false, matrix);


  gl.drawArrays(gl.TRIANGLES, 0, 3);




  requestAnimationFrame(amination)


}


amination();


2.8.3 代码示例

2.9 图形旋转-旋转矩阵

2.9.1 旋转矩阵

    1、三角公式

image.png

        图形旋转,以蓝色三角形旋转到虚线三角形为例。假设旋转前的顶点A的坐标为(x,y,z),旋转后的顶点A’的坐标为(x’,y’,z’):

        (1)顶点A的坐标

            x = R * cos(α)

            y = R * sin(α)

            z = 0

        (2)顶点A’的坐标

            x’ = R * cos(α + β)

            y’ = R * sin(α + β)

            z’ = 0

            根据三角公式得知:

            cos(α + β) = cos(α) * cos(β) – sin(α) * sin(β)

            sin(α + β) = sin(α) * cos(β) + cos(α) * sin(β)

            顶点A’的坐标代入三角公式为:

            x’ = R * (cos(α) * cos(β) – sin(α) * sin(β)) = R*cos(α)cos(β) – Rsin(α)*sin(β)

            y’ = R * (sin(α) * cos(β) + cos(α) * sin(β)) = R*sin(α)cos(β) + Rcos(α)*sin(β)

            z’ = 0

        (3)顶点A的公式代入顶点A’

            x’ = x * cos(β) – y * sin(β)

            y’ = y * cos(β) + x * sin(β)

            z’ = z

    2、矩阵推导

        矩阵的映射公式跟平移/缩放矩阵是一样的:

image.png

    3、转换公式

        (1)ax + by + cz + d = x * cos(β) – y * sin(β):只有当a = cos(β), b = -sin(β),c = d = 0的时候,等式左右两边成立

        (2)ex + fy + gz + h = y * cos(β) + x * sin(β):只有当e = sin(β), f = cos(β),g = h = 0的时候,等式左右两边成立

        (3)ix + jy + kz + l = Tz * z:只有当k = 1, i = j = l = 0的时候,等式左右两边成立

        (4)mx + ny + oz + p = 1:只有当m = n = o = 0, p = 1的时候,等式左右两边成立

    3、获得旋转矩阵

        代入公式之后得到以下的旋转矩阵:

image.png

2.9.2 通过矩阵实现图形旋转

    第1步声明矩阵变量和第2步获取矩阵变量,跟平移矩阵是一样的,可以参考2.7.3。

    3、创建旋转矩阵函数

        这个矩阵函数只需要接收deg角度参数即可。

function getRotateMatrix(deg) {
  return new Float32Array([
    Math.cos(deg),  Math.sin(deg), 0.0, 0.0,
    -Math.sin(deg), Math.cos(deg), 0.0, 0.0,
    0.0,            0.0,           1.0, 0.0,
    0.0,            0.0,           0.0, 1
  ])


}



    4、实现动画函数

        步骤跟平移矩阵完全一样,可以参考2.7.3。变量x不需要加判断,因为到达360度就等于回到原点,一直累加即可。

let x = 0;
function amination() {



  x += 0.01;



  const matrix = getRotateMatrix(x);
  gl.uniformMatrix4fv(mat, false, matrix);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(amination)
}



amination();

2.9.3 代码示例

2.10 图形复合变换-矩阵组合

    图形复合变换指的是它包含平移、缩放、旋转中的两项以上。

2.10.1 三个矩阵共同控制图形变换

    1、声明三个矩阵变量

        声明三个矩形变量分别对应平移、缩放、旋转,然后将这3个变量与坐标变量aPosition相乘,实现复合变换。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  uniform mat4 translateMatrix;
  uniform mat4 scaleMatrix;
  uniform mat4 rotationMatrix;
  void main() {
    gl_Position = translateMatrix * scaleMatrix * rotationMatrix * aPosition;
    gl_PointSize = 10.0;






  }









`;








    2、获取三个矩阵变量

const translateMatrix = gl.getUniformLocation(program, 'translateMatrix');
const scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
const rotationMatrix = gl.getUniformLocation(program, 'rotationMatrix');

    3、调整动画函数

        动画函数里要获取这3个矩阵,然后分别定义对应的变量作为参数传入平移、缩放、旋转的矩阵函数。

let deg = 0;

let translateX = -1;

let scaleX = 0.1;

function amination() {

  deg += 0.01;

  translateX += 0.01;

  scaleX += 0.01;



  if (translateX > 1) {

    translateX = -1;

  }




  if (scaleX > 1.5) {

    scaleX = 0.1;

  }




  const translate = getTranslateMatrix(translateX);

  const scale = getScaleMatrix(scaleX);

  const rotate = getRotateMatrix(deg);

  gl.uniformMatrix4fv(translateMatrix, false, translate);
  gl.uniformMatrix4fv(scaleMatrix, false, scale);
  gl.uniformMatrix4fv(rotationMatrix, false, rotate);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(amination);
}
amination();

2.10.2 组合矩阵

    上述的方式是可以实现图形复合的,但是它需要声明3个uniform变量,然后传入3个矩阵,然后改变3个变量值。这样的方式显得比较麻烦而且代码比较臃肿。

    可以通过将3个矩阵组合在一起,也一样能实现图形变换。

    1、矩阵相乘

        矩阵相乘的执行过程是矩阵A的列乘以矩阵B的行。

        注意:矩阵的乘法是不符合乘法交换律的,也就是说矩阵A * 矩阵B,是不等于矩阵B * 矩阵A的。

image.png

2.10.3 通过组合矩阵实现图形复合

    1、声明组合矩阵变量

        使用组合矩阵之后,就不需要声明3个uniform变量了,只需要声明一个uniform变量来代表组合矩阵即可。

const VERTEX_SHADER_SOURCE = `










  attribute vec4 aPosition;










  uniform mat4 mat;

  void main() {









    gl_Position = mat * aPosition

    gl_PointSize = 10.0;

  }



`;

    2、获取组合矩阵变量

const mat = gl.getUniformLocation(program, 'mat');

    3、创建矩阵组合函数

function mixMatrix(A, B) {
  const result = new Float32Array(16);




  for (let i=0; i<4; i++) {
    result[i] = A[i] * B[0] + A[i + 4] * B[1] + A[i + 8] * B[2] + A[i + 12] * B[3];
    result[i + 4] = A[i] * B[4] + A[i + 4] * B[5] + A[i + 8] * B[6] + A[i + 12] * B[7];
    result[i + 8] = A[i] * B[8] + A[i + 4] * B[9] + A[i + 8] * B[10] + A[i + 12] * B[11];
    result[i + 12] = A[i] * B[12] + A[i + 4] * B[13] + A[i + 8] * B[14] + A[i + 12] * B[15];
  }











  return result;
}

    4、优化动画函数

        动画函数里不再需要分别获取3个矩阵,只需要获取一个组合矩阵即可。

let deg = 0;

let translateX = -1;

let scaleX = 0.1;

function amination() {

  deg += 0.01;

  translateX += 0.01;

  scaleX += 0.01;



  if (translateX > 1) {

    translateX = -1;

  }




  if (scaleX > 1.5) {

    scaleX = 0.1;

  }




  const translate = getTranslateMatrix(translateX);

  const scale = getScaleMatrix(scaleX);

  const rotate = getRotateMatrix(deg);



  // 组合矩阵控制图形复合
  const matrix = mixMatrix(mixMatrix(translate, scale), rotate);
  gl.uniformMatrix4fv(mat, false, matrix);

  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(amination);
}
amination();

2.10.4 代码示例

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY0Kw5iE' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片