Svelte-Cubed: An Introduction to 3D in the Browser
Tags: svelte, threejs, beginners, tutorial
Published: 2022-01-27 02:18:13

Whatever you're making from data visualizations, or telling stories, being creative, selling products, 3D scenes open up new ways for people to engage with your content. (and it's also just really fun)

This article will walk through the basics of:

1. Setting up a scene
2. Adding lights and shapes
3. Incorporating the scene into a site

## Tools
We'll be using [svelte](https://svelte.dev/), [three.js](https://threejs.org/), and [svelte-cubed](https://svelte-cubed.vercel.app/). 

If you're unfamiliar with these tools and just want to make something that looks cool you can still follow along! And if you want to learn more, that's great too. Here are some helpful introductory resources you can come back to later (or banish into eternal browser-tab land):
- [Interactive official Svelte tutorial](https://svelte.dev/tutorial/basics)
- [Svelte for Beginners on YouTube](https://youtube.com/playlist?list=PL4cUxeGkcC9hlbrVO_2QFVqVPhlZmz7tO)
- [Interactive three.js tutorial](https://discoverthreejs.com/book/introduction/)

The best way to learn is to build, so let's go.

(full code is at the bottom if you run into any issues)

## Setting up a Scene: Canvas and Camera

Head on over to the Svelte REPL: https://svelte.dev/repl/

This is where we'll do all our development so you can easily share your scene and won't have to worry about setting anything up on your machine.

Start by importing our main tools in the script tag:
```html
<script>
import * as THREE from "three";
import * as SC from "svelte-cubed";

// all of our javascript and logic would go here

</script>
```
This enables us to use THREE and SC inside our App.svelte file. (And don't worry about bundle size; the Svelte compiler will optimize that for us)

Every 3D scene is projected onto a canvas, so let's create one. Under the script tags, add a svelte-cubed canvas:

```html
<SC.Canvas>

<!-- all of our scene stuff will go here! -->

</SC.Canvas>
```

Way to go! Now buy my book... just kidding. Let's continue.

We can't see anything because we have no camera. We'll go with the classic **perspective** camera (you can experiment with the **orthographic** camera at the end to see how it differs).

```html

<!-- CAMERA -->
<SC.PerspectiveCamera near={1} far={100} fov={55} />

```

wait wut? Okay, so we have to configure our camera a little bit to create our _view frustum_ aka what our camera can see. 

- **near:** anything closer than this will not be shown by the camera (unit: meters)
- **far:** anything farther than this will not be shown by the camera (unit: meters)
- **fov:** (Field of View) anything outside of this angle will not be shown by the camera (unit: degrees)

Let's see if it's working by creating a new color and setting it as our canvas background:

```html

<SC.Canvas background={new THREE.Color('seagreen')}>

  <!-- CAMERA -->
  <SC.PerspectiveCamera near={1} far={100} fov={55} />

<!-- all of our scene stuff will go here! -->

</SC.Canvas>

```

Success!

![A seagreen html canvas without any objects on it.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3m28xda92v18t6fc27j3.png)

## Adding Shapes

What regular people call shapes are called a "mesh" in 3D-land, so we'll use that word from now on. A mesh is a combination of a geometry and a material, just like your table: it has a geometry (e.g. rectangle or oval) and a material (e.g. wood or glass). 

Three.js offers many [geometries](https://threejs.org/docs/index.html?q=geometry) and [materials](https://threejs.org/docs/index.html?q=material) you can experiment with, and each variant can take several unique properties. For now let's add an octahedron... and together we'll learn what an octahedron is.

```html

<!-- MESHES -->
<SC.Mesh
  geometry={new THREE.OctahedronGeometry()}
  material={new THREE.MeshStandardMaterial({
    color: new THREE.Color('salmon')
  })}
/>

```
Notice that we passed a javascript object into our new material with a _color_ property. This is where you can handle all sorts of material enhancements, but we'll just use color for now.

And there it is! But it's not the color of salmon :( That's happening because our *MeshStandardMaterial* interacts with light and we don't have any lights! (You can experiment with a MeshBasicMaterial for something that does not interact with light to compare)

![A seagreen html canvas without an octahedron that is entirely black because there is no light.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i89nhe91aw16cgv710he.png)

## Adding Light

Unsurprisingly three.js offers many lighting options. Not sure how to incorporate this pun but: something, something, _the unbearable lightness of three-ing_. Now we're going to add two lights: a **directional** light and an **ambient** light, and you'll probably get the gist of what they do in the process.

```html

<SC.DirectionalLight
  color={new THREE.Color('white')}
  intensity={.75}
  position={[10, 10, 10]}
/>

```

![A seagreen html canvas with a dimly lit salmon-color octahedron.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rf532kmojwie1mc7gvu1.png)

Notice how the un-lit sides are really dark. Let's add the ambient light:

```html

  <SC.AmbientLight
    color={new THREE.Color('white')}
    intensity={.5}
  />

```

![A seagreen html canvas with a well lit salmon-color octahedron.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q2titm9s9frizntzi2wp.png)

Looking good! But still only looks 2D because we're looking at the mesh straight on.

We can move the mesh with its `rotation` or `position` properties, or we can move the camera with its `position` property, but to get a full range of interactions we'll add **orbit controls** *after* our camera.

```html

<!-- CAMERA -->
<SC.PerspectiveCamera near={1} far={100} fov={55} />

<SC.OrbitControls
  enabled
  enablePan
  enableZoom
  enableRotate
  enableDamping
/>

```

Now you can interact with your scene by clicking and dragging, scrolling to zoom, and right-click and drag to pan. 

![A camera rotating around a well lit salmon-color octahedron in front of a seagreen html canvas.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4jwc2vz812zr6o30iy5.gif)

COOL. But none of your friends know what an octahedron is, so you want to include this scene within the context of a regular website with some words. Svelte (and svelte-cubed) make this incredibly easy.

## Adding the Scene to Your Site

Let's move ALL our code into a new component. 
1. Copy all the code (script included) in `App.svelte` 
2. Click the + icon to create a new file and name it `Octo.svelte`
3. Paste all your code in `Octo.svelte`
4. Delete all the code in `App.svelte`

Now we can't see anything. `App.svelte` is the entry point for your application, and it's empty so the page is empty.

Let's add some markup in `App.svelte`:

```html
<h1>What is an Octahedron?</h1>

<div class="scene-container"></div>

<p>An octahedron is a three-dimensional shape having eight plane faces, especially a regular solid figure with eight equal triangular faces.</p>
```

We'll use the "scene-container" div to _contain_ our scene, and whatever dimensions we set on this container will control our scene dimensions too! Pretty neat.

Import your `Octo.svelte` component and nest it inside the "scene-container" div:

```html
<script>
  import Octo from "./Octo.svelte";
</script>
```
And drop it in the markup like an element:

```html
<div class="scene-container">
  <Octo />
</div>
```

Uh oh, it's taking over the whole page because we haven't styled the "scene-container". In `App.svelte` add the style tags under your markup:

```html
<style>
  .scene-container {
    /* position relative let's the canvas position itself relative to this container */
    position: relative;
    width: 75%;
    max-width: 400px
    height: 400px;
    margin: 0 auto;
  }
</style
```

And there you go! An informative geometry themed page with a responsive and interactive 3D scene. But you're just getting started. 

Add more shapes, change the camera, add `wireframe: true` to the mesh material properties. What if you change the mesh position to animate movement? What if you change the directional light position to change the angle of illumination? What about importing models and animating scenes?

If you enjoyed this and want to do more experiments, I built a little open source tool to help with some basics that will generate the svelte code for you to build on: https://sc3-lab.netlify.app/

The plan is to write a couple more of these for movement, transitions, and importing models. Until then, share what you built and be proud of it!


![A robot giving you a thumbs up.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mitfdmyot52bprha3sjj.gif)

## For Reference

### REPL
https://svelte.dev/repl/71b063fc410543598e8a727999cf7bbe

### App.svelte
```html
<script>
  import Octo from "./Octo.svelte";
</script>
<h1>What is an Octahedron?</h1>

<div class="scene-container">
  <Octo></Octo>
</div>

<p>An octahedron is a three-dimensional shape having eight plane faces, especially a regular solid figure with eight equal triangular faces.</p>

<style>
.scene-container {
/* position relative let's the canvas position itself relative to this container */
  position: relative; 
  width: 75%;
  max-width: 400px;
  height: 400px;
  margin: 0 auto;
}
</style>
```

### Octo.svelte
```html
<script>
import * as THREE from "three";
import * as SC from "svelte-cubed";
</script>

<SC.Canvas background={new THREE.Color('seagreen')}>
  
  <!-- LIGHTS -->
  <SC.AmbientLight
    color={new THREE.Color('white')}
    intensity={.5}
  />  
  <SC.DirectionalLight
    color={new THREE.Color('white')}
    intensity={.75}
    position={[10, 10, 10]}
  />

  <!-- MESHES -->
   <SC.Mesh
    geometry={new THREE.OctahedronGeometry()}
    material={new THREE.MeshStandardMaterial({
      color: new THREE.Color('salmon')
    })}
  />

  <!-- CAMERA -->
  <SC.PerspectiveCamera near={1} far={100} fov={55} />	    
  <SC.OrbitControls
    enabled
    enablePan
    enableZoom
    enableRotate
    enableDamping
  />

</SC.Canvas>

```