blog
blog copied to clipboard
【译】叶子——可互动的 Web 玩具
原文:Leaf Notes – An Interactive Web Toy
在浏览器上体验 https://tendril.ca/
我最近为多伦多的设计动画工作室 Tendril 推出了一个可互动的 Web 小玩具。你可以在其 官网首页 亲自体验。该网站会轮流展示数个不同的 Web 玩具,所以可能需要刷新一到两次才能看到它。
玩法非常简单:用鼠标划过植物就能使它们开花,并发出相应音调。
该项目十分有趣,我对目前结果也非常满意。Twitter 和 Instagram 上的热烈反应使我备受鼓舞,其中最让我暖心的是一位年仅四岁的小孩在平板上进行了体验。
本文将阐述我与优秀团队 Tendril 如何创造这个 Web 玩具,并讨论期间遇到的一些技术挑战。
概念
在前一段时间,Tendril 已在其官网推出了独具一格的交互动画(案例:1、2)。他们想让我创造一种全新的体验,且要体现生殖生长和程序化几何。
译者注: 生殖生长:当植物生长到一定时期以后,便开始分化形成花芽,以后开花、授粉、受精、结果(实),形成种子。——百度百科 程序化几何:通过程序生成的几何图形。
Tendril 先前的一个 Web 玩具
这个想法十分开放:为 Tendril 官网开发一个可互动的有趣玩具。它与已有的 Web 玩具共存,所以设计要适中、使用要简单、加载速度要快。总的来说:交互方式要轻易上手,整体体验要与 Tendril 的网站一致。
一个充满创作自由的想法对我来说可是一个挑战。在过去几个月里,我一直逼自己在开发前进行更多的搜索、头脑风暴、艺术指导和设计思考和构思。我发现铅笔和笔记本确实是最好的工具,不过像 Pinterest 和 Behance 这类平台则有助于管理参考文献和寻找灵感来源。
在讨论了几个不同想法后,我们选定了“与热带植物交互”的这个方向。
早期 情绪板
我早期的情绪板更倾向于单色而鲜明的视觉方向。这些信息反映出了项目在迭代开发中的变化区间。
💡相关说明:我希望有一个开源工具能将一组图片生成砌体结构风格的情绪板。虽然 InVision Boards 的用户体验很棒,但它是一个付费服务。
植物的生殖生长
早期程序化生化的植物几何体 Canvas2D 原型
最初,我使用 Canvas2D 的线来进行植物结构的原型设计。这无疑是快速验证想法和几何实现的好方法,因为这无需关心 WebGL 和 GPU 的复杂性。
我使用了简单的线段和二次贝塞尔曲线建立了植物的程序结构。二次贝塞尔曲线如下图所示,它由起点、控制点和终点构成。
使用简单的基本图形和参数函数(如线、曲线)能让事情变得更可控,如动画、GPU 的快速渲染、鼠标的碰撞检测、甚至是声音设计等。例如:定义变量 t,它是 [0, 1] 区间的数字,然后使用参数函数高效地计算出该值所代表的 2D 点。
结构
为每棵植物定义一个起点(如屏幕边界)和一个终点(如接近屏幕中点的某个位置)。然后,再放置一个稍微偏离两端点间中点的控制点,以形成一种弯曲植物茎的感觉。
为生成叶子,需按固定间隔遍历曲线,确定每个位置上的垂直法向量,并使用一些函数对法向量进行缩放 & 旋转操作,最终形成像“羽毛”一样的叶子。在最终案例中,我并未使用垂直法向量,而是使用了斜接的法向量(mitered normal)【译者注:mitered normal 翻译有误】。
我提取部分代码到以下 Canvas2D 案例中,你可以在 这里 查看/修改。点击以下案例可修改曲线结构。
学习到的错误及经验教训
在 2D 原型设计阶段,我犯了两个错误。在后续原型设计中应尽量避免:
- 维数:如果能将这种体验转化到三维空间就更好了,因为拥有深度和更好的互动效果。大多数算法均能转换到 三维空间上,但代码和数据结构是以二维空间作为假设前提而设计的。
- 单位:在最初 2D 原型制作时,使用了像素单位进行缩放和定位。这使得在适配屏幕分辨率时变得困难。如果在生成植物的代码中使用相对坐标会让上述情况变得更好处理,如 (0, 0) 代表屏幕左上位置,(1, 1) 代表屏幕右下位置,就像上面的 CodeSandbox 案例那样。
动画 & 交互
在几何植物的顶点上使用简单的弹簧效果,而不是使用复杂且 CPU 密集型的物理系统。这使得植物看起来有点像果冻,但不失为一个有趣好玩的互动。
对植物茎和叶子上的顶点,我都指定了 target
(即顶点应该弹向的目标位置)、position
(即顶点的实时位置)和 velocity
(速度和运动方向)。基础物理系统的伪代码如下:
// 1. 为速度 velocity 添加鼠标力
if (鼠标足够靠近顶点) {
velocity += mouseVelocity * mouseStrength;
}
// 2. 弹向目标位置
const delta = target - position;
velocity += delta * spring;
// 一直存在的且不变的“空气阻力”
velocity *= friction;
// 累加得出顶点位置
position += velocity;
以下是顶点弹簧效果的交互案例,它展示了如何让二次贝塞尔曲线与鼠标产生弹簧的效果。尝试一下案例吧,也可以 点击这里 阅读完整代码:
对于碰撞,我使用了 point within radius 模块判断顶点是否与鼠标发生碰撞。该碰撞检测的运算速度很快,但并非完美:叶子上存在部分“盲点”,即不会产于交互。为了在划动叶子时产生更精确的声音效果,我在小树叶上使用 point to line segment distance 进行碰撞检测。使用后者能产生更佳的互动体验,但在最终案例中,较大的鼠标半径和较多的植物数量使得难以发现两者差异。
渲染
尽管 Canvas2D 能很好地完成原型阶段的处理,但却不能胜任诸如逐像素着色的工作。
感谢 ThreeJs 及其 OrthographicCamera
,它们使得所有 canvas 代码迁移至 WebGL 变得不会太难。每根植物茎由一个 PlaneGeometry
(可复用)和一个自定义顶点着色器(vertex shader)组成。顶点着色器将平面几何段(plane segments)沿曲线(或线段,即植物茎或叶子)放置。
译者注:OrthographicCamera:正交相机,即镜头下所有东西均不会产生近大远小的透视效果,尺寸保持一致。
可通过我之前编写的笔记 《2D Quadratic Curves on the GPU》 了解更多该技术相关的知识点。通过该方法能生成每棵植物所需的曲线和线段。最终效果如下:
在顶点着色器中,我添加了参数函数,以实现沿 t 弧长变化的线宽。例如:thickness = sin(t * PI)
会压缩曲线的开始和结束部分。有了这些函数,平面几何的轮廓开始变得更像锥形叶子。
最后,添加颜色和外观细节——每片叶子在亮度、色相、饱和度、叶脉密度和旋转角度等方面均有了细微变化。所有这些计算均在片段着色器(fragment shader)完成。例如:每片叶子的叶脉和中线是基于纹理坐标计算得到的,并使用 fwidth()
计算出抗齿锯 2~3 像素的平滑曲线。
在开发期间,我使用 dat.gui 作为可视化滑块,并使用 surge.sh 与团队的其他成员共享迭代。这些工具使得我们能够尝试许多不同的想法和方向。而这种开发迭代的方式也让我们能想出一些有趣的特性:直到项目后期我们才引入了在黑色“手绘”状态下添加动画的想法(译者注:此句原文为:it wasn’t until later in the project that we introduced the idea of animating plants in from a black “hand-drawn” state)。
小细节
为了让项目更加生意盎然,我在小细节上花费了许多时间。实际上,叶子的核心结构和弹簧般的交互效果是最简单的部分,而大部分时间则花在了提升视觉效果、制作动画和修复各种跨浏览器问题上。
部分细节如下:
- 随机性在案例的几乎所有部分(即视觉上的细微变化、动效和音效)均有应用。例如,随机长度、曲率、密度、时间、风速、色调、线宽、亮度、音量等。在最终效果中,我使用固定的随机系数,以保证所有用户体验到一致的效果。
- 声音被节流(通过时间和最大同时播放数),从而避免破音和杂音。
- 声音的音量是基于鼠标的划动速率进行动态修改。鼠标的快速移动能产生更加戏剧性的声音效果。
- 根据鼠标的交互位置,声音会在往左/右声道靠拢,以呈现空间立体感。
- 多处性能优化:整个场景仅有一个着色器(译者注:顶点着色器和片段着色器组成一个着色器程序)和 3 种不同的几何形状;花费大量时间查看分析器(Profilers)和优化函数,直至能在所有浏览器和设备上流畅运行。屏幕像素密度、叶子密度、植物组织和其他变量均基于用户浏览器和分辨率进行适配。
最后的障碍
和一般交互式 Web 项目一样,项目的最后阶段通常需要小调整,以确保能在各类浏览器和设备上顺利运行。
对此,我使用了几个过去用于处理常见跨浏览器问题的模块,如用于统一鼠标和触摸事件的 touches 和兼容 iOS WebAudio 的 web-audio-player。
还有其他一些浏览器问题,以下是我处理的方案:
- 在 FireFox 和 MS Edge 中,当 JavaScript 有大量 CPU 运算时,
setTimeout
不能及时触发回调函数。因此,我认为它是不精确的,而且我也不可能为此等待 2~3 秒之久。于是选择 timeout-raf 修复它。 - 为不支持 WebAudio 的浏览器(如 Safari)进行 polyfill 兼容处理——stereo panner node。而对于声道有偏移问题的浏览器(如移动端 iOS safari)则采取在移动端禁用该效果的处理。
- Safari 同时还存在其他一些问题:我不得不控制最大同时播放数,以避免破音/咔嚓声;避免因浏览器偶尔中断 audio 上下文,而需在播放前调用
audioContext.resume()
。 - 与其他浏览器相比,JIT/JavaScript 引擎在 MS Edge 上表现太差。除了降低植物细节外,我无法修复该问题。
- iOS Safari 中嵌入 iFrame,有时会获取到不正确的
window.innerWidth
值。为了修复该问题,我最终为canvas
设置position:fixed
且宽高 100% 的样式。
作者
感谢 Tendril 团队,名单如下:
- Matt Jakob(创意总监)
- Niko Hook(声音设计)
- Sarah Arruda(出品人)
- Ahmed Wageh(创意开发者)
- John Szebegyinszki(监制)
本文和交互案例的源码均可在以下链接找到: