使用 HTML5 Canvas 进行图像处理和纹理映射?

发布于 2024-10-13 08:01:51 字数 717 浏览 4 评论 0原文

在我正在开发的 3D 引擎中,我成功地绘制了 3D 立方体。就我而言,填充侧面的唯一方法是使用纯色或渐变。为了让事情变得更令人兴奋,我真的很想使用简单的位图来实现纹理映射。

关键是我几乎找不到任何关于 JavaScript 图像处理主题的文章或代码示例。此外,HTML5 canvas 中的图像支持似乎仅限于裁剪。

我怎样才能拉伸位图,以便矩形位图可以填充不规则的立方体面?在 2D 中,由于透视原因,投影的方形立方体面不是正方形,因此我必须拉伸它以使其适合任何四边形。

希望这张图片能澄清我的观点。左面现在填充有白/黑渐变。在纹理映射后,如何用位图填充它?

Cube

有人对使用 JavaScript 和 HTML5 Canvas 进行透视纹理映射(或图像操作)有任何建议吗?

编辑:我成功了,感谢 6502!

然而,它是相当 CPU 密集型的,所以我很想听听任何优化的想法。

使用 6502 技术的结果 - 使用纹理图像

In a 3D engine I'm working on I've succesfully managed to draw a cube in 3D. The only method to fill the sides is using either a solid color or gradient as far as I'm concerned. To make things more exciting, I'd really love to implement texture mapping using a simple bitmap.

The point is that I can hardly find any articles or code samples on the subject of image manipulation in JavaScript. Moreover, image support in HTML5 canvas seems to be restricted to cropping.

How could I go about stretching a bitmap so that a rectangular bitmap can fill up a unregular cube face? In 2D, a projected square cube face is, due to perspective, not of a square shape, so I'll have to stretch it to make it fit in any quadrilateral.

Hopefully this image clarifies my point. The left face is now filled up with a white/black gradient. How could I fill it with a bitmap, after it has been texture-mapped?

Cube

Does anyone have any tips on perspective texture mapping (or image manipulation at all) using JavaScript and HTML5 Canvas?

Edit: I got it working, thanks to 6502!

It is, however, rather CPU intensive so I'd love to hear any optimization ideas.

Result using 6502's technique - Texture image used

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

毅然前行 2024-10-20 08:01:51

我认为你永远不会得到准确的结果...我花了一些时间研究如何使用画布 2d 上下文来制作 3d 图形,我发现通过计算适当的 2d 梯度和矩阵来进行纹理映射 gouraud 着色是可行的:

  • 当然,实心多边形是简单的
  • Gouraud 填充只能在一个组件上进行(即,您不能拥有一个三角形,其中每个顶点都是用双线性插值填充的任意 RGB,但您可以使用例如单一颜色的三个任意阴影来进行填充)
  • 线性纹理映射可以是使用剪切和图像绘制完成后,

我将使用网格细分来实现透视正确的纹理映射(就像在 PS1 上一样)。

然而我发现了很多问题......例如,使用矩阵变换(纹理映射所需)进行图像绘制在 Chrome 上非常不准确,并且 IMO 不可能获得像素精确的结果;一般来说,在画布上绘图时无法关闭抗锯齿功能,这意味着在细分三角形时您将获得可见的透明线。我还发现多通道渲染在 chrome 上工作得非常糟糕(可能是因为硬件加速渲染的实现方式)。

一般来说,这种渲染肯定会给网络浏览器带来压力,而且显然这些用例(例如奇怪的矩阵)没有经过很好的测试。我什至让 Firefox 严重崩溃,以至于导致我的 Ubuntu 上的整个 X susb 系统瘫痪。

您可以在此处或通过视频这里... IMO 确实令人印象深刻的是,这可以在浏览器中完成,而无需使用 3D 扩展,但我不这样做不要认为当前的问题将来会得到解决。

无论如何,用于绘制图像以使 4 个角最终位于特定像素位置的基本思想是绘制两个三角形,每个三角形都将使用双线性插值。

在下面的代码中,我假设您有一个图片对象 texture 和 4 个角,每个角都是一个带有字段 x,y,u,v 的对象,其中 x, y 是目标画布上的像素坐标,u,v纹理上的像素坐标:

function textureMap(ctx, texture, pts) {
    var tris = [[0, 1, 2], [2, 3, 0]]; // Split in two triangles
    for (var t=0; t<2; t++) {
        var pp = tris[t];
        var x0 = pts[pp[0]].x, x1 = pts[pp[1]].x, x2 = pts[pp[2]].x;
        var y0 = pts[pp[0]].y, y1 = pts[pp[1]].y, y2 = pts[pp[2]].y;
        var u0 = pts[pp[0]].u, u1 = pts[pp[1]].u, u2 = pts[pp[2]].u;
        var v0 = pts[pp[0]].v, v1 = pts[pp[1]].v, v2 = pts[pp[2]].v;

        // Set clipping area so that only pixels inside the triangle will
        // be affected by the image drawing operation
        ctx.save(); ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1);
        ctx.lineTo(x2, y2); ctx.closePath(); ctx.clip();

        // Compute matrix transform
        var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2;
        var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2;
        var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2;
        var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2
                      - v0*u1*x2 - u0*x1*v2;
        var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2;
        var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2;
        var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2
                      - v0*u1*y2 - u0*y1*v2;

        // Draw the transformed image
        ctx.transform(delta_a/delta, delta_d/delta,
                      delta_b/delta, delta_e/delta,
                      delta_c/delta, delta_f/delta);
        ctx.drawImage(texture, 0, 0);
        ctx.restore();
    }
}

