作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Michael Cole
Verified Expert in Engineering

Michael是一位专业的全栈网络工程师, speaker, 拥有20多年经验和计算机科学学位的顾问.

PREVIOUSLY AT

Ernst & Young
Share

Hurray! 我们着手为WebVR创建一个概念验证. 我们之前的博文已经完成了模拟,所以现在是时候进行一些创造性的尝试了.

对于设计师和开发者来说,这是一个非常激动人心的时刻,因为VR是一种范式转变.

2007年,苹果公司销售了第一款iPhone,开启了智能手机消费革命. 到2012年,我们进入了“移动优先”和“响应式”网页设计. 2019年,Facebook和Oculus发布了首款移动VR头盔. Let’s do this!

“移动优先”的互联网不是一时的时尚,我预测“虚拟现实优先”的互联网也不会. 在前三篇文章和演示中,我演示了您的 current browser.

如果你在这个系列的中间捡到这个, 我们正在建立旋转行星的天体引力模拟.

根据我们所做的工作,是时候进行一些创造性的游戏了. 在最后两篇文章中,我们将探讨画布和WebVR以及用户体验.

  • Part 4: 画布数据可视化 (this post)
  • Part 5: WebVR数据可视化

今天,我们将把我们的模拟变为现实. Looking back, 我注意到,当我开始制作可视化工具时,我对完成这个项目是多么的兴奋和感兴趣. 可视化让其他人对它很感兴趣.

这次模拟的目的是探索WebVR(浏览器中的虚拟现实)及其未来的技术 VR-first web. 这些相同的技术可以为浏览器边缘计算提供动力.

完成我们的概念证明,今天我们将首先创建一个画布可视化.

帆布可视化

在最后一篇文章中,我们将着眼于VR设计,并制作一个WebVR版本来完成这个项目.”

WebVR数据可视化

最简单的方法: console.log()

回到RR(真实的现实). 让我们为基于浏览器的“n-body”模拟创建一些可视化效果. 在过去的项目中,我在网络视频应用程序中使用过画布,但从未作为艺术家的画布使用过. 看看我们能做些什么.

如果您还记得我们的项目架构,我们将可视化委托给 nBodyVisualizer.js.

将可视化委托给nBodyVisualizer.js

nBodySimulator.js 有一个模拟循环 start() that calls its step() 函数,和底 step() calls this.visualize()

/ / src / nBodySimulator.js

  /**

   *这是模拟循环.

   */

  async step() {

	//如果worker没有准备好,跳过计算. 每33ms (30fps)运行一次. Will skip.

	if (this.ready()) {

  		await this.calculateForces ()

	} else {

  	console.跳过计算:${this . log.workerReady} $ {.workerCalculating}”)

	}

	//移除任何已经移动出边界的“碎片”

	//这可以防止按钮创建无趣的工作.

	this.trimDebris()

	//现在更新力. 如果工人已经在忙于计算,可以重新使用旧的力量.

	this.applyForces()

	// Now Visualize

	this.visualize()

  }

当我们按下绿色按钮时,主线程会向系统中添加10个随机体. 我们触摸了按钮代码 first post,你可以在回购中看到它 here. 这些主体非常适合测试概念验证, 但记住,我们在危险的性能区域- O(n²).

人类天生就会关心他们能看到的人和事,所以 trimDebris() 移除飞出视线的物体,这样它们就不会减慢其他物体的速度. 这是感知性能和实际性能之间的差异.

现在除了期末考我们什么都讲了 this.visualize(),让我们一起来看看!

/ / src / nBodySimulator.js

  /**

   *循环通过我们的可视化器和paint()

   */

  visualize() {

	this.visualizations.forEach(vis => {

  		vis.paint(this.objBodies)

	})

  }

  /**

   *在我们的列表中添加一个可视化工具

   */

  addVisualization (vis) {

	this.visualizations.push(vis)

  }

这两个函数允许我们添加多个可视化器. 在canvas版本中有两个可视化器:

// src/main.js 

