Skip to content

变换与矩阵

在3D图形学中,变换是将一个几何对象从一个位置、方向和大小转换到另一个位置、方向和大小的过程。矩阵是实现这些变换的强大工具。通过矩阵乘法,可以高效地实现平移、旋转和缩放等变换。

一、矩阵变换

(一)平移变换

平移变换是将一个对象沿着某个方向移动一定的距离。在3D空间中,平移可以通过一个4x4的平移矩阵实现。平移矩阵的形式如下:

T=[100tx010ty001tz0001]

其中,txtytz分别表示在x、y和z方向上的平移量。

(二)旋转变换

旋转变换是将一个对象围绕某个轴旋转一定的角度。在3D空间中,旋转变换可以通过一个4x4的旋转矩阵实现。例如,围绕z轴旋转θ度的旋转矩阵如下:

Rz(θ)=[cos(θ)sin(θ)00sin(θ)cos(θ)0000100001]

类似地,可以定义围绕x轴和y轴的旋转矩阵。

(三)缩放变换

缩放变换是将一个对象在某个方向上放大或缩小一定的比例。在3D空间中,缩放变换可以通过一个4x4的缩放矩阵实现。缩放矩阵的形式如下:

S=[sx0000sy0000sz00001]

其中,sxsysz分别表示在x、y和z方向上的缩放比例。

二、模型视图矩阵

模型视图矩阵(Model-View Matrix)是将一个对象从模型空间转换到视图空间的矩阵。模型空间是对象的局部坐标系,而视图空间是相对于观察者的坐标系。模型视图矩阵通常由以下两部分组成:

  1. 模型矩阵(Model Matrix):将对象从模型空间转换到世界空间。
  2. 视图矩阵(View Matrix):将对象从世界空间转换到视图空间。

模型视图矩阵可以通过模型矩阵和视图矩阵的乘积得到:

Mmodel-view=Mview×Mmodel

(一)模型矩阵

模型矩阵用于描述对象在世界空间中的位置、方向和大小。它可以通过平移、旋转和缩放矩阵的组合来构建。例如,先平移,再旋转,最后缩放:

Mmodel=S×R×T

(二)视图矩阵

视图矩阵用于描述观察者的位置和方向。它通常通过一个“观察矩阵”来构建,该矩阵将世界空间中的对象转换到观察者的坐标系中。视图矩阵可以通过以下参数构建:

  • 观察者的位置(eye):观察者的坐标。
  • 目标点(at):观察者正在看的点。
  • 上方向(up):定义观察者的“上”方向。

视图矩阵可以通过以下公式计算:

Mview=lookAt(eye,at,up)

三、投影矩阵

投影矩阵(Projection Matrix)用于将3D空间中的对象投影到2D屏幕上。有两种主要的投影方式:正交投影和透视投影。

(一)正交投影

正交投影是一种平行投影,其中投影线与投影平面垂直。正交投影不会产生透视效果,适用于绘制等轴测图和2D图形。正交投影矩阵可以通过以下参数构建:

  • 左(left):视口的左边界。
  • 右(right):视口的右边界。
  • 底(bottom):视口的底边界。
  • 顶(top):视口的顶边界。
  • 近(near):视口的近平面。
  • 远(far):视口的远平面。

正交投影矩阵的形式如下:

Portho=[2rightleft00002topbottom00002farnear00001]

(二)透视投影

透视投影是一种模拟人眼视觉的投影方式,其中远离观察者的对象看起来更小。透视投影矩阵可以通过以下参数构建:

  • 视场角(fovy):垂直方向的视场角(以度为单位)。
  • 宽高比(aspect):视口的宽高比。
  • 近(near):视口的近平面。
  • 远(far):视口的远平面。

透视投影矩阵的形式如下:

Pperspective=[1aspect×tan(fovy2)00001tan(fovy2)0000far+nearfarnear2×far×nearfarnear0010]

四、完整的WebGL变换示例

