imuncle.github.io
imuncle.github.io copied to clipboard
基于canvas生成3D立体画
trafficstars
3D立体画是裸眼3D的一种,最初是由一些极具创意的画家在街道或平地上创作的,后来又有了在纸上的绘制的作品。我的第一幅3D立体画是高二上画的:

然后上大学后许久没有画过了。
近期武汉新型冠状病毒肺炎疫情严重,只能呆在家,闲来无事想写一个能直接生成3D立体画的程序。
思路很简单,我们的目的是在相机的图像中将需要显示的物体放置在纸面上,还是放出这个世界坐标和图像坐标的关系:

首先定义一个相机:
// 模拟相机类
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/
强啊!以后就不需要手画了,直接打印出来
老板牛批