JavaScript中的2D透视变换

3
寻找一个计算矩形在特定角度下(从90度的俯视角度)准确尺寸的代码示例。
例如,我有一个矩形是1200毫米x2000毫米
如果它的一边被倾斜了30度,我该如何确定矩形的新透视尺寸。(从90度的观察者角度,俯视)
因为从这个角度来看,底边看起来更短,即使其实际上并不是。(伪3D..但是在2D形状和2D视图中)
编辑:这是一个参考问题,请添加其他答案和解决方案。

您需要确定视点坐标,因为透视畸变取决于物体-视点距离。 - MBo
我正在发布一篇非常冗长的回答,很快就会上线。 - Orren Ravid
1个回答

6
您所描述的透视变换称为foreshortening,我建议不仅查看我的答案,还要查找更多相关信息,因为它是一个具有各种应用的概念,并且可以使用不同的方法解决。
请注意,Javascript不是我的强项,如果我犯了任何语法错误,请谅解并纠正我。
针对您特定的形状,简单使用相似三角形应该可以给出顶点坐标,然后您可以使用这些坐标来构造您的形状。您可以将顶点坐标定义为空间中的向量。
我将从在2D空间中定义您原始形状的4个顶点开始(左下角我将定义为原点)。在Javascript中,您应该定义一个由4个3值数组组成的2D数组。2D数组中的每个数组都将是形如[x,y,z]的顶点坐标。x值将是顶点的水平值,y值将是高度值,而z值将是其在3D空间中的距离。可以通过将坡道的斜边长(在本例中为2000)乘以坡道角度的余弦值来获得y值。为了进行余弦运算,我们必须将角度从度数转换为弧度。转换始终为pi = 180,因此30度的弧度为pi / 6。x值可以通过将斜边长乘以相同角度的正弦值来获得。
var hypotenuse = 2000;
var theta = Math.PI/6;
var x = Math.sin(theta)*hypotenuse;
var y = Math.cos(theta)*hypotenuse;
var z = 1200;
var vertices = [[0,y,0], [0,y,z], [x,0,0], [x,0,z]];

在xyz平面上的顶点坐标。

透视变换是由于观察者与物体之间的视角关系引起的。因此,为了得到实际的透视变换,我们需要定义观察者的位置。我将任意地将其定义为点(x/2,y+y/2,z/2)。

var viewer = [x/2,y+y/2,z/2];

观察者位置

然后我们选择一个焦距来定义图像的位置。焦距只是从观察者到图像的距离,因此它只被定义为单个点。我会任意地将焦距放在 (viewer_x,(y+y/2)/2,viewer_z) 这里,因为它是观察者下方 (y+y/2)/2 的距离。但是,您必须确保焦点与观察者具有相同的 x 和 z 坐标,并且只更改了 y 轴。 更多关于焦距和透视变换的信息可以在http://en.wikipedia.org/wiki/3D_projection找到。

var focal_length = [viewer[0],(y+y/2)/2,viewer[2]];

焦距坐标

1.我们先沿着y轴(应用透视变换的轴)获取矩形每个顶点到观察者的距离。 2.然后获取焦距的y坐标到观察者的距离。 3.接着获取焦距和顶点距离的比率。 4.最后将每个顶点的x和z坐标乘以该比率,得到它们的新的缩短坐标。

距离是通过减去y坐标并取结果的绝对值来获得的。为了获得更有效的代码,我会在一个for循环中完成这一切。

var focal_distance = Math.abs(viewer[1] - focal_length[1]); 
//gets distance between focal point y and viewer y
var vert_distance;
for(var i = 0; i< vertices.length-1; i++){
    vert_distance = Math.abs(viewer[1]-vertices[i][1]); 
    //access each individual vertex and and get the distance between vertex y and viewer y
   for (var j = 0; j<vertices[i].length-1; j++){
       vertices[i][j] = vertices[i][j]*(focal_distance/vert_distance);
       //gets the ratio between focal distance and vertex distance and multiplies each vertex by it
   }
}

最后,由于我们已经沿着y轴缩短了距离,所以每个顶点的y坐标不再必要,因此我们现在将每个顶点保存到一个新的2d顶点数组中,而不是3d顶点。

var vertices2d = [[0,0],[0,0],[0,0],[0,0]];
//creates a new 2d vertex array of 4 empty 2d vertex coordinates
for(var i = 0; i<vertices.length-1; i++){
    vertices2d[i][0] = vertices[i][0];
    //sets the x values of the 2d vertices = to the x values of 3d vertices
    vertices2d[i][1] = vertices[i][2];
    //sets the y values of the 2d vertices = to the z values of the 3d vertices
}

现在只需将这些2D顶点坐标放入任何绘图程序中,即可得到所需的结果。

无论如何,我会让程序打印出顶点坐标:

for (var i = 0; i<vertices2d.length-1; i++){
    document.write("(" + vertices2d[i][0] + "," + vertices2d[i][1] + ")");
}

随意调整查看器坐标、焦距y值、平面的长度和宽度以及斜坡角度,以获得不同的结果。祝福并欢迎提出任何问题,如果您不理解。

完整的代码如下:

var hypotenuse = 2000;
var theta = Math.PI/6;
var x = Math.Sin(theta)*hypotenuse;
var y = Math.Cos(theta)*hypotenuse;
var z = 1200;
var vertices = [[0,y,0], [0,y,z], [x,0,0], [x,0,z]];
var viewer = [x/2,y+y/2,z/2];
var focal_length = [viewer[0],(y+y/2)/2,viewer[2]];
var focal_distance = Math.abs(viewer[1] - focal_length[1]); 
//gets distance between focal point y and viewer y
var vert_distance;
for(var i = 0; i< vertices.length-1; i++){
    vert_distance = Math.abs(viewer[1]-vertices[i][1]); 
    //access each individual vertex and and get the distance between vertex y and viewer y
   for (var j = 0; j<vertices[i].length-1; j++){
       vertices[i][j] = vertices[i][j]*(focal_distance/vert_distance);
       //gets the ratio between focal distance and vertex distance and multiplies each vertex by it
   }
}
var vertices2d = [[0,0],[0,0],[0,0],[0,0]];
//creates a new 2d vertex array of 4 empty 2d vertex coordinates
for(var i = 0; i<vertices.length-1; i++){
    vertices2d[i][0] = vertices[i][0];
    //sets the x values of the 2d vertices = to the x values of 3d vertices
    vertices2d[i][1] = vertices[i][2];
    //sets the y values of the 2d vertices = to the z values of the 3d vertices
}
for (var i = 0; i<vertices2d.length-1; i++){
  document.write("(" + vertices2d[i][0] + "," + vertices2d[i][1] + ")");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接