所有这些“delta”变量的那些丑陋的奇怪公式用于使用 Cramer 方法和 Sarrus 3x3 行列式方案。

更具体地说,我们正在寻找 ab、...f 的值,以便满足以下方程

a*u0 + b*v0 + c = x0
a*u1 + b*v1 + c = x1
a*u2 + b*v2 + c = x2

d*u0 + e*v0 + f = y0
d*u1 + e*v1 + f = y1
d*u2 + e*v2 + f = y2

delta< /code> 是矩阵的行列式

u0  v0  1
u1  v1  1
u2  v2  1

,例如,当您将第一列替换为 x0x1 时,delta_a 是同一矩阵的行列式>,x2。通过这些,您可以计算a = delta_a / delta

I think you will never get an accurate result... I spent some time investigating how to do 3d graphics using canvas 2d context and I found it viable to do texture mapping gouraud shading by computing appropriate 2d gradients and matrices:

  • Solid polygons are of course easy
  • Gouraud filling is possible only on one component (i.e. you cannot have a triangle where every vertex is an arbitrary RGB filled with bilinear interpolation, but you can do that filling using for example three arbitrary shades of a single color)
  • Linear texture mapping can be done using clipping and image drawing

I would implement perspective-correct texture mapping using mesh subdivision (like on PS1).

However I found many problems... for example image drawing with a matrix transform (needed for texture mapping) is quite inaccurate on chrome and IMO it's impossible to get a pixel-accurate result; in general there is no way to turn off antialiasing when drawing on a canvas and this means you will get visible see-through lines when subdividing in triangles. I also found multipass rendering working really bad on chrome (probably because of how hw-accellerated rendering is implemented).

In general this kind of rendering is surely a stress for web browsers and apparently these use cases (strange matrices for example) are not tested very well. I was even able to get Firefox crashing so bad that it took down the whole X susbsystem on my Ubuntu.

You can see the results of my efforts here or as a video here... IMO is surely impressing that this can be done in a browser without using 3D extensions, but I don't think current problems will be fixed in the future.

Anyway the basic idea used to draw an image so that the 4 corners ends up in specific pixels position is to draw two triangles, each of which will use bilinear interpolation.

In the following code I assume you have a picture object texture and 4 corners each of which is an object with fields x,y,u,v where x,y are pixel coordinates on the target canvas and u,v are pixel coordinates on texture:

function textureMap(ctx, texture, pts) {
    var tris = [[0, 1, 2], [2, 3, 0]]; // Split in two triangles
    for (var t=0; t<2; t++) {
        var pp = tris[t];
        var x0 = pts[pp[0]].x, x1 = pts[pp[1]].x, x2 = pts[pp[2]].x;
        var y0 = pts[pp[0]].y, y1 = pts[pp[1]].y, y2 = pts[pp[2]].y;
        var u0 = pts[pp[0]].u, u1 = pts[pp[1]].u, u2 = pts[pp[2]].u;
        var v0 = pts[pp[0]].v, v1 = pts[pp[1]].v, v2 = pts[pp[2]].v;

        // Set clipping area so that only pixels inside the triangle will
        // be affected by the image drawing operation
        ctx.save(); ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1);
        ctx.lineTo(x2, y2); ctx.closePath(); ctx.clip();

        // Compute matrix transform
        var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2;
        var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2;
        var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2;
        var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2
                      - v0*u1*x2 - u0*x1*v2;
        var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2;
        var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2;
        var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2
                      - v0*u1*y2 - u0*y1*v2;

        // Draw the transformed image
        ctx.transform(delta_a/delta, delta_d/delta,
                      delta_b/delta, delta_e/delta,
                      delta_c/delta, delta_f/delta);
        ctx.drawImage(texture, 0, 0);
        ctx.restore();
    }
}

Those ugly strange formulas for all those "delta" variables are used to solve two linear systems of three equations in three unknowns using Cramer's method and Sarrus scheme for 3x3 determinants.

More specifically we are looking for the values of a, b, ... f so that the following equations are satisfied

a*u0 + b*v0 + c = x0
a*u1 + b*v1 + c = x1
a*u2 + b*v2 + c = x2

d*u0 + e*v0 + f = y0
d*u1 + e*v1 + f = y1
d*u2 + e*v2 + f = y2

delta is the determinant of the matrix

u0  v0  1
u1  v1  1
u2  v2  1

and for example delta_a is the determinant of the same matrix when you replace the first column with x0, x1, x2. With these you can compute a = delta_a / delta.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文