window.Onload = function() {

  //创建模拟

  const sim = new nBodySimulator()

  

  //添加一些可视化工具

  sim.addVisualization (

    新nBodyVisPrettyPrint(文档.getElementById(“visPrettyPrint”))

  )

  sim.addVisualization (

    新nBodyVisCanvas(文档.getElementById(“visCanvas”))

  )

  …

在画布版本中,第一个可视化工具是显示为HTML的白色数字表. 第二个可视化工具是下面的黑色画布元素.

油画专业

为了创建它,我从一个简单的基类开始 nBodyVisualizer.js:

/ / src / nBodyVisualizer.js

/**

 *这是我们模拟的可视化工具工具包.

 */

/**

 *控制台的基类.Log()是模拟状态.

 */

导出类nBodyVisualizer {

  构造函数(htmlElement) {

	this.htmlElement = htmlElement

	this.resize()

  }

  resize() {}

  paint(bodies) {

	console.log(JSON.Stringify (body, null, 2))

  }

}

该类打印到控制台(每33毫秒)!),并跟踪一个htmlElement——我们将在子类中使用它,使它们易于声明 main.js.

这是最简单的方法了.

然而,尽管如此 console 可视化绝对是简单的,它实际上并不“有效”.“浏览器控制台(和浏览的人)不是为以33ms的速度处理日志消息而设计的. Let’s find the 下一个最简单的事情 这是可行的.

用数据可视化模拟

下一个“漂亮打印”迭代是将文本打印到HTML元素. 这也是我们用于画布实现的模式.

注意,我们保存了对an的引用 htmlElement 可视化器将在上面作画. 和其他网站一样,它也有移动优先的设计. 在桌面上,这将在页面左侧打印对象的数据表及其坐标. 在手机上,它会导致视觉混乱,所以我们跳过它.

/**

 *漂亮的打印模拟一个htmlElement的innerHTML

 */

导出类nBodyVisPrettyPrint扩展nBodyVisualizer {

  构造函数(htmlElement) {

	超级(htmlElement)

	this.isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

  }

  resize() {}

  paint(bodies) {

    

	if (this.isMobile) return

	let text = ''

	函数pretty(number) {

  		return number.toPrecision(2).padStart(10)

	}

	bodies.forEach( body => {

  	text += `
${body.name.padStart(12)} {x:${pretty(body.x)} y: ${漂亮(身体.y)} z: ${漂亮(身体.z)}质量:${漂亮(身体.mass)}) }` }) if (this.htmlElement),这.htmlElement.innerHTML = text } }

这个“数据流”可视化工具有两个功能:

  1. 这是一种“完整性检查”模拟输入到可视化器的方法. 这是一个“调试”窗口.
  2. 它看起来很酷,所以我们把它保留在桌面演示中!

现在我们对我们的输入相当有信心,让我们谈谈图形和画布.

用2D画布可视化模拟

“游戏引擎”是带有爆炸的“模拟引擎”. 两者都是非常复杂的工具,因为它们专注于资产管道, 流级加载, 以及各种不应该被注意到的令人难以置信的无聊的东西.

网络也通过“移动优先”的设计创造了自己的“不应该被注意到的东西”. 如果浏览器调整大小, canvas的CSS将调整DOM中canvas元素的大小, 因此,我们的可视化工具必须适应或忍受用户的蔑视.

#visCanvas {

  	margin: 0;

  	padding: 0;

  	background - color: # 1 f1f1f;

  	溢出:隐藏;

  	width: 100vw;

  	height: 100vh;

}

这个需求驱动 resize() in the nBodyVisualizer 基类和画布实现.

/**

 *绘制模拟状态画布

 */

导出类nBodyVisCanvas扩展nBodyVisualizer {

  构造函数(htmlElement) {

	超级(htmlElement)

	//监听resize来缩放模拟

	window.onresize = this.resize.bind(this)

  }

	//如果窗口大小被调整,我们需要调整我们的可视化

  resize() {

	if (!this.htmlElement),返回

	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可视化设计说明

我们的大小调整工作 against 默认画布实现. 如果我们正在可视化产品或数据图表,我们希望:

  1. 绘制到画布上(以首选的大小和宽高比)
  2. 然后让浏览器在页面布局期间调整绘制到DOM元素的大小

在这个更常见的用例中,产品或图形是体验的焦点.

我们的视觉化是一种戏剧性的视觉化 浩瀚的太空,将几十个小世界扔进虚空中取乐.

我们的天体通过谦虚来展示空间——保持自己在0到20像素之间的宽度. 的大小调整 space 在点之间创造一种“科学”的空间感,并提高感知速度.

为了在质量差别很大的物体之间创建一种比例感,我们用a初始化物体 drawSize 与质量成正比的:

/ / nBodySimulation.js

导出类Body {

  构造函数(name, color, x, y, z, mass, vX, vY, vZ) {

	...

	this.drawSize = Math.min(   Math.max( Math.Log10(质量),1),10)

  }

}

手工制作定制太阳能系统

现在,当我们在 main.js,我们将拥有可视化所需的所有工具:

	//设置Z轴为1,以获得最佳的可视化效果

	//创造稳定的宇宙是困难的

	//   name        	color 	x	y	z	m  	vz	vy   vz

  sim.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))

	//一对遥远的小行星固定画布的可视化位置.

  sim.addBody(新机构(“小行星”, 	“黑色”,-15,-15,0,0))  

  sim.addBody(新机构(“小行星”, 	"black", 15,15,0,0))

	//启动模拟  

  sim.start()

