计算机图形学变换矩阵入门

321 阅读4分钟

一、引言

在计算机图形学中,变换矩阵是实现图形平移、旋转、缩放等操作的数学基础。理解变换矩阵不仅有助于掌握图形学的基本原理,也是游戏开发、计算机辅助设计 (CAD)、虚拟现实等领域的必备技能。本文将深入浅出地介绍变换矩阵的基本概念和 JavaScript 实现。

二、基本变换类型

1. 平移变换

平移是将图形从一个位置移动到另一个位置的操作。在二维空间中,平移变换可以表示为:

// 二维平移变换函数
function translate(x, y, tx, ty) {
    return [x + tx, y + ty];
}

2. 旋转变换

旋转是将图形绕某个点旋转一定角度的操作。在二维空间中,绕原点旋转 θ 角度的变换可以表示为:

// 二维旋转变换函数(绕原点)
function rotate(x, y, angle) {
    const radians = angle * Math.PI / 180;
    const cos = Math.cos(radians);
    const sin = Math.sin(radians);
    return [
        x * cos - y * sin,
        x * sin + y * cos
    ];
}

3. 缩放变换

缩放是改变图形大小的操作。在二维空间中,缩放变换可以表示为:

// 二维缩放变换函数
function scale(x, y, sx, sy) {
    return [x * sx, y * sy];
}

三、变换矩阵表示

1. 齐次坐标

为了将平移变换也表示为矩阵乘法,引入齐次坐标。在二维空间中,点 (x, y) 的齐次坐标表示为 (x, y, 1)。

2. 二维变换矩阵

在齐次坐标下,二维变换矩阵是一个 3×3 的矩阵:

// 二维变换矩阵类
class Matrix2D {
    constructor() {
        // 初始化为单位矩阵
        this.data = [            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1]
        ];
    }
    
    // 矩阵乘法
    multiply(matrix) {
        const result = new Matrix2D();
        // 矩阵乘法实现...
        return result;
    }
    
    // 设置平移变换
    setTranslate(tx, ty) {
        this.data = [            [1, 0, tx],
            [0, 1, ty],
            [0, 0, 1]
        ];
    }
    
    // 设置旋转变换
    setRotate(angle) {
        const radians = angle * Math.PI / 180;
        const cos = Math.cos(radians);
        const sin = Math.sin(radians);
        this.data = [            [cos, -sin, 0],
            [sin, cos, 0],
            [0, 0, 1]
        ];
    }
    
    // 设置缩放变换
    setScale(sx, sy) {
        this.data = [            [sx, 0, 0],
            [0, sy, 0],
            [0, 0, 1]
        ];
    }
    
    // 应用变换到点
    transformPoint(x, y) {
        const point = [x, y, 1];
        const result = [0, 0, 0];
        
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                result[i] += this.data[i][j] * point[j];
            }
        }
        
        return [result[0], result[1]];
    }
}

四、变换组合

多个变换可以通过矩阵乘法组合成一个复合变换矩阵。注意,矩阵乘法不满足交换律,因此变换顺序很重要。

// 变换组合示例
function compositeTransform() {
    const matrix = new Matrix2D();
    
    // 先缩放
    matrix.setScale(2, 2);
    
    // 再旋转
    const rotateMatrix = new Matrix2D();
    rotateMatrix.setRotate(45);
    matrix.multiply(rotateMatrix);
    
    // 最后平移
    const translateMatrix = new Matrix2D();
    translateMatrix.setTranslate(100, 50);
    matrix.multiply(translateMatrix);
    
    // 应用复合变换到点(10, 10)
    const result = matrix.transformPoint(10, 10);
    return result;
}

五、三维变换矩阵简介

在三维空间中,变换矩阵扩展为 4×4 矩阵,用于处理三维点的平移、旋转和缩放。

