使用 dom3d 在 CSS 和 JavaScript 中渲染 3D 图形(客座文章)

James Long 今天我们有一篇由 James Long(@jlongster)撰写的客座文章。

James 是 Mozilla.com 网站开发团队的技术负责人。James 对开放网络上的交互式图形充满热情。

今天他解释了如何在没有 3D 变换支持的情况下使用 CSS 创建 3D 对象。James,请开始吧。


最近我在玩弄 CSS3,我发现它可以让我进行基本的 3D 渲染,这让我觉得很有意思!这导致了 dom3d 的创建,这是一个使用 CSS 渲染基本 3D 对象的 JavaScript 库。

现在问题是:为什么?canvas、WebGL,甚至 SVG 不都是更好的技术吗?也许吧。但是,CSS 正在成为一种描述复杂效果和形状的强大语言,我们应该进行实验。

请记住这一点,因为 CSS 绝对不是为了做到这一点而设计的,但值得尝试一下,看看我们将来应该如何使用 CSS。

优点

虽然这更像是一个实验,但它确实有一些现实世界的益处

所有可用于网络的渲染库(canvas、WebGL、SVG)都需要一个 canvas,它是一个页面上的约束框,具有特定的宽度和高度。无法渲染此框外部的任何内容。canvas 还捕获所有 DOM 事件(如点击),即使是完全透明的部分。从理论上讲,这可能会让您难以执行覆盖页面大部分或以某种方式深度集成到内容中的效果。

使用 CSS,我们不会局限于一个框,而且效果可以覆盖页面的大部分,而不会覆盖任何需要交互的链接或其他内容。

其他优点包括无需初始化 canvas 2D 或 WebGL,以及简单的 API,即使您不太了解 3D,也很容易上手。对于孩子们来说,在学习 WebGL 或其他东西之前,先用它玩一玩可能更容易。此外,因为它只是一堆 DOM 元素,所以您可以将其嵌入到任何地方(没有动画)。

因此请记住,这确实是一个 hack,但它具有上述优点。这可能对某些效果很有用:3D 光标、导航转换等等。

工作原理

三维物体只是一堆三角形拼在一起,所以让我们从一个简单的三角形开始。如果我们能够使它工作,那么渲染多个三角形以形成一个 3D 物体就会变得很简单。

在二维屏幕上渲染一个 3D 三角形涉及一个叫做“投影”的东西。这是将一个 3D 点投影到一个 2D 屏幕上的过程。将一个 3D 三角形代入一个简单的数学方程,您会得到一个 2D 三角形,它代表 3D 三角形在屏幕上的样子。

这个数学公式非常简单,但如果您不熟悉线性代数,可能会觉得很奇怪。您可以 查看渲染器代码

现在是令人兴奋的部分:您可以简单地使用 CSS3 变换 来渲染任何 2D 三角形吗?事实证明,您可以!只需要花一些时间来确定要生成哪些变换即可。CSS3 变换由平移、缩放、旋转和倾斜值组成,我们需要一些方程来计算特定 2D 三角形的这些值。

首先,让我们取一个简单的 DOM 元素并将其变成一个三角形。我们可以使用 linear-gradient 背景图像来实现这一点(另一种方法是 边框三角形)。

JSFiddle 演示.

现在让我们使用点 [20, 20]、[50, 120] 和 [120, 30] 绘制以下蓝色三角形。一个重要的步骤是设置一些初始参考点,使所有内容都处于同一个空间中。我们的方程将假设这些坐标空间。这是点 A、B、C 和边 AB 之间的关系。

Triangle comparison

如果我们仔细看看它,我们可以推导出变换值。首先,了解我们需要哪些角度和值,然后使用几何形状来形成方程(以伪代码形式)。红色框代表 DOM 元素,形式 AB 代表点 A 和 B 形成的边,旋转方向为顺时针。

dissecting triangles

rotation = atan2(AB.x, AB.y)
AC' = rotate(AC, -rotation)
width = AC'.x
height = length(AB)
skew = atan2(AC'.y, AC'.x)
translate = A

太棒了!让我们试一试。以下是一个通过应用每个方程进行变换的实时 DOM 元素

JSFiddle 演示.

生成的三角形与我们的目标三角形匹配!以下最终 CSS 代码

