ThreeJS - Snippets
Links
ThreeJS
Helfer
Axes Helper
Koordinatenachsen anzeigen
const axesHelper = new THREE.AxesHelper( 5 ); scene.add( axesHelper );
Viewport Settings
Handle Viewport Resizing
window.addEventListener('resize', () =>{
console.log('window resized')
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width/sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width,sizes.height)
renderer.setPixelRatio( Math.min(window.devicePixelRatio, 2) ) // in case monitor changed in double monitor settings
})
Handle Pixel Ratio Setting (Retina Displays)
Retina Displays haben eine Pixel Ratio von 2. D.h. das Display kann einen "Software"Bildpixel nochmal auf 4 physische Pixel verteilen und damit vor allem Vektoren nochmal schärfer darstellen. ThreeJS kann diese zusätzlichen Pixel ebenfalls nutzen wenn man dem renderer die Pixel Ratio mitgibt. Allerdings muss der Renderer auch mehr tun.
Moderne Handys haben Ratios bis zu 5, das ist allerdings sinnlos mehr als 2 oder 3 sehen wir bei normalem Betrachtungsabstand eh nicht. Deshalb setzen wir wenn möglich einen Ratio so hoch wie das Gerät kann aber nicht höher als 2 um die Performance zu erhalten.
renderer.setPixelRatio( Math.min(window.devicePixelRatio, 2) )
Handle Fullscreen Mode
// Handle Fullscreen
// including safari (needs webkit prefix)
window.addEventListener('dblclick', () =>
{
const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement
if(!fullscreenElement)
{
if(canvas.requestFullscreen)
{
canvas.requestFullscreen()
}
else if(canvas.webkitRequestFullscreen)
{
canvas.webkitRequestFullscreen()
}
}
else
{
if(document.exitFullscreen)
{
document.exitFullscreen()
}
else if(document.webkitExitFullscreen)
{
document.webkitExitFullscreen()
}
}
})
Animation Basics
Timebased Tick / Loop Function
Für Animationen können wir in einem Loop die Szene Rendern, Objekte verändern, Szene erneut Rendern usw. In JavaScript kann man dazu die window.requestAnimationFrame Funktion nutzen. Damit die zeitlichen Abläufe nicht von der Rechnerleistung sondern rein von der Zeit abhängen gibt es einige Möglichkeiten diesen Loop umzusetzen.
Die Beispiele setzen eine Setup mit einem Camera Object 'camera', einer Szene scene, und einem Renderer 'renderer' voraus. Du kannst z.B. das Beispiel auf der Hauptseite nutzen.
Pure JavaScript calculation
let time = Date.now()
const tick = () =>
{
// JS based time calculation
const currentTime = Date.now()
const deltaTime = currentTime - time
time = currentTime
//console.log(deltaTime)
// Update objects
mesh.rotation.y += 0.001 * deltaTime
renderer.render(scene, camera)
// tell JS to call tick on the next frame
window.requestAnimationFrame(tick)
}
// go...
tick()
ThreeJS Clock Object
const clock = new THREE.Clock()
const tick = () =>
{
// Hint: do NOT use clock.getDelta() - it can cause problems (buggy in end of 2021)
const elapsedTime = clock.getElapsedTime()
//console.log(elapsedTime)
mesh.rotation.y = elapsedTime * Math.PI * 2 // one revolution / s
camera.lookAt(mesh.position)
camera.position.z = Math.sin(elapsedTime) // back and forth
// Render
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick()
GSAP Animation
// GSAP has it's own requestAnimationFrame, thus no time calculation needed
// we just let gsap update our values and tick does render each frame
gsap.to(mesh.position,{ duration: 1, delay: 1, x: 2 })
gsap.to(mesh.position,{ duration: 1, delay: 1, x: 0 })
const tick = () =>
{
// Render on each frame
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
// GO...
tick()
Nützliche Snippets für Animationen
Kreisbewegung / Circular Movement
myObject.position.y = Math.sin(elapsedTime) //(-1 -> 1 -> -1 -> ...) myObject.position.x = Math.cos(elapsedTime)
Cursor auswerten
// Sizes
const sizes = { width: 800, height: 600}
// Cursor
const cursor = {
x: 0,
y: 0
}
window.addEventListener('mousemove', (event) =>
{
//cursor.x = event.clientX / sizes.width // 0 <= x <= 1
cursor.x = event.clientX / sizes.width - 0.5// -0.5 <= x <= +0.5
cursor.y = event.clientY / sizes.height - 0.5// -0.5 <= x <= +0.5
console.log('x: ' + cursor.x)
console.log('y: ' + cursor.y)
})
// ...
// Update camera with position
camera.position.x = cursor.x * 10
camera.position.y = cursor.y * 10
Kamera auf einer Kreisbahn
Das obige Beispiel läßt sich ausbauen. Die Mausbewegung gibt uns nun cursor.x Werte von -0.5 bis 0.5. Wenn wir auf zwei Achsen sinus und cosinus kombinieren bekommen wir eine Kreisbahn um den Mittelpunkt auf der Ebene dieser beiden Achsen. Eine volle Umdrehung bekommen wir wenn wir mit 2xPi multiplizieren. Den Abstand vergrößern wir wenn wir das Ergebnis mit irgendeinem Faktor multiplizieren.
// Update camera
camera.position.x = Math.sin(cursor.x * 2 * Math.PI) * 3
camera.position.z = Math.cos(cursor.x * 2 * Math.PI) * 3
camera.position.y = cursor.y * 5 // damit wir auch etwas von oben oder unten schauen können
camera.lookAt(mesh.position) // look at center
Für eine Kreisbahn um ein Objekt das nicht im Mittelpunkt ist, müßten wir noch die Koordinaten des Objekts auslesen und zu den Kamerakoordinaten addieren. So könnten wir den kompletten Kreis verschieben.
Orbit Controls
https://threejs.org/docs/index.html?q=controls#examples/en/controls/OrbitControls
ThreeJS spart mit eigenen Control Klassen eine Menge Arbeit. OrbitControls müssen zusätzlich geladen werden. Also in HTML
<script src="/javascripts/OrbitControls.js"></script>
Oder z.B. in Webpack:
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
Dann erstellt man einfach ein OrbitControl Objekt und übergibt die Kamera und ein DOM Objekt (i.d.R. das Canvas).
const controls = new OrbitControls(camera,canvas)
Geometry Snippets
Create Geometry / Geometry Objekt erzeugen
Beispiel: viele zufällige Dreiecke erzeugen
// Object
// const geometry = new THREE.BoxGeometry(1, 1, 1, 2, 2, 2)
const geometry = new THREE.BufferGeometry()
const count = 50
const positionsArray = new Float32Array(count * 3 * 3)
for (let i = 0; i < positionsArray.length; i++) {
positionsArray[i] = Math.random() - 0.5 //-0.5 < x < 0.5
}
const positionsAttribute = new THREE.BufferAttribute(positionsArray,3) // use vals 3 by 3
geometry.setAttribute('position',positionsAttribute) // position is the attribute name in shaders
// Example Array
// const positionsArray = new Float32Array([
// 0,0,0,
// 0,1,0,
// 1,0,0
// ])
Debugging
lil-gui
// https://lil-gui.georgealways.com/#
import GUI from 'lil-gui';
/**
* Debug
*/
const gui = new GUI({
width:400
})
gui.close()
const params = {
color: 0xff0000,
spin: () =>
{
console.log('spin')
let tl = new gsap.timeline
tl.to(mesh.rotation,{y: mesh.rotation.y + 0.5*Math.PI, duration: 1.5, ease: "circ"})
//...
}
}
//...
// Debug
//gui.add(mesh.position,'y',-2,2,0.1) // OR
gui.add(mesh.position,'y')
.min(-2)
.max(3)
.step(0.1)
.name('elevation') // chain version
gui.add(mesh,'visible')
gui.add(material,'wireframe')
// we can not use material.color as it's not an object
// thus we use a separately created object...
gui.addColor(params,'color')
// ... and update material when this param changed:
.onChange( ()=>
{
material.color.set(params.color)
})
gui.add(params, 'spin')
Textures
https://threejs.org/docs/index.html?q=texture#api/en/constants/Textures three.js - Textures Manche Materialien können eine oder mehrere Texturen enthalten.
Manuell laden
/**
* Textures
*/
const image = new Image()
const texture = new THREE.Texture(image)
image.src = '/textures/door/color.jpg'
image.onload = () =>
{
texture.needsUpdate = true
}
//...
const material = new THREE.MeshBasicMaterial({ map: texture })
TextureLoader
/**
* Textures
*/
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('/textures/door/color.jpg')
//...
const material = new THREE.MeshBasicMaterial({ map: texture })
TextureLoader Callbacks Manchmal nützlich für Fehlersuche etc.
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load(
'/textures/door/color.jpg',
() =>
{
console.log('load')
},
() =>
{
console.log('progress')
},
() =>
{
console.log('error')
}
)
Loading Manager
Nützlich für Progress
const loadingManager = new THREE.LoadingManager()
loadingManager.onStart = () =>
{
console.log('onStart')
}
loadingManager.onLoad = () =>
{
console.log('onLoad')
}
loadingManager.onProgress = () =>
{
console.log('onProgress')
}
loadingManager.onError = () =>
{
console.log('onError')
}
const textureLoader = new THREE.TextureLoader(loadingManager)
const colorTexture = textureLoader.load('/textures/door/color.jpg')
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const heightTexture = textureLoader.load('/textures/door/height.jpg')
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
const ambientOcclusionTexture = textureLoader.load('/textures/door/ambientOcclusion.jpg')
const metalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const roughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
UV Wrapping
Wrapping Mode
THREE.RepeatWrapping THREE.ClampToEdgeWrapping // default THREE.MirroredRepeatWrapping
const colorTexture = textureLoader.load('/textures/door/color.jpg')
// TEXTURE TRANSFORMATIONS
colorTexture.repeat.x = 2
colorTexture.repeat.y = 3
colorTexture.wrapS = THREE.RepeatWrapping
colorTexture.wrapT = THREE.RepeatWrapping
//colorTexture.wrapS = THREE.MirroredRepeatWrapping
//colorTexture.wrapT = THREE.MirroredRepeatWrapping
colorTexture.offset.x = 0.5
colorTexture.offset.y = 0.5
colorTexture.center.x = 0.5
colorTexture.center.y = 0.5
colorTexture.rotation = Math.PI * 0.25
Mipmapping
Minification
const colorTexture = textureLoader.load('/textures/checkerboard-1024x1024.png')
colorTexture.generateMipmaps = false // NearestFilter doesn't need Mipmap
colorTexture.minFilter = THREE.NearestFilter // oft bessere Schärfe aber auch mehr Moiré
Magnification
const colorTexture = textureLoader.load('/textures/minecraft.png')
colorTexture.magFilter = THREE.NearestFilter
Texture Sizing
256x256 / 512x1024 / 1024x1024 //Power of 2, often square
Normal-Maps png verwenden und evtl. NearestFilter
colorTexture.generateMipmaps = false // NearestFilter doesn't need that
colorTexture.minFilter = THREE.NearestFilter
colorTexture.magFilter = THREE.NearestFilter
mehrere Texturen in einem Bild (Bildkanäle) um Ladezeit zu verkürzen wenn notwendig.
Materials
three.js - materials
Besonders wichtig für realistische Ergebnisse ist das MeshStandardMaterial. Siehe unten.
MeshBasicMaterial
const material = new THREE.MeshBasicMaterial()
material.color.set(0xaabb00)
material.map = doorColorTexture
material.side = DoubleSide
material.wireframe = true
material.transparent = true
material.opacity = 0.5
material.alphaMap = doorAlphaTexture
Komplexere Materialien haben natürlich weitere Eigenschaften
MeshNormalMaterial
// normally to debug normals
const material = new THREE.MeshNormalMaterial()
material.flatShading = true
MeshMatcapMaterial
https://github.com/nidorx/matcaps - gute Quelle
// MATCAP - simulate lights / good for modelling
const textureLoader = new THREE.TextureLoader()
const matcapTexture = textureLoader.load('textures/matcaps/1.jpg')
const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture
MeshDepthMaterial
Nahe Bereiche werden hell, ferne dunkel gerendert. Kann man z.B. für Camera Tests oder für Nebel nutzen
// DEPTH
const material = new THREE.MeshDepthMaterial()
material.transparent = true
material.opacity = 0.8
MeshLambertMaterial
- Benötigt / Reagiert mit Licht
- Performant
- Ergibt manchmal Linien-Artefakte in der Geometrie
// LAMBERT (reacts to light)
const material = new THREE.MeshLambertMaterial()
MeshPhongMaterial
Ähnlich wie Lambert.
- Keine Artefakte
- Weniger Performant
- Erlaubt auch Lichtreflexion nicht nur Schattierung
// PHONG
const material = new THREE.MeshPhongMaterial
material.color.set(0xddeeee) // Grundfarbe
material.shininess = 180 // Glanz
material.specular = new THREE.Color(0x0066ff) // Glanzlichtfarbe
MeshToonMaterial
Cartoonartigen Effekt. Graustufengradient für Anzahl der Schattierungen. Dann NearestFilter verwenden, damit der Standardfilter die Abstufungen beim Mipmapping nicht kaputt macht.
const gradientTexture = textureLoader.load('textures/gradients/3.jpg')
gradientTexture.generateMipmaps = false
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
// TOON
const material = new THREE.MeshToonMaterial()
material.gradientMap = gradientTexture
MeshStandardMaterial
Standard Physical Based Rendering Material
// TEXTURES
const textureLoader = new THREE.TextureLoader()
const doorColorTexture = textureLoader.load('textures/door/color.jpg')
const doorAlphaTexture = textureLoader.load('textures/door/alpha.jpg')
const doorAmbientOcclusionTexture = textureLoader.load('textures/door/ambientOcclusion.jpg')
const doorHeightTexture = textureLoader.load('textures/door/height.jpg')
const doorMetalnessTexture = textureLoader.load('textures/door/metalness.jpg')
const doorNormalTexture = textureLoader.load('textures/door/normal.jpg')
const doorRoughnessTexture = textureLoader.load('textures/door/roughness.jpg')
// STANDARD MATERIAL
const material = new THREE.MeshStandardMaterial()
material.side = DoubleSide
material.map = doorColorTexture
material.roughness = 1 // default
material.roughnessMap = doorRoughnessTexture
material.metalness = 0 // default
material.metalnessMap = doorMetalnessTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1.1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.03
material.normalMap = doorNormalTexture
material.normalScale.set(0.5,0.5)
material.transparent = true // needed for alpha to work
material.alphaMap = doorAlphaTexture
// OBJECTS
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(1,1,100,100), // subdivisions needed for height map
material
)
// copy uv coordinates to uv2 attribute needed by aomap
plane.geometry.setAttribute(
'uv2',
new THREE.BufferAttribute(plane.geometry.attributes.uv.array,2)
)
MeshPhysicalMaterial
Wie MeshStandardMaterial bietet aber noch zusätzlich ein Coating. Also z.b. ein Material unter einer Lackschicht.
https://threejs.org/examples/#webgl_materials_physical_clearcoat
- Zusätzlich Coating
- Nicht so Performant wie Standard
PointsMaterial
You can use PointsMaterial with particles.
ShaderMaterial and RawShaderMaterial
ShaderMaterial and RawShaderMaterial can both be used to create your own materials.
Environment Map
The environment map is like an image of what's surrounding the scene. You can use it to add reflection or refraction to your objects. It can also be used as lighting information.
You can use it with many of the materials we saw.
// ENVIRONMENTAL MAP
const cubeTextureLoader = new THREE.CubeTextureLoader()
// load cube-images in the right order...
const environmentMapTexture = cubeTextureLoader.load([
'/textures/environmentMaps/4/px.png',
'/textures/environmentMaps/4/nx.png',
'/textures/environmentMaps/4/py.png',
'/textures/environmentMaps/4/ny.png',
'/textures/environmentMaps/4/pz.png',
'/textures/environmentMaps/4/nz.png',
])
const material = new THREE.MeshStandardMaterial()
material.envMap = environmentMapTexture
material.metalness = 0.7
material.roughness = 0.2
gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)
Cubemap erzeugen
Environment Maps gibt es in unterschiedlichen Formaten. Z.b. hier als HDRI:
https://polyhaven.com/
Es gibt Tools um diese in Cubemaps umzuwandeln:
https://matheowis.github.io/HDRI-to-CubeMap/
Hinweise
Eigenschaften kann man als Konstruktor Objekt übergeben oder direkt setzen oder über set (manchmal praktischer, wenn als Eigenschaftswert ein Objekt erwartet wird (z.B. bei der Farbe ein Farbobjekt)
3D Text
http://gero3.github.io/facetype.js/ - Konvertieren von Fonts nach Facetype
three.js nutzt Typeface Fonts
Typeface fonts bekommen
- Über Konvertiertools (s.o.)
- Aus den threejs examples: opening the /node_modules/three/examples/fonts/, taking the helvetiker_regular.typeface.json and LICENSE files, and putting these in the /static/fonts/ folder (that you need to create). The font is now accessible just by writing /fonts/helvetiker_regular.typeface.json at the end of the base URL.
Fontloader
Hinweis: Seit three.js 133 muss der Fontloader und die Fontgeometry importiert werden.
https://threejs.org/docs/index.html?q=fontloa#examples/en/loaders/FontLoader
Basic Example
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'//i.e. with webpack
//...
/**
* Fonts
*/
const fontLoader = new FontLoader()
fontLoader.load(
//'/fonts/helvetiker_regular.typeface.json',
'/fonts/BebasNeueBook_Regular.json',
(font) =>
{
console.log('font loaded')
const textGeometry = new TextGeometry(
'KHOLJA',
{
font: font,
size: 0.5,
height: 0.2, // more like extrusion depth
curveSegments: 8,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.01,
bevelOffset: 0,
bevelSegments: 4
}
)
textGeometry.center() // easy method to center the text
const textMaterial = new THREE.MeshBasicMaterial()
textMaterial.wireframe = true
const text = new THREE.Mesh(textGeometry,textMaterial)
scene.add(text)
}
)
Beispiel: Drehachse korrekt in Text zentrieren (Hard Way)
(BoundingBox + Bevel Korrektur)
/**
* Fonts
*/
const fontLoader = new FontLoader()
fontLoader.load(
//'/fonts/helvetiker_regular.typeface.json',
'/fonts/BebasNeueBook_Regular.json',
(font) =>
{
console.log('font loaded')
const bevelSize = 0.02
const bevelThickness = 0.03
const textGeometry = new TextGeometry(
'KHOLJA',
{
font: font,
size: 0.5,
height: 0.2, // more like extrusion depth
curveSegments: 8,
bevelEnabled: true,
bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelOffset: 0,
bevelSegments: 4
}
)
// use a BoundingBox to orientate at middle
textGeometry.computeBoundingBox()
console.log(textGeometry.boundingBox)
// move geometry (not the mesh)
// if we later rotate the mesh we will get a
// correct rotation axis
textGeometry.translate(
-(textGeometry.boundingBox.max.x-bevelSize) * 0.5,
-(textGeometry.boundingBox.max.y-bevelSize) * 0.5,
-(textGeometry.boundingBox.max.z-bevelThickness) * 0.5,
)
Drehachse zentrieren (easy way)
textGeometry.center()
Lights
AmbientLight
The AmbientLight applies omnidirectional lighting on all geometries of the scene. Therefore no shadows thrown.
// AmbientLight - color, intensity
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
gui.add(ambientLight,'intensity',0,1).name('AmbientLight')
DirectionalLight
The DirectionalLight will have a sun-like effect as if the sun rays were traveling in parallel.
// DirectionalLight - color, intensity
const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.3)
directionalLight.position.set(1, 0.25, 0)
scene.add(directionalLight)
gui.add(directionalLight,'intensity',0,1).name('DirectionalLight')
HemisphereLight
The HemisphereLight is similar to the AmbientLight but with a different color from the sky than the color coming from the ground. Faces facing the sky will be lit by one color while another color will lit faces facing the ground.
// HemisphereLight - color top, color bottom, intensity
const hemisphereLight = new THREE.HemisphereLight(0xff0000, 0x0000ff, 0.3)
scene.add(hemisphereLight)
gui.add(hemisphereLight,'intensity',0,1).name('HemisphereLight')
PointLight
The PointLight is almost like a lighter. The light source is infinitely small, and the light spreads uniformly in every direction. You can control that fade distance and how fast it is fading using the distance and decay properties.
// PointLight - color, intensity, distance, decay
const pointLight = new THREE.PointLight(0xff9000, 0.5, 10, 2)
pointLight.position.set(1, - 0.5, 1)
scene.add(pointLight)
gui.add(pointLight,'intensity',0,1).name('PointLight')
RectAreaLight
The RectAreaLight works like the big rectangle lights you can see on the photoshoot set. It's a mix between a directional light and a diffuse light. You can then move the light and rotate it. To ease the rotation, you can use the lookAt(...) method
// RectAreaLight - color, intensity, width, height
const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 2, 1, 1)
rectAreaLight.position.set(- 1.5, 0, 1.5)
rectAreaLight.lookAt(new THREE.Vector3())
scene.add(rectAreaLight)
gui.add(rectAreaLight,'intensity',0,5).name('RectAreaLight')
SpotLight
The SpotLight works like a flashlight. It's a cone of light starting at a point and oriented in a direction.
Rotating our SpotLight is a little harder. The instance has a property named target, which is an Object3D. The SpotLight is always looking at that target object. Simply create the target and do not forget to add it to the scene, and it should work:
// SpotLight - color, intensity, distance, angle (of beam), penumbra (contour diffusion),decay
const spotLight = new THREE.SpotLight(0x78ff00, 0.5, 10, Math.PI * 0.1, 0.25, 1)
spotLight.position.set(0, 2, 3)
spotLight.target.position.x = - 0.75
scene.add(spotLight.target)
scene.add(spotLight)
gui.add(spotLight,'intensity',0,1).name('SpotLight')
Light Helpers
Positioning and orienting the lights is hard. To assist us, we can use helpers. Only the following helpers are supported:
- HemisphereLightHelper
- DirectionalLightHelper
- PointLightHelper
- RectAreaLightHelper
- SpotLightHelper
To use them, simply instantiate those classes. Use the corresponding light as a parameter, and add them to the scene. The second parameter enables you to change the helper's size:
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 0.2)
scene.add(hemisphereLightHelper)
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.2)
scene.add(directionalLightHelper)
const pointLightHelper = new THREE.PointLightHelper(pointLight, 0.2)
scene.add(pointLightHelper)
For the SpotLightHelper, there is no size parameter. Also, after moving the target, you need to call the update(...) method but on the next frame:
For the SpotLightHelper, there is no size parameter. Also, after moving the target, you need to call the update(...) method but on the next frame:
const spotLightHelper = new THREE.SpotLightHelper(spotLight)
scene.add(spotLightHelper)
window.requestAnimationFrame(() =>
{
spotLightHelper.update()
})
The RectAreaLightHelper is even harder to use. Right now, the class isn't part of the THREE variable. You must import it from the examples dependencies as we did with OrbitControls:
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js'
//...
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
scene.add(rectAreaLightHelper)
Performance
Lights can cost a lot when it comes to performance.
Try to add as few lights as possible and try to use the lights that cost less.
Minimal cost:
- AmbientLight
- HemisphereLight
Moderate cost:
- DirectionalLight
- PointLight
High cost:
- SpotLight
- RectAreaLight
Baking Light
A good technique for lighting is called baking. The idea is that you bake the light into the texture. This can be done in a 3D software. Unfortunately, you won't be able to move the lights, because there are none and you'll probably need a lot of textures.
Shadows
Schatten einschalten
// Decide for every object if it casts and/or receives shadows
sphere.castShadow = true
plane.receiveShadow = true
// Decide for every light if it casts shadows
directionalLight.castShadow = true
// Enable shadowmap in renderer
renderer.shadowMap.enabled = true
Shadowmap optimieren
Jedes Licht nutzt eine Shadowmap mit einer Default Tilegröße von 512x512px. Über die shadowmap-Property kann man die Größe beeinflussen. Wegen dem Mipmapping solltest du eine Zweierpotenz nutzen (siehe Mipmapping). Vorsicht - Schatten kosten viel Performance.
directionalLight.shadow.mapSize.x = 1024
directionalLight.shadow.mapSize.y = 1024
Starters
Starter mit StandardMaterial, Lights, Animation, Resize Handler (webpack)
File:15-lights-good-starter.zip
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'lil-gui'
/**
* Base
*/
// Debug
const gui = new dat.GUI()
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
/**
* Lights
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
const pointLight = new THREE.PointLight(0xffffff, 0.5)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight)
/**
* Objects
*/
// Material
const material = new THREE.MeshStandardMaterial()
material.roughness = 0.4
// Objects
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 32),
material
)
sphere.position.x = - 1.5
const cube = new THREE.Mesh(
new THREE.BoxGeometry(0.75, 0.75, 0.75),
material
)
const torus = new THREE.Mesh(
new THREE.TorusGeometry(0.3, 0.2, 32, 64),
material
)
torus.position.x = 1.5
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
material
)
plane.rotation.x = - Math.PI * 0.5
plane.position.y = - 0.65
scene.add(sphere, cube, torus, plane)
/**
* Sizes
*/
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
/**
* Handle Resize
*/
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})
/**
* Camera
*/
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 1
camera.position.y = 1
camera.position.z = 2
scene.add(camera)
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
sphere.rotation.y = 0.1 * elapsedTime
cube.rotation.y = 0.1 * elapsedTime
torus.rotation.y = 0.1 * elapsedTime
sphere.rotation.x = 0.15 * elapsedTime
cube.rotation.x = 0.15 * elapsedTime
torus.rotation.x = 0.15 * elapsedTime
// Update controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
Optimization Helpers
Materialien und Geometrien wiederverwenden
Das Erstellen von komplexen Objekten kann zeit- und speicherintensiv sein.
console.time('donuts')
for(let i = 0; i < 100; i++)
{
const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45)
const donutMaterial = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
const donut = new THREE.Mesh(donutGeometry, donutMaterial)
scene.add(donut)
}
console.timeend('donuts')
Zwei Zeilen umgestellt aber weit über 100mal schneller !
console.time('donuts')
const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45)
const donutMaterial = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
for(let i = 0; i < 100; i++)
{
const donut = new THREE.Mesh(donutGeometry, donutMaterial)
scene.add(donut)
}
console.timeend('donuts')