以下是一个完整的WebGL示例,展示如何使用矩阵实现平移、旋转和缩放变换,并将对象从模型空间转换到视图空间和投影空间。

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebGL Transformation Example</title>
</head>
<body>
    <canvas id="webglCanvas" width="800" height="600"></canvas>
    <script>
        const canvas = document.getElementById('webglCanvas');
        const gl = canvas.getContext('webgl');

        if (!gl) {
            alert('Your browser does not support WebGL');
        }

        // 顶点着色器代码
        const vertexShaderSource = `
            attribute vec4 a_position;
            uniform mat4 u_modelViewProjectionMatrix;
            void main() {
                gl_Position = u_modelViewProjectionMatrix * a_position;
            }
        `;

        // 片元着色器代码
        const fragmentShaderSource = `
            precision mediump float;
            void main() {
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
            }
        `;

        // 创建顶点着色器
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vertexShaderSource);
        gl.compileShader(vertexShader);

        // 创建片元着色器
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, fragmentShaderSource);
        gl.compileShader(fragmentShader);

        // 创建程序
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program));
        }

        // 使用程序
        gl.useProgram(program);

        // 定义顶点数据
        const vertices = [
            -0.5, -0.5, 0.0,
             0.5, -0.5, 0.0,
             0.0,  0.5, 0.0
        ];

        // 创建顶点缓冲区对象
        const vertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

        // 获取attribute变量的位置
        const positionLocation = gl.getAttribLocation(program, 'a_position');

        // 绑定缓冲区到attribute变量
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

        // 获取uniform变量的位置
        const modelViewProjectionMatrixLocation = gl.getUniformLocation(program, 'u_modelViewProjectionMatrix');

        // 创建矩阵
        const modelMatrix = mat4.create();
        const viewMatrix = mat4.create();
        const projectionMatrix = mat4.create();
        const modelViewProjectionMatrix = mat4.create();

        // 设置模型矩阵(平移、旋转、缩放)
        mat4.translate(modelMatrix, modelMatrix, [0.0, 0.0, -2.0]); // 平移
        mat4.rotate(modelMatrix, modelMatrix, Math.PI / 4, [0, 0, 1]); // 旋转45度
        mat4.scale(modelMatrix, modelMatrix, [1.0, 1.0, 1.0]); // 缩放

        // 设置视图矩阵(观察者位置和方向)
        mat4.lookAt(viewMatrix, [0, 0, 5], [0, 0, 0], [0, 1, 0]);

        // 设置投影矩阵(透视投影)
        mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 100.0);

        // 计算模型视图投影矩阵
        mat4.multiply(modelViewProjectionMatrix, viewMatrix, modelMatrix);
        mat4.multiply(modelViewProjectionMatrix, projectionMatrix, modelViewProjectionMatrix);

        // 将矩阵传递给着色器
        gl.uniformMatrix4fv(modelViewProjectionMatrixLocation, false, modelViewProjectionMatrix);

        // 清空画布
        gl.clearColor(0.0, 0.0, 0.0, 1.0); // 设置背景颜色为黑色
        gl.clear(gl.COLOR_BUFFER_BIT);

        // 绘制三角形
        gl.drawArrays(gl.TRIANGLES, 0, 3);
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.3/gl-matrix-min.js"></script>
</body>
</html>

代码解析

  1. 顶点着色器:通过u_modelViewProjectionMatrix将顶点从模型空间转换到裁剪空间。
  2. 片元着色器:设置片元颜色为红色。
  3. 矩阵操作
    • 使用gl-matrix库创建和操作矩阵。
    • 设置模型矩阵(平移、旋转、缩放)。
    • 设置视图矩阵(观察者位置和方向)。
    • 设置投影矩阵(透视投影)。
    • 计算模型视图投影矩阵,并传递给着色器。
  4. 绘制命令:使用gl.drawArrays绘制三角形。

通过这些矩阵操作,可以实现对象的平移、旋转和缩放,并将其从模型空间转换到视图空间和投影空间。

Theme by threelab