width: 93px;
height: 104px;
background: -moz-linear-gradient(-0.727211rad, #0000FF 50%, transparent 0pt);
-moz-transform: translate(20px, 20px) rotate(-0.291457rad) skewY(0.391125rad);
-moz-transform-origin: top left;

注意tranform-origin: top left 行很重要。通常,变换是相对于元素的中心进行的,但我们的方程假设为左上角。

注意:dom3d 还生成带有 -webkit-o 前缀的代码,以支持 WebKit 和 Opera。

您可以查看 这些方程的实现。事实证明,这些方程适用于任何三角形,只要给定的点按逆时针顺序排列,这在图形世界中是标准的。

全部实现

既然我们可以将一个 3D 三角形投影到 2D 空间并使用 CSS 渲染它,那么我们现在要做的就是将其应用于多个 3D 三角形以形成一个 3D 对象!

我们需要一些 3D 数据。我使用 Blender 将一个茶壶导出为简单的 OBJ 文件格式,并 编写了一个脚本数据作为 JavaScript 导出。使用此技术渲染所有这些三角形会产生以下结果

JSFiddle 演示.

茶壶!但是,我们可以做得更好。3D 效果的一大特点是阴影。如果我们计算法线(一个表示三角形朝向的向量)并指定光线方向,我们可以计算每个三角形法线和光线的点积来获得 平面阴影。查看 平面阴影的代码

JSFiddle 演示.

有很多调整可以使它更进一步。例如,上面的对象启用了 z 索引。如果没有它,应该在另一个三角形后面的三角形实际上可能会出现在顶部,因为它是在后面渲染的。dom3d 使用一个 后到前 渲染三角形。

可以使用 setTimeout 或 requestAnimationFrame 函数来实现实时动画,该函数会不断渲染对象。dom3d 支持缩放、平移、偏航和俯仰变换,但您可以在渲染之间以您喜欢的任何方式修改对象数据。在 dom3d 网站 上查看一些示例。

以下代码使用 dom3d 渲染 茶壶动画

JSFiddle 演示.

对于网页来说,更适合响应用户交互来更新动画,而不是不断渲染并占用 CPU。请查看 dom3d 网站上的极点示例,了解一个示例。

改进和最后的想法

这里最有趣的可能性是将实际页面元素作为 3D 对象的一部分包括在内。导航项可能会弹出并绕着 3D 空间旋转,而导航项会与其无缝地一起变换。

不过,这正是这个 hack 开始暴露其缺陷的地方。不幸的是,这个 hack 有点过于粗糙,无法提供合适的网络体验。因为它将 DIV 欺骗成伪三角形,所以它消除了将任何页面元素与它集成的可能性。但是,随着 3D CSS 变换 的到来,我们可以开始构建由任何类型的页面元素组成的真正的 3D 对象。3D 变换的唯一限制是 3D 对象需要使用矩形而不是三角形来构建。

其他人已经开始尝试使用 3D 变换,例如 构建纯 CSS 3D 城市。还有一个很酷的库 Sprite3D,它提供了一个 JavaScript API 用于从页面元素构建基本 3D 对象。

dom3d 最明显的问题是对象中的接缝,这种接缝出现在所有浏览器中。显然,在渲染引擎强制执行其 CSS3 变换并使用线性梯度时,存在一些错误!

dom3d 库提供了一个 API 来处理所有这些内容,但它还没有得到很好的文档化。您可以随意浏览 README 和 github 上的代码。这些 API 也可以改进。它还提供了一个 SVG 渲染后端,此处可见,但我认为这不是正确的方向。我们应该专注于使用页面元素构建基本 3D 对象。

这是一个有趣的实验,我对浏览器变得多么快速和强大感到兴奋。网络是一个令人兴奋的平台,并且每年都在变得更加丰富和强大!

关于 Chris Heilmann

HTML5 和开放网络的传播者。让我们解决这个问题!

Chris Heilmann 的更多文章...


3 条评论

  1. Axel Hecht

    为什么 matrix() 只是可选的,而不是默认的?我认为如果您使用它,您就可以摆脱所有三角函数,这应该会更快。

    2011 年 8 月 8 日 下午 03:56

  2. Ben Francis

    这是一个有趣的技巧 :)

    我知道它与您想要实现的目标不同,但我想知道您是否看过 http://www.x3dom.org/

    X3D 是一种用于 Web 上的声明式 3D 图形的 ISO 标准,实际上在 HTML5 规范中引用了它,尽管目前还没有浏览器原生支持它。 http://web3d.org

    我认为,对于许多 Web 作者来说,X3D 作为一种抽象,可能比原始 WebGL 更容易,因为它具有熟悉的声明式语法。

    X3DOM 是一项实验,旨在让 X3D 更像 HTML,并成为 DOM 的一部分。

    2011 年 8 月 8 日 下午 06:04

  3. James Long

    @alex 我不确定如何在没有三角函数的情况下生成矩阵值。事实证明,使用转换函数实际上更快,因为我不必执行将转换转换为矩阵的额外步骤。

    2011 年 8 月 8 日 下午 07:30

本文的评论已关闭。