你可能会注意到底部的两个“小行星”. 这些零质量对象是用来将模拟的最小视口“固定”到以0为中心的30x30区域的方法,0.

现在我们已经为paint函数做好了准备. 天体云可以从原点(0,0,0), 所以除了规模,我们还必须改变.

当模拟具有自然感觉时,我们就“完成”了. 没有“正确”的方法. 来安排行星的初始位置, 我只是胡乱摆弄这些数字,直到它们长到足够有趣为止.

	//在画布上绘制

paint(bodies) {

	if (!this.htmlElement),返回

	//我们需要将我们的3d浮动宇宙转换为2d像素可视化

	//计算移位和缩放

	Const bounds = this.bounds(bodies)

	const shiftX = bounds.xMin

	const shiftY = bounds.yMin

	const twoPie = 2 * Math.PI

    

	让scaleX =这个.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) => {

  	// Center

  		const drawX = (body.x - shiftX) * scaleX

  		const drawY = (body.y - shiftY) * scaleY

  	//在画布上绘制

  		this.vis.beginPath();

  		this.vis.圆弧(drawX, drawY, body.drawSize, 0, twoPie, false);

  		this.vis.fillStyle = body.color || "#aaa"

  		this.vis.fill();

	});

  }

	//因为我们从顶部将3D空间绘制成2D,所以我们忽略了z

bounds(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.x
            
		if (ret.xMax < body.x) ret.xMax = body.x
            
		if (ret.yMin > body.y) ret.yMin = body.y
            
		if (ret.yMax < body.y) ret.yMax = body.y
            
		if (ret.zMin > body.z) ret.zMin = body.z
            
		if (ret.zMax < body.z) ret.zMax = body.z
	})
	return ret
  }

}

实际的画布绘制代码只有五行——每一行以 this.vis. 剩下的代码是场景的 grip.

艺术永远不会结束,它必须被抛弃

当客户似乎在花钱时,他们不会赚钱, 现在是提出来的好时机. 投资艺术是一项商业决策.

这个项目的客户端(我)决定从画布实现转向WebVR. 我想要一个华丽的WebVR演示. 我们来总结一下吧!

有了我们所学到的,我们可以把这个画布项目带向不同的方向. 如果你还记得第二篇文章,我们正在内存中制作身体数据的几个副本:

内存中主体数据的副本

如果性能比设计复杂性更重要, 可以直接将画布的内存缓冲区传递给WebAssembly. 这节省了几个内存副本,从而提高了性能:

就像WebAssembly和AssemblyScript一样, 这些项目正在处理上游兼容性问题,因为规范设想了这些令人惊叹的新浏览器功能.

所有这些项目——以及我在这里使用的所有开源——都在为虚拟现实优先的互联网公共资源的未来奠定基础. 再见,谢谢大家!

在最后一篇文章中,我们将看看创建VR场景与虚拟现实场景之间的一些重要设计差异. a flat-web page. 因为VR是非常重要的,所以我们将使用WebVR框架来构建我们的旋转世界. 我选择了谷歌的A-Frame,它也是建立在画布上的.

WebVR的诞生经历了一段漫长的旅程. 但是这个系列不是关于 A-Frame hello world演示. 我兴奋地写了这个系列,向您展示将推动互联网vr世界到来的浏览器技术基础.

聘请Toptal这方面的专家.
Hire Now
Michael Cole

Michael Cole

Verified Expert in Engineering

达拉斯,美国

2014年9月10日成为会员

About the author

Michael是一位专业的全栈网络工程师, speaker, 拥有20多年经验和计算机科学学位的顾问.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

PREVIOUSLY AT

Ernst & Young

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

Toptal开发者

Join the Toptal® community.