Slide 1

Slide 1 text

WebGL in Ember Ember-SF Meetup January 24, 2017 https://speakerdeck.com/mtmckenna/webgl-in-ember [email protected] @mattmckenna

Slide 2

Slide 2 text

In This Talk • WebGL in general • Why Ember is helpful when developing WebGL apps • Simplest way to integrate Three.js into Ember • A few ways Ember can help you develop Three.js apps

Slide 3

Slide 3 text

What is WebGL? • WebGL is a web API that... • ... can draw 3D graphics on an HTML . • ... renders through graphics hardware (i.e. it renders fast). • Screenshot from A Journey Through Middle Earth

Slide 4

Slide 4 text

Let's Look at Something! • Impact of Diabetes

Slide 5

Slide 5 text

Ways to WebGL • Use the WebGL API directly • Use a 3D JavaScript library like Three.js, Babylon.js, or PlayCanvas • Use a native 3D engine that transpiles into JS (e.g. Unity or Unreal)

Slide 6

Slide 6 text

Using the WebGL API Directly Can Be Rough Because... • The API hasn't been designed for app developer speed. • It is easy to get stuck on math/graphics issues.

Slide 7

Slide 7 text

Using a Native Engine Can Be Rough Because... • There's a lot of overhead when running apps built with native engines (file size, etc.). • You know, it totally might be fine--I just have no experience with native 3D engines.

Slide 8

Slide 8 text

You Probably Want to Use a JavaScript Library • Three.js hits the sweet spot for developer productivity and customizability. • Look how small a starter app can be!

Slide 9

Slide 9 text

Creating a Cube in Three.js var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ); var renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); var geometry = new THREE.BoxGeometry( 1, 1, 1 ); var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); var cube = new THREE.Mesh( geometry, material ); scene.add( cube ); camera.position.z = 5; var animate = function () { requestAnimationFrame( animate ); cube.rotation.x += 0.1; cube.rotation.y += 0.1; renderer.render(scene, camera); }; animate();

Slide 10

Slide 10 text

Sample Cube!

Slide 11

Slide 11 text

• Production-level WebGL apps (like all production-level JavaScript apps!) have to deal with boring boilerplate like compiling assets, routing, persisting data, maintaining state, testing, and deploying. Sample Cube is Cool, but Production Apps are Harder: Part 1 of 2

Slide 12

Slide 12 text

Build a Game in Ember Starring Your Cat (Emberconf 2016) • Let Ember handle the boring parts: • Building full-screen, nested UIs • Testing • Deploying to web/mobile/desktop • Croissant the Pizza Cat

Slide 13

Slide 13 text

Create the Sample Cube in Ember $ npm install -g ember-cli $ ember new ember-threejs-cube $ cd ember-threejs-cube $ npm install --save-dev ember-browserify three $ ember g component sample-cube $ ember serve Install everything with ember-cli: {{sample-cube}} app/templates/application.hbs: import Ember from 'ember'; import THREE from 'npm:three'; export default Ember.Component.extend({ didInsertElement() { this._super(...arguments); } }); app/components/sample-cube.js:

Slide 14

Slide 14 text

Sample Cube in Ember!

Slide 15

Slide 15 text

• Production-level WebGL apps also have to deal with... • Interactivity • Async stuff (downloading things, component lifecycles) • Custom shaders Sample Cube is Cool, but Production Apps are Harder: Part 2 of 2

Slide 16

Slide 16 text

Three.js in Ember Demo App • Three.js in Ember • threejs-in-ember.netlify.com • GitHub

Slide 17

Slide 17 text

Interactivity • Using an Ember component's built in event handling is handy for making the scene interactive. • Ember's data-down-actions-up (DDAU) pattern still applies when updating properties of the scene.

Slide 18

Slide 18 text

Handling Events touchStart() { this.set('rotating', true); }, touchMove(event) { this.handleUserRotation(event); }, touchEnd() { this.set('rotating', false); }, app/components/three-container.js

Slide 19

Slide 19 text

Updating a Scene with DDAU • Data down, actions up! • Can use standard Ember/HTML UI controls to modify the Three.js scene. {{three-container scale=scale partyMode=partyMode shapeData=model}} app/templates/application.hbs: actions: { updateScale(event) { this.set('scale', event.target.value); } } app/controllers/application.js:

Slide 20

Slide 20 text

Async Stuff • ember-concurrency solves two problems in the demo app: • Removes the need to track the download state of textures (e.g. no need for an "isDownloading" flag, etc.) • Gracefully cancels the animation callback when the component has been destroyed

Slide 21

Slide 21 text

requestAnimationFrame without ember-concurrency animate() { this.rotateShape(); requestAnimationFrame(() => { this.animate(); }); } Blows up if the component is destroyed before the callback is called, which is bad: So you have to add a guard conditional, which is annoying: animate() { this.rotateShape(); requestAnimationFrame(() => { if (!this.isDestroyed) { this.animate(); } }); }

Slide 22

Slide 22 text

requestAnimationFrame with ember-concurrency animateTask: task(function * () { this.rotateShape(); requestAnimationFrame(() => { this.get('animateTask').perform(); }); }) Let ember-concurrency handle animation and stop worrying about the component lifecyle:

Slide 23

Slide 23 text

Shaders • Programs written in GLSL • Run directly on the GPU (graphics processing unit) • Responsible for figuring out what your 3D objects look like • Two types of shaders: vertex, fragment

Slide 24

Slide 24 text

Fragment Shaders • Fragment shaders determine the color of things on the screen. • Here's a fragment shader that makes things green: void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); }

Slide 25

Slide 25 text

Passing Shaders Around in JS is a Pain • Shaders are passed into WebGL as strings and later compiled, which is sort of logistically annoying. • What's the best way to configure your environment so passing the shader string into the above statement that doesn't feel super hacky? new THREE.ShaderMaterial({ vertexShader: vertexShaderString, fragmentShader: fragmentShaderString });

Slide 26

Slide 26 text

Formatting Shaders: Script Tags void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } var fragmentShaderString = document.getElementById('fragment-shader').innerText;

Slide 27

Slide 27 text

Formatting Shaders: Concatenated Strings var fragmentShaderString = 'varying vec3 vColor; \n' + 'void main() { \n' + ' gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); \n' + '}';

Slide 28

Slide 28 text

Formatting Shaders: Joined Array var fragmentShaderString = [ 'varying vec3 vColor; \n', 'void main() { \n', ' gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); \n', '}' ].join('');

Slide 29

Slide 29 text

Formatting Shaders: Imported Module export default ` void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } `; import fragmentShaderString from './fragment-shader';

Slide 30

Slide 30 text

Formatting Shaders: Importing via ember-stringify void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } import shaders from 'threejs-in-ember/ember-stringify'; var fragmentShaderString = shaders['fragment.glsl']; shaders/fragment.glsl: import the shader:

Slide 31

Slide 31 text

Party Mode

Slide 32

Slide 32 text

Recap • Combine Ember's best practices with 3D JS libraries like Three.js to have a great time creating 3D scenes for the web.

Slide 33

Slide 33 text

WebGL Resources • WebGL Fundamentals • WebGL Programming Guide (book) • MDN: Learn WebGL • MDN: Lighting In WebGL

Slide 34

Slide 34 text

Thank you! https://speakerdeck.com/mtmckenna/webgl-in-ember