Ура! Тръгнахме да създадем Доказателство за концепция за WebVR. Предишните ни публикации в блога завършиха симулацията, така че сега е време за малко творческа игра.
Това е невероятно вълнуващо време да бъдете дизайнер и разработчик, защото VR е промяна на парадигмата.
През 2007 г. Apple продаде първия iPhone, стартирайки революцията в потреблението на смартфони. Към 2012 г. бяхме добре в „уеб мобилния“ и „отзивчив“ уеб дизайн. През 2019 г. Facebook и Oculus пуснаха първите мобилни VR слушалки. Да го направим!
Интернет „първият за мобилни устройства“ не беше прищявка и предвиждам, че „първият за VR“ интернет няма да бъде. В предишните три статии и демонстрации демонстрирах технологичната възможност във вашия текущ браузър.
Ако вземате това в средата на поредицата, ние изграждаме симулация на небесна гравитация на въртящи се планети.
Стоейки върху работата, която сме свършили, е време за творческа игра. В последните две публикации ще изследваме платното и WebVR и потребителското изживяване.
Днес ще оживим нашата симулация. Поглеждайки назад, забелязах колко по-развълнуван и заинтересуван бях от завършването на проекта, след като започнах да работя върху визуализаторите. Визуализациите го направиха интересен за други хора.
Целта на тази симулация беше да изследва технологията, която ще даде възможност на WebVR - Виртуална реалност в браузъра - и предстоящите VR-първо уеб. Същите тези технологии могат да задвижат компютърните технологии в браузъра.
Завършвайки нашето доказателство за концепция, днес първо ще създадем визуализация на платно.
В последната публикация ще разгледаме дизайна на VR и ще направим версия на WebVR, за да накараме този проект да бъде „свършен“.
console.log()
Обратно към RR (Реална реалност). Нека създадем няколко визуализации за нашата симулация „n-body“, базирана на браузър. Използвал съм платно в уеб видео приложения в минали проекти, но никога като платно на художник. Нека видим какво можем да направим.
Ако си спомняте нашата архитектура на проекта, ние делегирахме визуализацията на nBodyVisualizer.js
.
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 (n²).
Хората са създадени да се грижат за хората и нещата, които могат да видят, така че 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 Simulation const sim = new nBodySimulator() // Add some visualizers sim.addVisualization( new nBodyVisPrettyPrint(document.getElementById('visPrettyPrint')) ) sim.addVisualization( new nBodyVisCanvas(document.getElementById('visCanvas')) ) …
Във версията на платното първият визуализатор е таблицата с бели числа, показани като 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)) } }
Този клас отпечатва в конзолата (на всеки 33ms!), А също така проследява и htmlElement - който ще използваме в подкласове, за да ги улесним за деклариране в main.js
Това е най-простото нещо, което би могло да работи.
Въпреки това, докато това console
визуализацията определено е проста, всъщност не „работи“. Конзолата на браузъра (и сърфирането на хора) не е проектирана да обработва регистрационни съобщения със скорост 33ms. Нека да намерим следващото най-просто нещо това може да работи.
Следващата итерация на „красив печат“ беше отпечатването на текст в HTML елемент. Това е и моделът, който използваме за изпълнението на платното.
Забележете, че запазваме препратка към htmlElement
визуализаторът ще рисува върху. Подобно на всичко останало в мрежата, той има мобилен дизайн. На работния плот това отпечатва таблицата с данни на обектите и техните координати вляво на страницата. На мобилни устройства това би довело до визуална бъркотия, така че го пропускаме.
/** * Pretty print simulation to an htmlElement's innerHTML */ export class nBodyVisPrettyPrint extends nBodyVisualizer { constructor(htmlElement) super(htmlElement) this.isMobile = /iPhone resize() {} paint(bodies) { if (this.isMobile) return let text = '' function pretty(number) { return number.toPrecision(2).padStart(10) } bodies.forEach( body => { text += `
${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 } }
Този визуализатор „поток от данни“ има две функции:
Сега, след като сме доста уверени в приноса си, нека поговорим за графики и платно.
„Game Engine“ е „Двигател за симулация“ с експлозии. И двете са невероятно сложни инструменти, тъй като се фокусират върху тръбопроводи за активи, зареждане на ниво стрийминг и всякакви невероятно скучни неща, които никога не бива да се забелязват.
Мрежата също така създаде свои „неща, които никога не бива да се забелязват“ с дизайн „първо за мобилни устройства“. Ако браузърът преоразмери, CSS на нашето платно ще преоразмери елемента на платното в DOM, така че нашият визуализатор трябва да се адаптира или да претърпи презрението на потребителите.
#visCanvas { margin: 0; padding: 0; background-color: #1F1F1F; overflow: hidden; width: 100vw; height: 100vh; }
Това изискване задвижва resize()
в nBodyVisualizer
базовия клас и реализацията на платното.
/** * 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 visualization resize() { 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
- размерите на зоната за рисуванеПреоразмеряването ни работи срещу изпълнението на платното по подразбиране. Ако визуализирахме графика на продукт или данни, бихме искали да:
В този по-често използван случай продуктът или графиката са в центъра на вниманието.
Нашата визуализация вместо това е театрална визуализация на необятността на космоса , драматизиран чрез хвърляне на десетки малки светове в празнотата за забавление.
Нашите небесни тела демонстрират това пространство чрез скромност - запазвайки се между 0 и 20 пиксела в ширина. Това преоразмеряване мащабира пространство между точките, за да създаде усещане за „научна“ просторност и повишава възприеманата скорост.
За да създадем усещане за мащаб между обекти с изключително различни маси, ние инициализираме тела с drawSize
пропорционално на масата:
// nBodySimulation.js export 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 // 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)) // 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 simulation sim.start()
Може да забележите двата „астероида“ отдолу. Тези обекти с нулева маса са хак, използван за „фиксиране“ на най-малкия прозорец на симулацията в зона 30x30, центрирана на 0,0.
Вече сме готови за нашата функция за боядисване. Облакът от тела може да се „клати“ далеч от началото (0,0,0), така че трябва да се изместим и в допълнение към мащаба.
Ние сме „готови“, когато симулацията има естествено усещане. Няма „правилен“ начин да го направите. За да подредя първоначалните позиции на планетата, просто се забърках с числата, докато се държат достатъчно дълго, за да бъде интересно.
// Paint on the canvas paint(bodies) // Because we draw the 3D space in 2D from the top, we ignore 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.y) ret.yMin = body.y if (ret.yMax body.z) ret.zMin = body.z if (ret.zMax Действителният код за рисуване на платно е само пет реда - всеки започва с this.vis
Останалата част от кода е на сцената захват .
Изкуството никога не е завършено, трябва да бъде изоставено
Когато изглежда, че клиентите харчат пари, които няма да им донесат пари, точно сега е подходящ момент да ги накарат. Инвестирането в изкуство е бизнес решение.
Клиентът за този проект (аз) реши да премине от внедряването на платното към WebVR. Исках крещяща демонстрация на WebVR, изпълнена със свръх. Така че нека да завършим това и да вземем малко от това!
С това, което научихме, бихме могли да приемем този проект за платно в различни посоки. Ако си спомняте от втория пост, ние правим няколко копия на данните на тялото в паметта:

Ако производителността е по-важна от сложността на дизайна, възможно е да предадете буфера на паметта на платното директно на WebAssembly. Това спестява няколко копия от паметта, което увеличава производителността:
- Прототип CanvasRenderingContext2D към AssemblyScript
- Оптимизиране на повикванията на функция CanvasRenderingContext2D с помощта на AssemblyScript
- OffscreenCanvas - Ускорете вашите операции на платното с уеб работник
Подобно на WebAssembly и AssemblyScript, тези проекти се справят с прекъсвания на съвместимостта, тъй като спецификациите предвиждат тези невероятни нови функции на браузъра.
Всички тези проекти - и всички отворени кодове, които използвах тук - изграждат основи за бъдещето на VR общите интернет общности. Виждаме ви и ви благодарим!
В последната публикация ще разгледаме някои важни дизайнерски разлики между създаването на VR сцена спрямо плоска уеб страница. И тъй като VR е нетривиална, ние ще изградим нашия въртящ се свят с рамка WebVR. Избрах A-Frame на Google, който също е изграден върху платно.
Измина дълъг път, за да стигна до началото на WebVR. Но тази поредица не беше за A-Frame здравей световна демонстрация . Написах тази поредица с вълнение, за да ви покажа основите на технологията на браузъра, които ще задвижват първите светове на VR, които идват в интернет.