Skip to content

五、图形变换

1.图形平移-着色器

js
const canvas = document.querySelector("#canvas");

const gl = canvas.getContext("webgl");

// 顶点着色器
const VERTEX_SHADER_SOURCE = `
                attribute vec4 aPosition;
                attribute float aTranslate;
                void main() {		
                    gl_Position = vec4(aPosition.x+aTranslate,aPosition.y,aPosition.z,1.0);
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

const aPosition = gl.getAttribLocation(program, "aPosition");
const aTranslate = gl.getAttribLocation(program, "aTranslate");

const points = new Float32Array([
    -0.5, -0.5, 
     0.5, -0.5,
    -0.5,  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);

let x = 0;
const animate = () => {
    requestAnimationFrame(animate);
    x += 0.01;
    gl.vertexAttrib1f(aTranslate, x);
    gl.drawArrays(gl.TRIANGLE_FAN, 0, 3);
};
animate();

2.图形缩放-着色器

js
const canvas = document.querySelector("#canvas");

const gl = canvas.getContext("webgl");

// 顶点着色器
const VERTEX_SHADER_SOURCE = `
                attribute vec4 aPosition;
                attribute float aScale;
                void main() {
                    gl_Position = vec4(aPosition.x*aScale,aPosition.y,aPosition.z,1.0);
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

const aPosition = gl.getAttribLocation(program, "aPosition");
const aScale = gl.getAttribLocation(program, "aScale");

const points = new Float32Array([
    -0.5, -0.5, 
    0.5, -0.5, 
    -0.5,  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);

let x = 0;
const animate = () => {
    requestAnimationFrame(animate);
    x += 0.01;
    gl.vertexAttrib1f(aScale, x);
    gl.drawArrays(gl.TRIANGLE_FAN, 0, 3);
};
animate();

3.图形旋转-着色器

js
const canvas = document.querySelector("#canvas");

const gl = canvas.getContext("webgl");

// 顶点着色器
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;
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

const aPosition = gl.getAttribLocation(program, "aPosition");
const deg = gl.getAttribLocation(program, "deg");

const points = new Float32Array([
    -0.5, -0.5, 
    0.5, -0.5, 
    -0.5, 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);

let x = 0;
const animate = () => {
    requestAnimationFrame(animate);
    x += 0.01;
    gl.vertexAttrib1f(deg, x);
    gl.drawArrays(gl.TRIANGLE_FAN, 0, 3);
};
animate();

4.图形平移-矩阵

4.1 线性代数平移知识

1.下图中,当点A移动到A',可以得到下列式子

image-20231007152538677

matlab
// 公式一
x' = x + x1
y' = y + y1
z' = z + z1
w' = w = 1 // 齐次坐标为1

2.我们定义一个四维矩阵,与位置向量vec4(x,y,z,w)相乘,可以得出下列式子

[xyzw][abcdefghijklmnop]=[xyzw]
matlab
// 公式二
ax + by + cz + d = x'	
ex + fy + gz + h = y'
ix + jy + kz + l = z'
mx + ny + oz + p = w'

3.由公式一、二可得

matlab
ax + by + cz + d = x + x1		// 仅当a=1,b=c=0,d=x1时,等式两边成立
ex + fy + gz + h = y + y1		// 仅当f=1,e=g=0,h=y1时,等式两边成立
ix + jy + kz + l = z + z1		// 仅当k=1,i=j=0,l=z1时,等式两边成立
mx + ny + oz + p = 1			// 仅当m=n=o=0,p=1时,等式两边成立

4.由此可得系数矩阵

[100x1010y1001z10001][100001000010x1y1z11]

5.通过js将系数矩阵转化为列主序,返回列主序的矩阵数组

js
// 平移矩阵
/**
 *
 * @param {number} x 平移后的x坐标
 * @param {number} y 平移后的y坐标
 * @param {number} z 平移后的z坐标
 * @returns
 */
const 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.2 给uniform矩阵变量赋值

通过gl.uniformMatrix4fv()uniform矩阵变量赋值,语法如下

js
gl.uniformMatrix4fv(location, transpose, array)
  • location:指定 uniform 变量的存储位置
  • transpose:在 webgl 中恒为false
  • array:矩阵数组

4.3 实现图形平移

js
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl");

// 顶点着色器
const VERTEX_SHADER_SOURCE = `
                attribute vec4 aPosition;
                uniform mat4 mat;       // 创建四维矩阵
                void main() {
                    gl_Position = mat * aPosition;      // 四维矩阵与 位置向量 相乘
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

const aPosition = gl.getAttribLocation(program, "aPosition");
const mat = gl.getUniformLocation(program, "mat");

const points = new Float32Array([
    -0.5, -0.5, 
     0.5, -0.5,
    -0.5,  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);

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

let x = 0;

// 平移矩阵
const 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  ,
  ]);
};

const animate = () => {
    x += 0.01;
    if (x > 1) x = 0;
    const matrix = getTranslateMatrix(x, x);	
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(animate);
};
animate();

5.图形缩放-矩阵

5.1.线性代数缩放知识

1.下图中,当点A移动到A',可以得到公式一

image-20231007163656203

matlab
// 公式一
x' = Tx * x
y' = Ty * y
z' = Tz * z
w' = w = 1

2.查看点A和点A’之间的映射公式,可得公式二

[xyzw][abcdefghijklmnop]=[xyzw]
matlab
// 公式二
ax + by + cz + d = x'	
ex + fy + gz + h = y'
ix + jy + kz + l = z'
mx + ny + oz + p = w'

3.合并公式一、二得

matlab
ax + by + cz + d = Tx * x	// 仅当x=Tx,b=c=d=0时,等式两边成立
ex + fy + gz + h = Ty * y	// 仅当f=Ty,e=g=h=0时,等式两边成立
ix + jy + kz + l = Tz * z	// 仅当k=Tz,i=j=l=0时,等式两边成立
mx + ny + oz + p = 1		// 仅当m=n=o=0,p=1时,等式两边成立

4.由此可得系数矩阵,此矩阵行主序和列主序都相同

[tx0000ty0000tz00001]

5.获取缩放矩阵

js
/**
 * 
 * @param {number} x 缩放后的x坐标
 * @param {number} y 缩放后的y坐标
 * @param {number} z 缩放后的z坐标
 * @returns 
 */
const 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,
  ]);
};

5.2 实现图形缩放

js
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl");

// 顶点着色器
const VERTEX_SHADER_SOURCE = `
                attribute vec4 aPosition;
                uniform mat4 mat;       // 创建四维矩阵
                void main() {
                    gl_Position = mat * aPosition;      // 四维矩阵与 位置向量 相乘
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

const aPosition = gl.getAttribLocation(program, "aPosition");
const mat = gl.getUniformLocation(program, "mat");

const points = new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 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 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,
  ]);
};

let x = 0;
const animate = () => {
    x += 0.01;
    if (x > 1) x = 0;
    const matrix = getTranslateMatrix(x, x);
    console.log(matrix);
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(animate);
};
animate();

6.图形平移-矩阵

6.1 线性代数旋转知识

image-20231007165136776

1.上图中点A旋转到点A'可得

顶点A

matlab
x = R * cos(α)
y = R * sin(α)
z = 0

顶点A'

matlab
x' = R * cos(α + β)
   = R * (cos(α)*cos(β) - sin(α)*sin(β))
   = R * cos(α)*cos(β) - R * sin(α)*sin(β)
   
y' = R * sin(α + β)
   = R * (sin(α)*cos(β) - cos(α)*sin(β))
   = R * sin(α)*cos(β) - R * cos(α)*sin(β)

公式合并之后得到公式一

matlab
// 公式一
x' = x/cos(α) * cos(α)*cos(β) - y/sin(α) * sin(α)*sin(β)
   = x * cos(β) - y * sin(β)
   
y' = y/sin(α) * sin(α)*cos(β) - x/cos(α) * cos(α)*sin(β)
   = y * cos(β) + x * sin(β)
   
z' = z

2.通过矩阵推导

[xyzw][abcdefghijklmnop]=[xyzw]
matlab
// 公式二
ax + by + cz + d = x'	
ex + fy + gz + h = y'
ix + jy + kz + l = z'
mx + ny + oz + p = w'

3.合并公式一、二得

matlab
ax + by + cz + w = x * cos(β) - y * sin(β) // 仅当a = cos(β),b = -sin(β),c = w = 0时,等式两边成立
ex + fy + gz + h = y * cos(β) + x * sin(β) // 仅当e = sin(β) f = cos(β), g = h = 0时,等式两边成立
ix + jy + kz + l = z		// 仅当 k = 1, i = j = k = 0时,等式两边成立
mx + ny + oz + p = 1  		// 仅当 m = n = o = 0, p = 1时,等式两边成立

4.由此得到旋转矩阵

[cos(β)sin(β)00sin(β)cos(β)0000100001][cos(β)sin(β)00sin(β)cos(β)0000100001]

5.通过js获取旋转矩阵

js
/**
 * 
 * @param {number} deg 旋转的度数
 * @returns 返回旋转矩阵
 */
const 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,
  ]);
};

