2、顶点着色器之视图矩阵

1、作用:将物体从世界坐标系转换到相机坐标系,相当于从世界坐标系转换到相机的局部(本地)坐标系。

2、基于LookAt函数的视图矩阵:

相机位置eye:(ex,ey,ez),世界坐标系下的位置

目标位置center:(cx,cy,cz),这是相机朝向的点,也是世界坐标系下的位置

上方向up:(ux,uy,uz),用于定义相机的上方向,一般都是世界坐标系的上方向,这样相机才是正对着物体而不是倾斜的

构建视图矩阵的步骤如下:

  1. 计算相机的方向向量(z轴方向,camera direction,相机坐标系的"视线方向"在世界坐标系中的表示):从相机位置到目标位置的反方向,用于定义新的Z轴
    z = e y e − c e n t e r ∣ e y e − c e n t e r ∣ \mathbf{z}=\frac{\mathbf{eye}-\mathbf{center}}{|\mathbf{eye}-\mathbf{center}|} z=∣eye−center∣eye−center
  2. 计算相机的右方向向量(x轴方向,camera right,相机坐标系的"右方向"在世界坐标系中的表示):通过向上方向和摄像机方向的叉积,计算出相机的右方向,用于定义新的x轴
    x = u p × z ∣ u p × z ∣ \mathbf{x}=\frac{\mathbf{up}×\mathbf{z}}{|\mathbf{up}×\mathbf{z}|} x=∣up×z∣up×z
  3. 计算相机的上方向向量(y轴方向,camera up,相机坐标系的"上方向"在世界坐标系中的表示):通过相机的Z轴和X轴的叉积,得到新的Y轴方向
    y = z × x \mathbf{y}=\mathbf{z}×\mathbf{x} y=z×x
  4. 构建视图矩阵:根据上面计算的向量,视图矩阵可以写成如下形式:
    V i e w M a t r i x = [ x x x y x z − x ⋅ e y e y x y y y z − y ⋅ e y e z x z y z z − z ⋅ e y e 0 0 0 1 ] \mathbf{ViewMatrix}=\begin{bmatrix} x_x & x_y & x_z & −\mathbf{x⋅eye}\\ y_x & y_y & y_z & −\mathbf{y⋅eye}\\ z_x & z_y & z_z & −\mathbf{z⋅eye}\\ 0&0&0&1\end{bmatrix} ViewMatrix= xxyxzx0xyyyzy0xzyzzz0−x⋅eye−y⋅eye−z⋅eye1
    3、示例代码:
javascript 复制代码
// matrix.js
const regPos = /^-?\d+(\.\d+)?$/; // 支持整数和浮点数,支持负号
function isVector3D(vector) {
  if (!Array.isArray(vector)) return false;
  if (vector.length != 3) return false;
  return (
    regPos.test(vector[0]) && regPos.test(vector[1]) && regPos.test(vector[2])
  );
}

function normalized(vector) {
  if (!isVector3D(vector)) return null;

  const vectorLength = Math.sqrt(
    Math.pow(vector[0], 2) + Math.pow(vector[1], 2) + Math.pow(vector[2], 2)
  );
  return vector.map((item) => {
    return item / vectorLength;
  });
}

function cross(v1, v2) {
  if (!isVector3D(v1)) return null;
  if (!isVector3D(v2)) return null;
  return [
    v1[1] * v2[2] - v1[2] * v2[1],
    v1[2] * v2[0] - v1[0] * v2[2],
    v1[0] * v2[1] - v1[1] * v2[0],
  ];
}

function dot(v1, v2) {
  if (!isVector3D(v1)) return null;
  if (!isVector3D(v2)) return null;
  return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}

function lookAt(eye, target, up = [0, 1, 0]) {
  if (!isVector3D(eye)) return null;
  if (!isVector3D(target)) return null;
  if (!isVector3D(up)) return null;

  const eyeMinusTarget = eye.map((item, index) => item - target[index]);
  const z = normalized(eyeMinusTarget); // Z轴
  const x = normalized(cross(up, z)); // X轴
  const y = cross(z, x); // Y轴

  // glsl中的mat4类型是列主序的,这里也要改为列主序
  return new Float32Array([
    x[0],
    y[0],
    z[0],
    0,
    x[1],
    y[1],
    z[1],
    0,
    x[2],
    y[2],
    z[2],
    0,
    -dot(x, eye),
    -dot(y, eye),
    -dot(z, eye),
    1,
  ]);
}

