Rich-Harris / svelte-gl

Just an idea. For now.
191 stars 4 forks source link

svelte-gl

This is not a thing. I would like it to become a thing one day, if time allows, but for now I just want to get some ideas out of my head and into a place where they can be examined.

The idea is simple: a cross between Svelte and Three.js. In other words, a compiler that takes a scene graph (written in HTMLx) as input, and generates low-level WebGL code as output.

A-Frame is strong evidence that markup-driven approaches to describing 3D scenes are intuitive for authors, and practical from a performance standpoint. But A-Frame and Three.js, while truly excellent, are large frameworks (in terms of JavaScript bundle size) that are therefore unsuitable for casual or whimsical uses of WebGL.

My hypothesis is that we can make it much easier to embed 3D graphics on the web in a way that is potentially more performant than existing solutions (because compilers) at a fraction of the cost in terms of bytes, and insodoing unlock new forms of creativity.

It's entirely possible that I am completely wrong about this.

API ideas

<!-- every scene must have a camera, but one could
     be created automatically at a sensible location
     if not specified, a la A-Frame -->
<camera
  position={cameraPos}
  target="#my-box"
/>

<!-- position defaults to { x: 0, y: 0, z: 0 } -->
<box
  id="my-box"
  size={2}
  on:click="set({ color: 'blue' })"
  {color}
/>

<pointlight
  position="{{ x: 10, y: 10, x: 10 }}"
  target="#my-box"
/>
import Scene from './Scene.htmlx';

const scene = new Scene({
  canvas: document.querySelector('canvas'),
  data: {
    cameraPos: { x: 0, y: 0, z: -5 },
    color: red
  }
});

// later — trigger a re-render
scene.set({
  cameraPos: { x: -2, y: 4, z: -3 }
});

We could adopt large chunks of the Svelte component API, for handling local state, lifecycle hooks and the like.

Interop with Svelte components

It would be pretty great if we could do this sort of thing:

<h1>this is a svelte-gl component</h1>

<canvas>
  <MyScene {color}/>
</canvas>

<input type=color bind:color/>

<script>
   export default {
      components: {
         MyScene: './MyScene.gl.htmlx'
      }
   }
</script>

Implementation

Err, TODO. A couple of high level thoughts though.

Firstly, the job is easier in some respects than Svelte's DOM manipulation — rather than surgically updating parts of the document, in WebGL land we just re-render the entire world on any state change.

But the flip side is that it's probably not enough to just convert each element in the graph into a draw call; we probably need to do more work up-front to figure out there are no constraint violations (e.g. in the example above, we need to ensure that #my-box refers to an element that currently exists, and figure out its position so that it can be set as the target of the camera and the light. Though maybe it should be target={boxPos} anyway).

By far the easiest way to get started — and probably the most sensible, long-term — would be to add a new compiler target within Svelte itself. That way we wouldn't need to reimplement a bunch of stuff, and the two projects would stay in sync. The danger is that the experiment doesn't pan out, and Svelte is left with a vestigial appendage, so it would require buy-in from the Svelte community. The alternative would be to extract the different compiler targets out from Svelte into separate packages (@sveltejs/compile-dom, @sveltejs/compile-ssr, @sveltejs/compile-gl) that the core calls out to. I haven't looked into how feasible that would be.

Misc thoughts