Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

基于canvas生成3D立体画 #104

Open
imuncle opened this issue Jan 31, 2020 · 2 comments
Open

基于canvas生成3D立体画 #104

imuncle opened this issue Jan 31, 2020 · 2 comments
Labels
web web开发点滴

Comments

@imuncle
Copy link
Owner

imuncle commented Jan 31, 2020

3D立体画是裸眼3D的一种,最初是由一些极具创意的画家在街道或平地上创作的,后来又有了在纸上的绘制的作品。我的第一幅3D立体画是高二上画的:

image

然后上大学后许久没有画过了。

近期武汉新型冠状病毒肺炎疫情严重,只能呆在家,闲来无事想写一个能直接生成3D立体画的程序。

思路很简单,我们的目的是在相机的图像中将需要显示的物体放置在纸面上,还是放出这个世界坐标和图像坐标的关系:

image

首先定义一个相机:

// 模拟相机类
var Camera = function() {
    var arr = [[1000, 0, 400],
               [0, 1000, 300],
               [0, 0, 1]];
    this.intrinsic = new Matrix(arr);   // 相机内参
    var arr1 =  [[1, 0, 0, 0],
                [0, 1, 0, 0],
                [0, 0, 1, 0]];
    this.extrinsic = new Matrix(arr1);  //相机外参
    var arr2 = [[1, 0, 0],
                [0, 1, 0],
                [0, 0, 1]];
    this.rotate = new Matrix(arr2);     // 相机旋转矩阵
    this.camera_angle = 0;
    this.camera_distance = 100;
    this.camera_height = 100;
    this.camera_move = 0;
}

然后我还创建了一个矩阵相乘类:

// 简单的矩阵库,只支持矩阵乘法
var Matrix = function(matrix) {
    this.matrix = matrix;
    this.rows = this.matrix.length;     // 行数y
    this.cols = this.matrix[0].length;  // 列数x
}

// 矩阵相乘
Matrix.prototype.dot = function(matrix) {
    var self_matrix = this;
    var cols = matrix.cols;
    var rows = this.rows;
    var result = new Array(rows);
    for(var i = 0; i < rows; i++) {
        result[i] = new Array(cols);
    }
    for(var i = 0; i < cols; i++) {
        for(var j = 0; j < rows; j++) {
            var sum = 0;
            for(var k = 0; k < self_matrix.cols; k++) {
                sum += self_matrix.matrix[j][k] * matrix.matrix[k][i];
            }
            result[j][i] = sum;
        }
    }
    var new_matrix = new Matrix(result);
    return new_matrix;
}

最关键的是接下来这个paper类,我模拟了一个A4纸,A4四个角点的世界坐标是已知给定的,根据相机内参和外参计算出其在图像中的坐标,就可以显示A4纸了。

// 纸张以中心旋转
Paper.prototype.rotate = function() {
    var arr =  [[Math.cos(this.paper_angle), -Math.sin(this.paper_angle)],
                [Math.sin(this.paper_angle), Math.cos(this.paper_angle)]];
    var rotate = new Matrix(arr);
    var corners = [
        [[-105], [-148.5]],
        [[105], [-148.5]],
        [[105], [148.5]],
        [[-105], [148.5]]
    ];
    for(var i = 0; i < corners.length; i++) {
        var point = new Matrix(corners[i]);
        var result = rotate.dot(point);
        this.corners[i][0] = result.matrix[0][0] + 105;
        this.corners[i][1] = result.matrix[1][0] + 148.5;
    }
}

// 在canvas上显示纸张
Paper.prototype.show = function(context1) {
    this.rotate();
    var arr =  [[1, 0, 0, -this.height/2 + self.camera.camera_move],
                [0, 1, 0, self.camera.camera_height],
                [0, 0, 1, self.camera.camera_distance]];
    self.camera.extrinsic.set(arr);
    var arr1 = [[1, 0, 0],
                [0, Math.cos(self.camera.camera_angle), -Math.sin(self.camera.camera_angle)],
                [0, Math.sin(self.camera.camera_angle), Math.cos(self.camera.camera_angle)]];
    self.camera.rotate.set(arr1);
    self.camera.extrinsic = self.camera.rotate.dot(self.camera.extrinsic);
    context1.beginPath();
    var point = new Matrix([[this.corners[3][0]], [0], [this.corners[3][1]], [1]]);
    var result = self.camera.intrinsic.dot(self.camera.extrinsic).dot(point);
    context1.moveTo(result.matrix[0][0] / result.matrix[2][0], result.matrix[1][0] / result.matrix[2][0]);
    // 从左下角逆时针
    for(var i = 0; i < this.corners.length; i++) {
        point = new Matrix([[this.corners[i][0]], [0], [this.corners[i][1]], [1]]);
        result = self.camera.intrinsic.dot(self.camera.extrinsic).dot(point);
        context1.lineTo(result.matrix[0][0] / result.matrix[2][0], result.matrix[1][0] / result.matrix[2][0]);
    }
    context1.strokeStyle = 'red';
    context1.lineWidth = 1;
    context1.stroke();
}

最后是生成图像的代码,逻辑很简单,遍历A4中的每个像素点,投影到图像坐标系,获取到对应的像素值。

Generate.prototype.show = function(context1, context2) {
    var context1 = self.canvas1.getContext('2d');
    var context2 = self.canvas2.getContext('2d');
    context2.clearRect(0, 0, self.canvas2.width, self.canvas2.height);
    var imageData = context2.getImageData(0, 0, self.canvas2.width, self.canvas2.height);
    var img_data = imageData.data;
    for(var i = 0; i < self.paper.width; i++) {
        for(var j = 0; j < self.paper.height; j++) {
            var point = [j, i];
            point = self.paper.rotate_point(point);
            var temp_point = new Matrix([[point[0]], [0], [point[1]], [1]]);
            var result = self.camera.intrinsic.dot(self.camera.extrinsic).dot(temp_point);
            var pixel = context1.getImageData(result.matrix[0][0] / result.matrix[2][0], result.matrix[1][0] / result.matrix[2][0], 1, 1);
            var data = pixel.data;
            img_data[4 * (i + j * self.paper.width)] = data[0];
            img_data[4 * (i + j * self.paper.width) + 1] = data[1];
            img_data[4 * (i + j * self.paper.width) + 2] = data[2];
            img_data[4 * (i + j * self.paper.width) + 3] = data[3];
        }
    }
    context2.putImageData(imageData, 0, 0);
}

最后,我把代码上传到了github上:https://github.com/imuncle/3Ddraw

在线体验:https://imuncle.github.io/3Ddraw/

@imuncle imuncle added the web web开发点滴 label Jan 31, 2020
@XieSheng-qmaker
Copy link

强啊!以后就不需要手画了,直接打印出来

@FENYUN323
Copy link

老板牛批

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
web web开发点滴
Projects
None yet
Development

No branches or pull requests

3 participants