WebVR第4部分(画布数据可视化)

本文概述

  • 可能工作的最简单方法:console.log()
  • 用数据可视化仿真
  • 使用2D Canvas可视化仿真
  • 手工定制太阳能系统
  • 艺术从未完成, 必须被遗弃
欢呼!我们着手为WebVR创建概念证明。我们之前的博客文章完成了模拟, 因此现在该进行一些创造性的练习了。
这是成为设计师和开发人员的激动人心的时刻, 因为VR是一种范式转变。
2007年, 苹果销售了第一部iPhone, 掀开了智能手机消费革命。到2012年, 我们已经进入” 移动优先” 和” 响应式” 网页设计。在2019年, Facebook和Oculus发布了第一款移动VR头显。我们开工吧!
“ 移动优先” 互联网不是一种时尚, 我预测” VR优先” 互联网也不会。在前三篇文章和演示中, 我展示了当前浏览器中的技术可能性。
如果你是在本系列的中间进行讲解的话, 那么我们正在建立一个尖刺行星的天体重力模拟。
  • 第1部分:简介和体系结构
  • 第2部分:Web Workers为我们提供了其他浏览器线程
  • 第3部分:用于我们的O(n2)性能瓶颈代码的WebAssembly和AssemblyScript
站在我们已经完成的工作上, 是时候进行一些创意工作了。在最后两篇文章中, 我们将探讨canvas和WebVR以及用户体验。
  • 第4部分:Canvas数据可视化(本文)
  • 第5部分:WebVR数据可视化
今天, 我们将使仿真变得栩栩如生。回顾过去, 我发现一旦开始使用可视化工具, 我对完成该项目会感到更加兴奋和感兴趣。可视化使其他人感到有趣。
该模拟的目的是探索将使WebVR(浏览器中的虚拟现实)和即将面世的VR优先的Web成为可能的技术。这些相同的技术可以支持浏览器边缘计算。
为了完善概念验证, 今天我们首先创建一个画布可视化。
WebVR第4部分(画布数据可视化)

文章图片
Canvas Visualizer演示, 示例代码
在最后一篇文章中, 我们将研究VR设计并制作WebVR版本, 以使该项目” 完成” 。
WebVR第4部分(画布数据可视化)

文章图片
可能工作的最简单方法:console.log() 返回RR(现实)。让我们为基于浏览器的” n体” 模拟创建一些可视化效果。我在过去的项目中曾在网络视频应用程序中使用过画布, 但从未将其用作艺术家的画布。让我们看看我们能做什么。
如果你还记得我们的项目架构, 我们会将可视化委托给nBodyVisualizer.js。
WebVR第4部分(画布数据可视化)

文章图片
nBodySimulator.js具有一个模拟循环start(), 该循环调用其step()函数, 而step()的底部调用this.visualize()
// src/nBodySimulator.js/*** This is the simulation loop.*/async step() { // Skip calculation if worker not ready. Runs every 33ms (30fps). Will skip. if (this.ready()) {await this.calculateForces() } else {console.log(`Skipping calculation: ${this.workerReady} ${this.workerCalculating}`) } // Remove any "debris" that has traveled out of bounds // This keeps the button from creating uninteresting work. this.trimDebris() // Now Update forces. Reuse old forces if worker is already busy calculating. this.applyForces() // Now Visualize this.visualize()}

当我们按下绿色按钮时, 主线程将向系统添加10个随机主体。我们触摸了第一篇文章中的按钮代码, 你可以在此处的回购中看到它。这些机构非常适合测试概念验证, 但请记住, 我们处在危险的性能范围内-O(n2)。
人们的设计目的是关心他们所看到的人和物, 因此trimDebris()会移除看不见的物体, 从而不会减慢其余物体的速度。这是感知性能与实际性能之间的差异。
现在我们已经涵盖了除最终的this.visualize()之外的所有内容, 让我们来看一下!
// src/nBodySimulator.js/*** Loop through our visualizers and paint()*/visualize() { this.visualizations.forEach(vis => {vis.paint(this.objBodies) })}/*** Add a visualizer to our list*/addVisualization(vis) { this.visualizations.push(vis)}

这两个功能使我们可以添加多个可视化器。画布版本中有两个可视化器:
// src/main.js window.onload = function() {// Create a Simulationconst sim = new nBodySimulator()// Add some visualizerssim.addVisualization(new nBodyVisPrettyPrint(document.getElementById("visPrettyPrint")))sim.addVisualization(new nBodyVisCanvas(document.getElementById("visCanvas")))…

在画布版本中, 第一个可视化器是显示为HTML的白色数字表。第二个可视化器是下面的黑色画布元素。
WebVR第4部分(画布数据可视化)