export { isVector3D, normalized, dot, cross, lookAt };
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL2 视图矩阵示例</title>
    <style>
        html,
        body {
            margin: 0;
            overflow: hidden;
        }

        canvas {
            position: fixed;
            top: 0;
            left: 0;
            outline: none;
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>
    <canvas id="webgl-canvas"></canvas>
    <div style="display: flex;position: fixed;left: 10px;top: 10px;">
        <button id="front">从正面看</button>
        <button id="back">从背面看</button>

    </div>
    <script type="module">
        import { lookAt } from './matrix.js'
        const canvas = document.getElementById("webgl-canvas");
        const gl = canvas.getContext("webgl2");

        if (!gl) {
            console.log("WebGL2 not supported, falling back on WebGL");
        }

        const vertexShaderSource = `#version 300 es
        in vec4 aPosition;
        uniform mat4 uViewMatrix;
        void main() {
            gl_Position = uViewMatrix * aPosition;
        }`;

        const fragmentShaderSource = `#version 300 es
        precision highp float;
        out vec4 outColor;
        void main() {
            outColor = vec4(1.0, 0.0, 0.0, 1.0);  // Red color
        }`;

        function createShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error("Shader compile failed:", gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        }

        function createProgram(gl, vertexShader, fragmentShader) {
            const program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                console.error("Program link failed:", gl.getProgramInfoLog(program));
                gl.deleteProgram(program);
                return null;
            }
            return program;
        }

        const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
        const program = createProgram(gl, vertexShader, fragmentShader);

        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        const size = 0.5;
        const positions = new Float32Array([
            -size, -size, size,
            size, -size, size,
            -size, size, size,
            size, -size, size,
            size, size, size,
            -size, size, size,
        ]);
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

        const vao = gl.createVertexArray();
        gl.bindVertexArray(vao);

        const aPosition = gl.getAttribLocation(program, "aPosition");
        gl.enableVertexAttribArray(aPosition);
        gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);

        gl.useProgram(program);

        // 根据设备的像素比率调整 canvas 尺寸,否则很模糊
        const pixelRatio = window.devicePixelRatio || 1;
        canvas.width = canvas.clientWidth * pixelRatio;
        canvas.height = canvas.clientHeight * pixelRatio;
        gl.viewport(0, 0, canvas.width, canvas.height);

        // 设置视图矩阵
        const uViewMatrix = gl.getUniformLocation(program, "uViewMatrix");
        let viewMatrix = new Float32Array([
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ])
        gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);

        document.getElementById("back").onclick = (e) => {
            // 相机在红色矩形的后面,由于启用了背面剔除,所以看不到
            viewMatrix = lookAt([0, 0, -1], [0, 0, 0], [0, 1, 0])
            gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);
            alert("相机在(0,0,-1)处看向(0,0,0)处,相机在红色矩形的后面,由于启用了背面剔除,所以看不到")
        }
        document.getElementById("front").onclick = (e) => {
            // 相机在红色矩形的前面,可以看到
            viewMatrix = lookAt([0, 0, 1], [0, 0, 0], [0, 1, 0])
            gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);
            alert("相机在(0,0,-1)处看向(0,0,0)处,相机在红色矩形的前面,所以可以看到")
        }

        function render() {
            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.enable(gl.DEPTH_TEST);       // 开启深度测试,防止面重叠
            gl.enable(gl.CULL_FACE);        // 开启背面剔除
            gl.cullFace(gl.BACK);           // 剔除背面

            gl.bindVertexArray(vao);
            gl.drawArrays(gl.TRIANGLES, 0, 6);

            requestAnimationFrame(render);
        }

        render();
    </script>
</body>

</html>
相关推荐
yeflx4 小时前
本质矩阵、基础矩阵和单应矩阵详解
线性代数·矩阵
王_teacher15 小时前
机器学习 矩阵求导 完整公式+严谨推导
人工智能·线性代数·考研·机器学习·矩阵·线性回归
做cv的小昊1 天前
【TJU】研究生应用统计学课程笔记(2)——第一章 数理统计的基本知识(1.3 统计中常用的分布族)
笔记·线性代数·数学建模·矩阵·概率论·学习方法·抽象代数
AIminminHu1 天前
OpenGL渲染与几何内核那点事-项目实践理论补充(一-3-(10):从“像素画师”到“硅基神明”:一个CAD开发者穿越GPU着色器管线的十年进化史)
着色器·片段着色器·顶点着色器·opengl 1.0·顶点/片段着色器
Pentane.1 天前
【力扣hot100】【Leetcode 54】螺旋矩阵|边界控制 算法笔记及打卡(19/100)
算法·leetcode·矩阵
mxwin1 天前
Unity URP 下 TBN 矩阵学习 切线空间、tangent.w 与镜像 UV 的那些坑
学习·unity·矩阵·shader
Dxy12393102161 天前
Elasticsearch 8 如何进行二维矩阵向量搜索
大数据·elasticsearch·矩阵
MediaTea1 天前
Scikit-learn:特征矩阵与目标变量
人工智能·python·机器学习·矩阵·scikit-learn
Jasmine_llq2 天前
《B4259 [GESP202503 二级] 等差矩阵》
线性代数·矩阵·顺序输入输出算法·双重循环遍历算法·行列乘积计算算法·矩阵逐行输出算法·空格分隔输出算法
代码改善世界2 天前
【MATLAB初阶】矩阵操作(二):矩阵的运算
android·matlab·矩阵