Introduction To 3D With Svelte
Published Oct 31, 2022
Table of Contents
- Introduction
- What Is Threlte?
- Learn Three.js
- Project Setup
- Creating Your First Scene
- Adding Helpers And GUI Elements For Values
- Importing 3D Models
- Conclusion
Introduction
By the end of this post youāre going to learn how to spice up your boring site using 3D with Svelte in the browser.
š§Ŗ The project files are available on GitHub and you can try it on StackBlitz.
What Is Threlte?
Three.js is a popular 3D library for JavaScript that abstracts having to write low-level graphics code yourself and lets you create anything from immersive 3D experiences to games in your browser.
Threlte is a component library for Svelte to build and render Three.js scenes declaratively.
šæļø You might have heard of other libraries such as Svelte Cubed and Svelthree but Threlte is most feature-complete and is even blessed by Rich Harris.
Using imperative code you have to specify each step to get to a desired outcome.
Take for example creating a <h1>
element using JavaScript.
const titleEl = document.createElement('h1')
titleEl.innerText = 'Hello'
document.body.append(titleEl)
Using declarative code you just state the desired outcome.
<h1>Hello</h1>
Learn Three.js
Three.js is powerful and might be intimidating if youāve never done 3D or game development before but itās worth learning as the knowledge you gain transfers to web development.
If you want to learn more about Three.js and 3D in general read Three.js fundamentals and you can use the Three.js editor to play around.
I want to emphasize that Threlte is a component library and isnāt trying to reimplent Three.js but sits on top of it.
The way you learn Three.js is from the Three.js documentation and then extend Threlte to do what you have to do.
Project Setup
Iām using a SvelteKit project with TypeScript (optional).
# initialize SvelteKit project
npm create svelte
# install dependencies
npm i
# start development server
npm run dev
Install Threlte and Three.js.
# dependencies
npm i @threlte/core @threlte/extras three
# TypeScript types
npm i -D @types/three
The next step is not that clear to me but from what I understand Vite ignores transforming dependencies when using SSR to speed things up but in this case you donāt want that.
import { sveltekit } from '@sveltejs/kit/vite'
import type { UserConfig } from 'vite'
const config: UserConfig = {
plugins: [sveltekit()],
ssr: {
noExternal: ['three', 'troika-three-text'],
},
}
export default config
Creating Your First Scene
Letās start by adding a camera, some lights and a mesh to your scene.
<script lang="ts">
import * as Threlte from '@threlte/core'
import * as Three from 'three'
import * as Utils from 'three/src/math/MathUtils'
</script>
<div class="scene">
<Threlte.Canvas>
<!-- Camera -->
<Threlte.PerspectiveCamera position={{ x: 20, y: 20, z: 20 }} fov={50}>
<!-- Controls -->
<Threlte.OrbitControls autoRotate />
</Threlte.PerspectiveCamera>
<!-- Lights the scene equally -->
<Threlte.AmbientLight color="white" intensity={0.2} />
<!-- Light that casts a shadow -->
<Threlte.DirectionalLight
color="white"
intensity={2}
position={{ x: 10, y: 10 }}
shadow={{
camera: { top: 8 },
}}
/>
<!-- Sphere -->
<Threlte.Mesh
geometry={new Three.SphereGeometry(4, 64, 64)}
material={new Three.MeshStandardMaterial({ color: 'white' })}
position={{ y: 4 }}
receiveShadow
castShadow
/>
<!-- Floor -->
<Threlte.Mesh
geometry={new Three.PlaneGeometry(20, 20)}
material={new Three.MeshStandardMaterial({
color: 'white',
side: Three.DoubleSide,
})}
rotation={{ x: Utils.DEG2RAD * 90 }}
receiveShadow
/>
</Threlte.Canvas>
</div>
<style>
.scene {
width: 100%;
height: 100%;
position: absolute;
inset: 0;
background: radial-gradient(hsl(220 14% 20%), hsl(220 20% 10%));
background-attachment: fixed;
}
</style>
Congrats on your first 3D scene in the browser! š„³
I namespaced the imports because I find itās easier to use <Threlte.Canvas>
than typing <Canvas>
and I know where itās from in the case of Three.SphereGeometry
instead of thinking about the import.
Letās break things down:
- The
<Canvas>
is where the magic happens and where things get rendered on the screen. - Iām using a
<PerspectiveCamera>
thatās slightly above the mesh with a set field of view - The
<AmbientLight>
is going to equally light your scene, think of it as cheap global illumination - The
<DirectionalLight>
is more like a sun in your scene that also casts a shadow (the shadow camera also has to be adjusted) - The sphere uses
<Threlte.Mesh>
where you can set the geometry and material for the mesh including casting shadows - The floor is almost the same as the sphere but uses
DoubleSide
, so itās visible from both sides and to rotate it by 90 degrees I useDEG2RAD
because it uses radians, soMath.PI / 2
also works
Adding Helpers And GUI Elements For Values
Right now I have no idea whatās going on in the 3D space and what direction anything is facing.
The changes you do are almost instant in the browser but tweaking feels like guesswork and I want to spend more time being creative.
You could use Blender to set up your scene and translate it but there has to be a better way to do it inside the browser first before you have to learn some sophisticated 3D graphics software (itās a lot of fun though).
Three.js provides a bunch of useful helpers and Iām going to use the GridHelper to see where things are positioned and the AxesHelper to show the X (red), Y (green) and Z (blue) axes to understand the direction of things.
This is an important lesson in translating Three.js to Threlte because youāre going to read the Three.js documenation or try something from a Three.js tutorial and want to know how to do the same thing in Threlte.
šæļø Threlte has a friendly Discord server if you ever need help.
If you look at the top of the Three.js documentation for the grid helper you can see it extends the Object3D
class and Threlte has a Object3DInstance
component just for that.
Letās add the helpers!
<script lang="ts">
// ...
const gridHelper = new Three.GridHelper(20, 10)
const axesHelper = new Three.AxesHelper(10)
</script>
<Threlte.Canvas>
<!-- ... -->
<Threlte.Object3DInstance object={gridHelper} />
<Threlte.Object3DInstance object={axesHelper} />
<!-- ... -->
</Threlte.Canvas>
Awesome! š
If you ever used a game engine youāre probably used to a GUI to control the values. Thatās what I want!
You could use a HTML <input />
in Svelte and bind the value but I donāt want to build a UI myself and thatās what why Iām going to use Tweakpane.
# dependencies
npm i tweakpane
# TypeScript types
npm i -D @tweakpane/core
Because SvelteKit runs the code on the server and client you need to check if youāre in the browser context.
<script lang="ts">
// ...
import { Pane } from 'tweakpane'
import { browser } from '$app/environment'
const sphere = {
position: { x: 0, y: 4, z: 0 },
}
if (browser) {
const pane = new Pane({ title: 'Scene' })
const sphereControls = pane.addFolder({ title: 'Sphere' })
sphereControls.addInput(sphere, 'position')
sphereControls.on('change', ({ value }) => {
sphere.position = value as any
})
}
</script>
<Threlte.Canvas>
<!-- ... -->
<Threlte.Mesh
geometry={new Three.SphereGeometry(4, 64, 64)}
material={new Three.MeshStandardMaterial({ color: 'white' })}
position={sphere.position}
receiveShadow
castShadow
/>
<!-- ... -->
</Threlte.Canvas>
This already feels so much better! š
As an exercise try adding a directional light helper and then add controls for it (this one is tricky because you need to pass the light as reference).
Importing 3D Models
You can get free 3D models from Sketchfab but make sure you check ādownloadableā to filter the results.
Thereās a lot of options for 3D file formats but you want GLB (GL Transmission Format Binary file) thatās more efficient for sharing 3D data on the web (GLTF is also fine but GLB keeps everything in one binary file).
šæļø Drag the downloaded 3D model into the Three.js editor to make sure it works because Sketchfab converts them for you from another format which can cause weird issues. If you notice a problem take the original 3D model and use Blender to export it.
Threlte makes it easy to import your 3D model using the <GLTF>
component and you can also control the exported animations with the useGltfAnimations
hook.
<script lang="ts">
import * as Three from 'three'
import * as Threlte from '@threlte/core'
import * as Extra from '@threlte/extras'
</script>
<Threlte.Canvas>
<!-- ... -->
<Extra.GLTF url="models/ghost.glb" />
<Extra.GLTF url="models/garden.glb" />
<!-- ... -->
</Threlte.Canvas>
Thatās it! š„³
If youāre interested watch the Halloween special video where I make a spooky 3D scene with Svelte and show you how to edit your 3D models using Blender and share some extra tips.
Conclusion
I hope this is just the start of your journey into 3D and I encourage you to learn some game development with Godot.
If youāre bad at math like me watch Math for Game Devs by @FreyaHolmer and youāre going to understand why learning math is useful for the first time in your life and expand your horizon.
Thank you for reading! šļø