文章图片
左侧的HTML可视化器是白色数字表。黑色画布可视化工具在下面
为此, 我从nBodyVisualizer.js中的一个简单基类开始:
// src/nBodyVisualizer.js/** * This is a toolkit of visualizers for our simulation. *//** * Base class that console.log()s the simulation state. */export class nBodyVisualizer {constructor(htmlElement) { this.htmlElement = htmlElement this.resize()}resize() {}paint(bodies) { console.log(JSON.stringify(bodies, null, 2))}}

此类会打印到控制台(每隔33毫秒!), 并跟踪一个htmlElement-我们将在子类中使用该元素, 以便在main.js中轻松声明它们。
这是可能可行的最简单的方法。
但是, 尽管此控制台可视化绝对简单, 但实际上并不” 起作用” 。浏览器控制台(和正在浏览的人)并非旨在以33ms的速度处理日志消息。让我们找到可能可行的下一个最简单的方法。
用数据可视化仿真 下一个” 漂亮打印” 迭代是将文本打印到HTML元素。这也是我们用于canvas实现的模式。
请注意, 我们正在保存对可视化工具将在其上绘制的htmlElement的引用。像网络上的所有其他内容一样, 它具有移动优先的设计。在桌面上, 这会在页面左侧打印对象的数据表及其坐标。在移动设备上会导致视觉混乱, 因此我们将其跳过。
/** * Pretty print simulation to an htmlElement's innerHTML */export class nBodyVisPrettyPrint extends nBodyVisualizer {constructor(htmlElement) { super(htmlElement) this.isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); }resize() {}paint(bodies) { if (this.isMobile) return let text = '' function pretty(number) {return number.toPrecision(2).padStart(10) } bodies.forEach( body => {text += `< br> ${body.name.padStart(12)} {x:${pretty(body.x)}y:${pretty(body.y)}z:${pretty(body.z)}mass:${pretty(body.mass)}) }` }) if (this.htmlElement) this.htmlElement.innerHTML = text}}

此” 数据流” 可视化器具有两个功能:
  1. 这是一种” 合理检查” 仿真器在可视化器中的输入的方法。这是一个” 调试” 窗口。
  2. 看起来很酷, 因此让我们将其保留在桌面演示中!
现在我们对输入内容非常有信心, 让我们来谈谈图形和画布。
使用2D Canvas可视化仿真 “ 游戏引擎” 是带有爆炸的” 模拟引擎” 。两者都是难以置信的复杂工具, 因为它们专注于资产管道, 流级别加载以及永远不应该被注意到的各种令人厌烦的东西。
网络还通过” 移动优先” 设计创建了自己的” 不应该引起注意的事物” 。如果浏览器调整大小, 我们画布的CSS将调整DOM中画布元素的大小, 因此我们的可视化工具必须适应或遭受用户的鄙视。
#visCanvas {margin: 0; padding: 0; background-color: #1F1F1F; overflow: hidden; width: 100vw; height: 100vh; }

