SOURCE

console 命令行工具 X clear

                    
>
console
window.onload = function () {
    /*获取元素*/
    var myCanvas = document.querySelector('#myCanvas');
    /*获取绘图工具*/
    var ctx = myCanvas.getContext('2d');

    /*
    1. 设置网格的大小,gridSize用于确定网格之中的线之间的间隔
    2. 获取Canvas的宽度width、高度height,用于计算x轴、y轴需要绘画的条数
    3. 采用遍历的方式,绘画x轴的线条
    4. 采用遍历的方式,绘画y轴的线条
    */

    // 1. 设置网格大小
    var girdSize = 60;
    // 2. 获取Canvas的width、height
    var CanvasWidth = ctx.canvas.width;
    var CanvasHeight = ctx.canvas.height;
    // 3. 采用遍历的方式,绘画x轴的线条
    var xLineTotals = Math.floor(CanvasHeight / girdSize); // 计算需要绘画的x轴条数
    // 4.采用遍历的方式,绘画y轴的线条
    var yLineTotals = Math.floor(CanvasWidth / girdSize); // 计算需要绘画y轴的条数

    const spacing = CanvasWidth / girdSize;

    const vertices = [];

    // 每个点的坐标
    for (let x = 0; x < girdSize; x++) {
        const row = [];
        for (let y = 0; y < girdSize; y++) {
            const z = Math.random() * 50;
            row.push([x * spacing, y * spacing, z]);
        }
        vertices.push(row);
    }

    for (let i = 0; i < girdSize - 1; i++) {
        for (let j = 0; j < girdSize - 1; j++) {
            // 每格四个点的坐标
            const v1 = vertices[i][j];
            const v2 = vertices[i + 1][j];
            const v3 = vertices[i + 1][j + 1];
            const v4 = vertices[i][j + 1];

            // 计算面法向量(将一个格子分解成两个三角型)
            const n1 = calculateNormal(v1, v2, v3);
            const n2 = calculateNormal(v1, v3, v4);

            // 计算平均法向量,相邻两个点之间的坐标
            const normal = [
                (n1[0] + n2[0]) / 2,
                (n1[1] + n2[1]) / 2,
                (n1[2] + n2[2]) / 2,
            ];

            // 计算强度(rgba颜色值)
            const intensity = calculateIntensity(normal);

            // 绘制面
            const color = `rgb(${intensity}, ${intensity}, ${intensity})`;
            // 填充颜色
            ctx.fillStyle = color;
            // 画出边线
            ctx.beginPath();
            ctx.moveTo(v1[0], v1[1]);
            ctx.lineTo(v2[0], v2[1]);
            ctx.lineTo(v3[0], v3[1]);
            ctx.lineTo(v4[0], v4[1]);
            ctx.closePath();
            ctx.fill();

            // 绘制法线
            const midpoint = [
                (v1[0] + v2[0] + v3[0] + v4[0]) / 4,
                (v1[1] + v2[1] + v3[1] + v4[1]) / 4,
                (v1[2] + v2[2] + v3[2] + v4[2]) / 4,
            ];
            const normalLineEnd = [
                midpoint[0] + normal[0] * 10,
                midpoint[1] + normal[1] * 10,
                midpoint[2] + normal[2] * 10,
            ];
            // drawLine(midpoint, normalLineEnd, 'rgb(255, 0, 0)');


        }
    }


    for (var i = 0; i < xLineTotals; i++) {
        ctx.beginPath(); // 开启路径,设置不同的样式
        ctx.moveTo(0, girdSize * i - 0.5); // -0.5是为了解决像素模糊问题
        ctx.lineTo(CanvasWidth, girdSize * i - 0.5);
        ctx.strokeStyle = "#ccc"; // 设置每个线条的颜色
        ctx.stroke();
    }


    for (var j = 0; j < yLineTotals; j++) {
        ctx.beginPath(); // 开启路径,设置不同的样式
        ctx.moveTo(girdSize * j, 0);
        ctx.lineTo(girdSize * j, CanvasHeight);
        ctx.strokeStyle = "#ccc"; // 设置每个线条的颜色
        ctx.stroke();
    }


}

// 标准化向量  计算向量的单位向量(即方向相同但长度为1的向量)
function normalizeVector(v) {
    const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    return [v[0] / length, v[1] / length, v[2] / length];
}
// 计算两个向量的叉积,即返回垂直于这两个向量所在平面的法向量
function crossProduct(v1, v2) {
    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 calculateNormal(v1, v2, v3) {
    // (三角形只要计算两个边向量即可)
    const vector1 = subtractVectors(v2, v1);
    const vector2 = subtractVectors(v3, v1);
    // 计算两个向量的叉积
    const normal = crossProduct(vector1, vector2);
    return normalizeVector(normal);
    // return    normal;
}

// 计算两个向量相减
function subtractVectors(a, b) {
    return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}

function calculateIntensity(normal) {
    const lightDirection = [0, 0, 1];
    normalizeVector(lightDirection);
    const intensity = dotProduct(normal, lightDirection) * 255;
    return intensity;
}

function dotProduct(v1, v2) {
    return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}

function drawLine(midpoint, normalLineEnd, rgb) {
    ctx.beginPath(); // 开启路径,设置不同的样式
    ctx.moveTo(girdSize * j, 0);
    ctx.lineTo(girdSize * j, CanvasHeight);
    ctx.strokeStyle = rgb; // 设置每个线条的颜色
    ctx.stroke();
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        canvas{
            border: 1px solid #cccccc;
            margin-top: 100px;
            margin-left: 100px;
        }
    </style>

</head>
<body>
    <canvas id="myCanvas" width="400" height="400"></canvas>
</body>
</html>