// 三维变换矩阵类(简化版)
class Matrix3D {
    constructor() {
        // 初始化为单位矩阵
        this.data = [
            [1, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ];
    }
    
    // 三维旋转变换(绕Z轴)
    setRotateZ(angle) {
        const radians = angle * Math.PI / 180;
        const cos = Math.cos(radians);
        const sin = Math.sin(radians);
        this.data = [
            [cos, -sin, 0, 0],
            [sin, cos, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ];
    }
    
    // 其他三维变换方法...
}

六、实际应用示例

下面是一个使用 HTML5 Canvas 和变换矩阵实现图形变换的完整示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图形变换矩阵示例</title>
    <style>
        canvas {
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
        // 获取Canvas上下文
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        
        // 二维变换矩阵类
        class Matrix2D {
            constructor() {
                this.data = [
                    [1, 0, 0],
                    [0, 1, 0],
                    [0, 0, 1]
                ];
            }
            
            multiply(matrix) {
                const result = new Matrix2D();
                const a = this.data;
                const b = matrix.data;
                
                for (let i = 0; i < 3; i++) {
                    for (let j = 0; j < 3; j++) {
                        result.data[i][j] = 0;
                        for (let k = 0; k < 3; k++) {
                            result.data[i][j] += a[i][k] * b[k][j];
                        }
                    }
                }
                
                return result;
            }
            
            setTranslate(tx, ty) {
                this.data = [
                    [1, 0, tx],
                    [0, 1, ty],
                    [0, 0, 1]
                ];
            }
            
            setRotate(angle) {
                const radians = angle * Math.PI / 180;
                const cos = Math.cos(radians);
                const sin = Math.sin(radians);
                this.data = [
                    [cos, -sin, 0],
                    [sin, cos, 0],
                    [0, 0, 1]
                ];
            }
            
            setScale(sx, sy) {
                this.data = [
                    [sx, 0, 0],
                    [0, sy, 0],
                    [0, 0, 1]
                ];
            }
            
            // 将矩阵应用到Canvas上下文
            applyToContext(ctx) {
                const m = this.data;
                ctx.transform(m[0][0], m[1][0], m[0][1], m[1][1], m[0][2], m[1][2]);
            }
        }
        
        // 绘制原始三角形
        function drawOriginalTriangle() {
            ctx.save();
            ctx.strokeStyle = 'blue';
            ctx.beginPath();
            ctx.moveTo(0, -50);
            ctx.lineTo(43.3, 25);
            ctx.lineTo(-43.3, 25);
            ctx.closePath();
            ctx.stroke();
            ctx.restore();
        }
        
        // 绘制变换后的三角形
        function drawTransformedTriangle() {
            const matrix = new Matrix2D();
            
            // 设置复合变换
            const translate1 = new Matrix2D();
            translate1.setTranslate(100, 100);
            
            const rotate = new Matrix2D();
            rotate.setRotate(45);
            
            const scale = new Matrix2D();
            scale.setScale(1.5, 1.5);
            
            const translate2 = new Matrix2D();
            translate2.setTranslate(50, 0);
            
            // 注意矩阵乘法顺序(从右到左)
            matrix.multiply(translate1);
            matrix.multiply(rotate);
            matrix.multiply(scale);
            matrix.multiply(translate2);
            
            ctx.save();
            matrix.applyToContext(ctx);
            
            ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
            ctx.beginPath();
            ctx.moveTo(0, -50);
            ctx.lineTo(43.3, 25);
            ctx.lineTo(-43.3, 25);
            ctx.closePath();
            ctx.fill();
            
            ctx.restore();
        }
        
        // 绘制坐标系
        function drawAxes() {
            ctx.save();
            ctx.strokeStyle = '#ccc';
            ctx.beginPath();
            ctx.moveTo(-canvas.width, 0);
            ctx.lineTo(canvas.width, 0);
            ctx.moveTo(0, -canvas.height);
            ctx.lineTo(0, canvas.height);
            ctx.stroke();
            ctx.restore();
        }
        
        // 渲染函数
        function render() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // 将原点移到Canvas中心
            ctx.save();
            ctx.translate(canvas.width / 2, canvas.height / 2);
            
            drawAxes();
            drawOriginalTriangle();
            drawTransformedTriangle();
            
            ctx.restore();
        }
        
        // 初始化
        render();
    </script>
</body>
</html>

七、总结

变换矩阵是计算机图形学的核心概念之一,通过矩阵运算可以高效地实现图形的各种变换。掌握变换矩阵不仅有助于理解图形学 API 的工作原理,也为更高级的图形处理技术打下基础。