此要求在nBodyVisualizer基类和canvas实现中驱动resize()。
/** * Draw simulation state to canvas */export class nBodyVisCanvas extends nBodyVisualizer {constructor(htmlElement) { super(htmlElement) // Listen for resize to scale our simulation window.onresize = this.resize.bind(this)} // If the window is resized, we need to resize our visualizationresize() { if (!this.htmlElement) return this.sizeX = this.htmlElement.offsetWidth this.sizeY = this.htmlElement.offsetHeight this.htmlElement.width = this.sizeX this.htmlElement.height = this.sizeY this.vis = this.htmlElement.getContext('2d')}

这导致我们的可视化器具有三个基本属性:
  • this.vis-可用于绘制图元
  • this.sizeX
  • this.sizeY-绘图区域的尺寸
Canvas 2D可视化设计说明
我们的调整大小针对默认的画布实现。如果我们要可视化产品或数据图, 则需要:
  1. 在画布上绘制(以首选的尺寸和纵横比)
  2. 然后让浏览器在页面布局期间将该图形调整为DOM元素的大小
在这种更常见的用例中, 产品或图形是体验的重点。
相反, 我们的可视化是对广阔空间的戏剧性可视化, 通过将数十个微小的世界扑向虚空以进行娱乐来进行戏剧化。
我们的天体通过谦虚展示了空间-将自身保持在0到20像素之间。调整大小可缩放点之间的空间, 以创建” 科学的” 宽敞感并增强感知的速度。
为了在质量大不相同的对象之间创建比例感, 我们使用与质量成比例的drawSize初始化物体:
// nBodySimulation.jsexport class Body {constructor(name, color, x, y, z, mass, vX, vY, vZ) { ... this.drawSize = Math.min(Math.max( Math.log10(mass), 1), 10)}}

手工定制太阳能系统 现在, 当我们在main.js中创建太阳系时, 将拥有实现可视化所需的所有工具:
// Set Z coords to 1 for best visualization in overhead 2D canvas // Making up stable universes is hard //namecolorx y z mvz vyvzsim.addBody(new Body("star", "yellow", 0, 0, 0, 1e9))sim.addBody(new Body("hot jupiter", "red", -1, -1, 0, 1e4, .24, -0.05, 0))sim.addBody(new Body("cold jupiter", "purple", 4, 4, -.1, 1e4, -.07, 0.04, 0)) // A couple far-out asteroids to pin the canvas visualization in place.sim.addBody(new Body("asteroid", "black", -15, -15, 0, 0))sim.addBody(new Body("asteroid", "black", 15, 15, 0, 0)) // Start simulationsim.start()

你可能会注意到底部的两个” 小行星” 。这些零质量的对象是用于将模拟的最小视口” 固定” 在以0, 0为中心的30× 30区域的hack。
现在我们准备好绘画功能。物体的云可以从原点(0, 0, 0)” 摆动” 开, 因此除了比例之外, 我们还必须移动。
当模拟具有自然感觉时, 我们就” 完成” 了。没有做到这一点的” 正确” 方法。为了排列行星的初始位置, 我只是弄弄数字, 直到它们保持足够长的时间以至于变得有趣。
// Paint on the canvaspaint(bodies) { if (!this.htmlElement) return // We need to convert our 3d float universe to a 2d pixel visualization // calculate shift and scale const bounds = this.bounds(bodies) const shiftX = bounds.xMin const shiftY = bounds.yMin const twoPie = 2 * Math.PI let scaleX = this.sizeX / (bounds.xMax - bounds.xMin) let scaleY = this.sizeY / (bounds.yMax - bounds.yMin) if (isNaN(scaleX) || !isFinite(scaleX) || scaleX < 15) scaleX = 15 if (isNaN(scaleY) || !isFinite(scaleY) || scaleY < 15) scaleY = 15 // Begin Draw this.vis.clearRect(0, 0, this.vis.canvas.width, this.vis.canvas.height)bodies.forEach((body, index) => {// Centerconst drawX = (body.x - shiftX) * scaleXconst drawY = (body.y - shiftY) * scaleY// Draw on canvasthis.vis.beginPath(); this.vis.arc(drawX, drawY, body.drawSize, 0, twoPie, false); this.vis.fillStyle = body.color || "#aaa"this.vis.fill(); }); } // Because we draw the 3D space in 2D from the top, we ignore zbounds(bodies) { const ret = { xMin: 0, xMax: 0, yMin: 0, yMax: 0, zMin: 0, zMax: 0 } bodies.forEach(body => {if (ret.xMin > body.x) ret.xMin = body.xif (ret.xMax < body.x) ret.xMax = body.xif (ret.yMin > body.y) ret.yMin = body.yif (ret.yMax < body.y) ret.yMax = body.yif (ret.zMin > body.z) ret.zMin = body.zif (ret.zMax < body.z) ret.zMax = body.z }) return ret }}

实际的画布绘制代码只有五行-每行以this.vis开头。其余的代码是场景的抓地力。
艺术从未完成, 必须被遗弃 如果客户似乎在花钱, 而这并不能赚钱, 那么现在正是提倡这笔钱的好时机。投资艺术品是一项商业决策。
这个项目(我)的客户决定从画布实现过渡到WebVR。我想要一个华丽的, 充满炒作的WebVR演示。因此, 让我们总结一下并得到其中的一部分!
根据我们所学的知识, 我们可以在各个方向进行这个画布项目。如果你还记得第二篇文章, 我们将在内存中复制身体数据的多个副本:
WebVR第4部分(画布数据可视化)

文章图片
如果性能比设计复杂度更重要, 则可以将画布的内存缓冲区直接传递给WebAssembly。这样可以节省几个内存副本, 从而增加了性能:
  • CanvasRenderingContext2D原型到AssemblyScript
  • 使用AssemblyScript优化CanvasRenderingContext2D函数调用
  • OffscreenCanvas —通过Web Worker加速你的Canvas操作
就像WebAssembly和AssemblyScript一样, 这些项目正在处理上游兼容性中断, 因为规范预见了这些令人惊叹的新浏览器功能。
所有这些项目以及我在此使用的所有开源都为VR优先的Internet共享的未来奠定了基础。我们见到你, 谢谢!
在最后一篇文章中, 我们将介绍在创建VR场景与平面网页之间的一些重要设计差异。而且由于VR并非无关紧要, 因此我们将使用WebVR框架来构建我们的世界。我选择了Google的A-Frame, 它也是基于画布构建的。
【WebVR第4部分(画布数据可视化)】开始WebVR的过程很漫长。但是本系列不是关于A-Frame的世界演示。我激动地写了这个系列文章, 向你展示了浏览器技术基础, 这些基础技术将为互联网的VR第一世界提供动力。

    推荐阅读