HTML5透视网格

5

我正在尝试在我的画布上绘制透视网格,我从另一个网站更改了函数,得到了以下结果:

    function keystoneAndDisplayImage(ctx, img, x, y, pixelHeight, scalingFactor) {
    var h = img.height,
            w = img.width,

            numSlices = Math.abs(pixelHeight),

            sliceHeight = h / numSlices,

            polarity = (pixelHeight > 0) ? 1 : -1,
            heightScale = Math.abs(pixelHeight) / h,

            widthScale = (1 - scalingFactor) / numSlices;

    for(var n = 0; n < numSlices; n++) {

        var sy = sliceHeight * n,
                sx = 0,
                sHeight = sliceHeight,
                sWidth = w;

        var dy = y + (sliceHeight * n * heightScale * polarity),
                dx = x + ((w * widthScale * n) / 2),
                dHeight = sliceHeight * heightScale,
                dWidth = w * (1 - (widthScale * n));

        ctx.drawImage(img, sx, sy, sWidth, sHeight,
                dx, dy, dWidth, dHeight);
    }
}

它创建了一个几乎好的透视网格,但它没有按比例缩放高度,所以每个正方形都有相同的高度。这里是一个工作的jsFiddle和它应该看起来像什么,就在画布下面。我想不出任何数学公式来使高度按比例扭曲到“透视距离”(顶部)。希望你能理解。对于语言错误,我很抱歉。任何帮助将不胜感激。问候。

如果你想要一个透视正确的网格,那么就需要涉及到大量的三维数学计算。最重要的构造是矩阵。如果你不想自己构建三维引擎,可以使用现有的引擎,比如three.js。可以查看这个例子的源代码:http://threejs.org/examples/canvas_performance.html。 - bitWorking
我画布中唯一需要的3D元素就是那个网格。有没有不需要进行复杂3D数学计算的方法来实现它呢?我只需要添加一个东西,让每行水平线之间的距离发生变化 (类似这样)。 - VixinG
有什么想法吗?我什么都没找到。 - VixinG
使用图像作为背景有什么问题? - bitWorking
我正在学习,我喜欢知道如何做出完整的透视网格。 - VixinG
1个回答

14

很遗憾,除了使用三维方法外,没有其他合适的方式。但幸运的是,它并不那么复杂。

以下代码将生成一个可沿 X 轴旋转的网格(就像您的图片中一样),因此我们只需要关注该轴。

为了理解发生了什么:我们在笛卡尔坐标空间中定义网格。这是一个花哨的说法,意思是我们将点定义为向量而不是绝对坐标。也就是说,一个网格单元可以从 0,0 到 1,1,而不是例如从 10,20 到 45,45 这样的数字。

投影阶段,我们将这些笛卡尔坐标投影到屏幕坐标中。

结果将如下所示:

snapshot 3d grid

在线演示

好的,让我们深入了解一下 - 首先我们设置一些需要用于投影等的变量:

fov = 512,         /// Field of view kind of the lense, smaller values = spheric
viewDist = 22,     /// view distance, higher values = further away
w = ez.width / 2,  /// center of screen
h = ez.height / 2,
angle = -27,       /// grid angle
i, p1, p2,         /// counter and two points (corners)
grid = 10;         /// grid size in Cartesian

要调整网格,我们不调整循环(见下文),而是修改fovviewDist,并修改grid以增加或减少单元格的数量。

假设您想要更极端的视图-通过将fov设置为128并将viewDist设置为5,您将使用相同的gridangle获得此结果:

enter image description here

完成所有数学计算的“魔法”函数如下:

function rotateX(x, y) {

    var rd, ca, sa, ry, rz, f;

    rd = angle * Math.PI / 180; /// convert angle into radians
    ca = Math.cos(rd);
    sa = Math.sin(rd);

    ry = y * ca;   /// convert y value as we are rotating
    rz = y * sa;   /// only around x. Z will also change

    /// Project the new coords into screen coords
    f = fov / (viewDist + rz);
    x = x * f + w;
    y = ry * f + h;

    return [x, y];
}

就是这样。值得一提的是,正是新的Y和Z的组合使得在顶部(在这个角度)线条更小。

现在我们可以在笛卡尔空间中创建一个网格,然后直接将这些点旋转到屏幕坐标空间:

/// create vertical lines
for(i = -grid; i <= grid; i++) {
    p1 = rotateX(i, -grid);
    p2 = rotateX(i, grid);
    ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}

/// create horizontal lines
for(i = -grid; i <= grid; i++) {
    p1 = rotateX(-grid, i);
    p2 = rotateX(grid, i);
    ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}

同时要注意,位置0,0是屏幕中心。这就是为什么我们使用负值来从左侧或向上移出的原因。您可以看到两条中心线是直线。

就是这样了。要给单元格涂色,您只需选择笛卡尔坐标,然后调用rotateX()进行转换,您就会得到所需角落的坐标。

例如——随机选择一个单元格编号(在X和Y轴上均在-10到10之间):

c1 = rotateX(cx, cy);         /// upper left corner
c2 = rotateX(cx + 1, cy);     /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1);     /// bottom left corner

/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();

/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();

一个动画版本可以帮助看到发生了什么。


1
EasyCanvas非常强大,我看过文档了。我会用它并给予信用 :) 还有一个问题:脚本通过在两侧添加单元格来创建网格,这就是为什么它在网格中间按X轴旋转的原因。是否有任何方法可以以相同的方式旋转,但是不是在中间,而是在最后一行水平线上旋转?我现在正在尝试实现这一点。 - VixinG
1
@VixinG 当然可以。只需将网格“翻译”上移,即添加从-20到0的单元格,然后0将成为枢轴点或旋转的基础。如果您想在旋转后移动整个网格,只需向rotateX()产生的Y值添加一个值即可。 - user1693593
@Ken-AbdiasSoftware,是的,我已经自己翻译了。再次感谢,祝你有一个美好的一天! - VixinG
3
404链接到jsfiddle。 - rofrol

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