9、webgl 中的 矩阵转换

1、Matrix4函数库

此处需要了解一个矩阵函数库cuon-matrix.js这是 《webgl编程指南》作者写的一个库,它封装了一些简单易用的方法,来实现一些复杂繁琐的矩阵计算操作;

代码地址:
https://gitee.com/ithanmang/webgl-notes

具体方法如下表:

9、webgl 中的 矩阵转换

Matrix4对象有两种方法,一种是名称中带有set和一种不带set的;

  • 包含set的 – 会根据参数计算出 变换矩阵,然后将变换矩阵写入到 自身中;
  • 不含set的 – 会现根据参数计算出变换矩阵,然后将自身与刚刚计算得到的变换矩阵相乘;

2、基本变换

使用上面的方法可以方便进行矩阵变换:

如平移操作

var xformMatrix = new Matrix4();

// 平移
xformMatrix.setTranslate(0.5, 0.5, 0.5);

// 将变换后的矩阵传递 其中的 elements 是列主序的类型化数组
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix.elements);

顶点着色器接收就可以了

attribute vec4 a_Position;
uniform mat4 u_xformMatrix;
void main(){
    // 变换后的坐标 = 变换矩阵 * 原始坐标
    gl_Position = u_xformMatrix * a_Position;
}

3、复合变换

例如我们想要先平移然后再旋转:

9、webgl 中的 矩阵转换

显然,这包括了两种变换,先进行平移变换然后再进行旋转变换

首先,先进行平移变换

  • <平移后的坐标> = <平移矩阵> * <原始坐标>

然后,平移后的坐标再进行旋转变换

  • <平移旋转后的坐标> = <旋转矩阵> * <平移后的坐标>

组合起来就是

  • <平移旋转后的坐标> = <旋转矩阵> * ( <平移矩阵> * <原始坐标>)

所以可以先计算<旋转矩阵> * <平移矩阵>,然后将多次变换后的矩阵乘以 原始坐标就可以实现复合变换;

一个模型可能经过了多次变换,将这些变换全部复合成一个等效的变换,也就得到了 模型变换,或称 建模变换,所以,模型变换的矩阵称为 模型矩阵

所以此时着色器可以这样写

attribute vec4 a_Position;
// 声明一个模型矩阵
uniform mat4 u_ModelMatrix;
void main(){
    // 复合变换后的坐标 = 模型矩阵 * 原始坐标
    gl_Position = u_ModelMatrix * a_Position;
}

使用Matrix4.js中的方法可以这样写,先平移然后再旋转

  • 先平移:xformMatrix.setTranslate(0.5, 0.5, 0)
  • 再旋转:xformMatrix.rotate(45, 0, 0, 1)

9、webgl 中的 矩阵转换

先旋转再平移,和先平移再旋转计算的模型矩阵不一定相同

实例代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>先平移后旋转</title>
    <link rel="stylesheet" href="../css/common.css">
</head>
<body>
<canvas id="webgl" width="512" height="512"></canvas>
</body>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>

<script>

    // 顶点着色器
    var vertex_shader_source = '' +
        'attribute vec4 a_Position;' +
        'uniform mat4 u_ModelMatrix;' +
        'void main() {' +
        '   gl_Position = u_ModelMatrix * a_Position;' +
        '}';

    // 片元着色器
    var fragment_shader_source = '' +
        'void main(){' +
        '   gl_FragColor = vec4(0.5, 0.0, 0.5, 1.0);' +
        '}';

    (function () {

        // 获取canvas对象
        var canvas = document.getElementById('webgl');

        // 获取webgl 上下文对象
        var gl = getWebGLContext(canvas);

        // 初始化着色器
        if (!initShaders(gl, vertex_shader_source, fragment_shader_source)) {
            console.log('初始化着色器失败!');
            return false;
        }

        // 设置顶点位置
        var n = initVertexBuffer(gl);
        if (n < 0) {
            console.log('顶点写入缓存失败!');
            return false;
        }

        // 旋转角度
        var ANGLE = 90.0;

        // 创建旋转矩阵对象
        var modelMatrix = new Matrix4();
        modelMatrix.setRotate(ANGLE, 0, 0, 1);
        modelMatrix.translate(0.5, 0.5, 0);

        
        // 通过旋转矩阵传递给顶点着色器
        var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
        if (!u_ModelMatrix) {
            console.log('获取uniform变量失败!');
            return false;
        }

        gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

        // 设置清空颜色
        gl.clearColor(0.0, 0.5, 0.5, 1.0);

        // 清空canvas
        gl.clear(gl.COLOR_BUFFER_BIT);

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

        // 将顶点信息写入缓存区
        function initVertexBuffer(gl) {

            var vertices = new Float32Array([
                0.0, 0.25, 0.25, -0.25, -0.25, -0.25
            ]);

            var n = vertices.length / 2;

            // 创建缓冲区对象
            var vertexBuffer = gl.createBuffer();
            if (!vertexBuffer) {
                console.log('创建缓冲区对象失败!');
                return -1;
            }

            // 绑定缓冲区对象到目标
            gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

            // 将数据写入到缓冲区对象
            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

            var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
            if (a_Position < 0) {
                console.log('获取attribute变量失败!');
                return -1;
            }

            // 将缓冲区对象分配给attribute变量
            gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

            // 开启attribute变量
            gl.enableVertexAttribArray(a_Position);

            return n;

        }

    }());

</script>
</html>