6.2 实现图形旋转

js
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl");

// 顶点着色器
const VERTEX_SHADER_SOURCE = `
                attribute vec4 aPosition;
                uniform mat4 mat;
                void main() {
                    gl_Position = mat * aPosition;
                    gl_PointSize = 10.0;
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

const aPosition = gl.getAttribLocation(program, "aPosition");
const mat = gl.getUniformLocation(program, "mat");

const points = new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    -0.5, 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);

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

const 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,
    ]);
};

let x = 0;
const animate = () => {
    x += 0.01;
    const matrix = getRotateMatrix(x);
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(animate);
};
animate();

7.图形复合变换-矩阵组合

7.1 封装函数

使用下面方法,可以混合两个矩阵,从而达到矩阵的复合变换

js
/**
 * 
 * @param {Float32Array} A 矩阵数组1
 * @param {Float32Array} B 矩阵数组2
 * @returns 
 */
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;
}

7.2 实现复合变换

js
const canvas = document.querySelector("#canvas");

const gl = canvas.getContext("webgl");

// 顶点着色器
const VERTEX_SHADER_SOURCE = `
                attribute vec4 aPosition;
                uniform mat4 mtx;
                void main() {
                    gl_Position = mtx  * aPosition;
                    gl_PointSize = 10.0;
                }
            `;

const FRAGMENT_SHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
                }
            `;

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

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

const mtx = gl.getUniformLocation(program, "mtx");
// prettier-ignore
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);


let deg = 0;
let translateX = -1;
let scaleX = 0.1;

const animate = () => {
    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 martix = mixMatrix(mixMatrix(translate, scale), rotate);
    gl.uniformMatrix4fv(mtx, false, martix);
    // gl.uniformMatrix4fv(scaleMatrix, false, scale);
    // gl.uniformMatrix4fv(rotateMatrix, false, rotate);

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

MIT Licensed