ThreeJS - Snippets: Unterschied zwischen den Versionen

Aus Wikizone
Wechseln zu: Navigation, Suche
 
(66 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
 
== Links ==
 
== Links ==
 
  [[ThreeJS]]
 
  [[ThreeJS]]
 +
[[Three.js - Particles]]
 +
[[Three.js - Shaders]]
  
 +
== Basics ==
 +
<syntaxhighlight lang="javascript">
 +
/**
 +
* Test cube
 +
*/
 +
const cube = new THREE.Mesh(
 +
    new THREE.BoxGeometry(1, 1, 1),
 +
    new THREE.MeshBasicMaterial()
 +
)
 +
scene.add(cube)
 +
</syntaxhighlight>
 
== Helfer ==
 
== Helfer ==
 
=== Axes Helper ===
 
=== Axes Helper ===
Zeile 69: Zeile 82:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Animation Basics ==
+
== Animation ==
 
+
[[Three.js - Animation]]
 +
[[Three.js - Scroll Based Animation]]
 
=== Timebased Tick / Loop Function ===
 
=== 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 ====
 
<syntaxhighlight lang="javascript">
 
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()
 
</syntaxhighlight>
 
 
 
==== ThreeJS Clock Object ====
 
==== ThreeJS Clock Object ====
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Zeile 170: Zeile 161:
  
 
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.
 
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.
 +
 +
=== Bouncing Sphere + Shadow ===
 +
<syntaxhighlight lang="javascript">
 +
    // Update the sphere
 +
    sphere.position.x = Math.cos(elapsedTime) * 1.5
 +
    sphere.position.z = Math.sin(elapsedTime) * 1.5
 +
    sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))
 +
 +
    // Update the shadow accordingly
 +
    sphereShadow.position.x = sphere.position.x
 +
    sphereShadow.position.z = sphere.position.z
 +
    sphereShadow.material.opacity = (1 - sphere.position.y) * 0.3
 +
</syntaxhighlight>
  
 
== Orbit Controls ==
 
== Orbit Controls ==
Zeile 182: Zeile 186:
 
== Geometry Snippets ==
 
== Geometry Snippets ==
 
=== Create Geometry / Geometry Objekt erzeugen ===
 
=== Create Geometry / Geometry Objekt erzeugen ===
 +
[[Three.js - eigene Geometrie erzeugen]]
 
Beispiel: viele zufällige Dreiecke erzeugen
 
Beispiel: viele zufällige Dreiecke erzeugen
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Zeile 215: Zeile 220:
 
})
 
})
 
gui.close()
 
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
 
// Debug
Zeile 237: Zeile 232:
 
// we can not use material.color as it's not an object
 
// we can not use material.color as it's not an object
 
// thus we use a separately created object...
 
// thus we use a separately created object...
 +
// ... and update material when this param changed:
 
gui.addColor(params,'color')
 
gui.addColor(params,'color')
// ... and update material when this param changed:
 
 
.onChange( ()=>
 
.onChange( ()=>
 
{
 
{
Zeile 245: Zeile 240:
 
gui.add(params, 'spin')
 
gui.add(params, 'spin')
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
'''Beispiel: Renderer ClearColor'''
== 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 ===
 
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
/**
+
// Debug
* Textures
+
const params = {}
*/
+
const gui = new dat.GUI({width: 400})
const image = new Image()
+
params.clearColor = '#2b4945'
const texture = new THREE.Texture(image)  
 
image.src = '/textures/door/color.jpg'
 
image.onload = () =>
 
{
 
    texture.needsUpdate = true
 
}
 
 
 
 
//...
 
//...
 
+
renderer.setClearColor(params.clearColor)
const material = new THREE.MeshBasicMaterial({ map: texture })
+
gui.addColor(params, 'clearColor').onChange( () => {renderer.setClearColor(params.clearColor)})
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== TextureLoader ===
+
== Textures ==
 +
https://threejs.org/docs/index.html?q=texture#api/en/constants/Textures
 +
[[three.js - Textures]] - ausführliche Infos zu TextureLoader Callbacks, LoadingManager...
 +
[[three.js - baked map texture]]
 +
=== Beispiel ===
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
/**
 
/**
Zeile 275: Zeile 261:
 
  */
 
  */
 
const textureLoader = new THREE.TextureLoader()
 
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('/textures/door/color.jpg')
 
//...
 
const material = new THREE.MeshBasicMaterial({ map: texture })
 
</syntaxhighlight>
 
 
'''TextureLoader Callbacks'''
 
Manchmal nützlich für Fehlersuche etc.
 
<syntaxhighlight lang="javascript">
 
const textureLoader = new THREE.TextureLoader()
 
const texture = textureLoader.load(
 
    '/textures/door/color.jpg',
 
    () =>
 
    {
 
        console.log('load')
 
    },
 
    () =>
 
    {
 
        console.log('progress')
 
    },
 
    () =>
 
    {
 
        console.log('error')
 
    }
 
)
 
</syntaxhighlight>
 
 
=== Loading Manager ===
 
Nützlich für Progress
 
<syntaxhighlight lang="javascript">
 
 
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 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 doorNormalTexture = textureLoader.load('/textures/door/normal.jpg')
 +
const doorMetalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
 +
const doorRoughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
  
const colorTexture = textureLoader.load('/textures/door/color.jpg')
+
// HOW TO REPEAT TILES - uv wrapping
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
+
const repeat = 10;
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')
 
</syntaxhighlight>
 
  
=== UV Wrapping ===
+
const grassColorTexture = textureLoader.load('/textures/grass/color.jpg')
====Wrapping Mode====
+
const grassNormalTexture = textureLoader.load('/textures/grass/normal.jpg')
THREE.RepeatWrapping
+
const grassRoughnessTexture = textureLoader.load('/textures/grass/roughness.jpg')
THREE.ClampToEdgeWrapping // default
 
THREE.MirroredRepeatWrapping
 
  
<syntaxhighlight lang="javascript">
+
grassColorTexture.repeat.set(repeat,repeat)
const colorTexture = textureLoader.load('/textures/door/color.jpg')
+
grassNormalTexture.repeat.set(repeat,repeat)
 +
grassRoughnessTexture.repeat.set(repeat,repeat)
  
// TEXTURE TRANSFORMATIONS
+
grassColorTexture.wrapS = THREE.RepeatWrapping
colorTexture.repeat.x = 2
+
grassNormalTexture.wrapS = THREE.RepeatWrapping
colorTexture.repeat.y = 3
+
grassRoughnessTexture.wrapS = THREE.RepeatWrapping
  
colorTexture.wrapS = THREE.RepeatWrapping
+
grassColorTexture.wrapT = THREE.RepeatWrapping
colorTexture.wrapT = THREE.RepeatWrapping
+
grassNormalTexture.wrapT = THREE.RepeatWrapping
//colorTexture.wrapS = THREE.MirroredRepeatWrapping
+
grassRoughnessTexture.wrapT = THREE.RepeatWrapping
//colorTexture.wrapT = THREE.MirroredRepeatWrapping
 
  
colorTexture.offset.x = 0.5
+
//...
colorTexture.offset.y = 0.5
 
  
colorTexture.center.x = 0.5
+
/**
colorTexture.center.y = 0.5
+
* Objects
colorTexture.rotation = Math.PI * 0.25
+
*/
  
</syntaxhighlight>
+
// Door
 +
const door = new THREE.Mesh(
 +
    new THREE.PlaneGeometry(2,2,100,100),// HEIGHTMAP NEEDS SOME SUBDIVISIONS
 +
    new THREE.MeshStandardMaterial({
 +
        map: doorColorTexture,
 +
        transparent: true, // NEEDED FOR ALPHA TO WORK
 +
        alphaMap: doorAlphaTexture,
 +
        aoMap: doorAmbientOcclusionTexture,
 +
        displacementMap: doorHeightTexture,
 +
        displacementScale: 0.05,
 +
        normalMap: doorNormalTexture,
 +
        metalnessMap: doorMetalnessTexture,
 +
        roughnessMap: doorRoughnessTexture,
 +
        //wireframe: true
 +
    })
 +
)
 +
// AOMAP NEEDS HIS OWN UV ATTRIBUTE (here we copy from geometry)
 +
door.geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(door.geometry.attributes.uv.array, 2))
  
==== Mipmapping ====
+
house.add(door)
  
'''Minification'''
+
// Floor
<syntaxhighlight lang="javascript">
+
const floor = new THREE.Mesh(
const colorTexture = textureLoader.load('/textures/checkerboard-1024x1024.png')
+
    new THREE.PlaneGeometry(20, 20),
colorTexture.generateMipmaps = false // NearestFilter doesn't need Mipmap
+
    new THREE.MeshStandardMaterial({
colorTexture.minFilter = THREE.NearestFilter // oft bessere Schärfe aber auch mehr Moiré
+
        //color: '#a9c388',
</syntaxhighlight>
+
        map: grassColorTexture,
 
+
        normalMap: grassNormalTexture,
'''Magnification'''
+
        roughnessMap: grassRoughnessTexture   
<syntaxhighlight lang="javascript">
+
    })
const colorTexture = textureLoader.load('/textures/minecraft.png')
+
)
colorTexture.magFilter = THREE.NearestFilter
 
</syntaxhighlight>
 
  
'''Texture Sizing'''
+
floor.rotation.x = - Math.PI * 0.5
256x256 / 512x1024 / 1024x1024 //Power of 2, often square
+
floor.position.y = 0
  
'''Normal-Maps''' png verwenden und evtl. NearestFilter
+
scene.add(floor)
<syntaxhighlight lang="javascript">
 
colorTexture.generateMipmaps = false // NearestFilter doesn't need that
 
colorTexture.minFilter = THREE.NearestFilter
 
colorTexture.magFilter = THREE.NearestFilter
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''mehrere Texturen in einem Bild (Bildkanäle)''' um Ladezeit zu verkürzen wenn notwendig.
+
== Materials ==
 +
[[three.js - materials]] - Mehr Info und weitere Materialien.
  
== Materials ==
 
[[three.js - materials]]
 
Besonders wichtig für realistische Ergebnisse ist das MeshStandardMaterial. Siehe unten.
 
 
=== MeshBasicMaterial ===
 
=== MeshBasicMaterial ===
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Zeile 401: Zeile 346:
 
material.opacity = 0.5
 
material.opacity = 0.5
 
material.alphaMap = doorAlphaTexture
 
material.alphaMap = doorAlphaTexture
</syntaxhighlight>
 
 
Komplexere Materialien haben natürlich weitere Eigenschaften
 
=== MeshNormalMaterial ===
 
<syntaxhighlight lang="javascript">
 
// normally to debug normals
 
const material = new THREE.MeshNormalMaterial()
 
material.flatShading = true
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Zeile 419: Zeile 356:
 
const material = new THREE.MeshMatcapMaterial()
 
const material = new THREE.MeshMatcapMaterial()
 
material.matcap = matcapTexture
 
material.matcap = matcapTexture
</syntaxhighlight>
 
 
=== MeshDepthMaterial ===
 
Nahe Bereiche werden hell, ferne dunkel gerendert. Kann man z.B. für Camera Tests oder für Nebel nutzen
 
<syntaxhighlight lang="javascript">
 
// DEPTH
 
const material = new THREE.MeshDepthMaterial()
 
material.transparent = true
 
material.opacity = 0.8
 
</syntaxhighlight>
 
 
=== MeshLambertMaterial ===
 
* Benötigt / Reagiert mit Licht
 
* Performant
 
* Ergibt manchmal Linien-Artefakte in der Geometrie
 
<syntaxhighlight lang="javascript">
 
// LAMBERT (reacts to light)
 
const material = new THREE.MeshLambertMaterial()
 
</syntaxhighlight>
 
 
=== MeshPhongMaterial ===
 
Ähnlich wie Lambert.
 
* Keine Artefakte
 
* Weniger Performant
 
* Erlaubt auch Lichtreflexion nicht nur Schattierung
 
<syntaxhighlight lang="javascript">
 
// PHONG
 
const material = new THREE.MeshPhongMaterial
 
material.color.set(0xddeeee) // Grundfarbe
 
material.shininess = 180 // Glanz
 
material.specular = new THREE.Color(0x0066ff) // Glanzlichtfarbe
 
</syntaxhighlight>
 
 
=== MeshToonMaterial ===
 
Cartoonartigen Effekt. Graustufengradient für Anzahl der Schattierungen. Dann NearestFilter verwenden, damit der Standardfilter die Abstufungen beim Mipmapping nicht kaputt macht.
 
<syntaxhighlight lang="javascript">
 
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
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Zeile 513: Zeile 406:
 
)
 
)
 
</syntaxhighlight>
 
</syntaxhighlight>
 
=== 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 ===
 
=== 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.
+
[[Three.js - Environment Map (Panorama)]]
  
You can use it with many of the materials we saw.
 
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
// ENVIRONMENTAL MAP
 
// ENVIRONMENTAL MAP
Zeile 551: Zeile 431:
  
 
</syntaxhighlight>
 
</syntaxhighlight>
 
==== 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 ===
 
=== Hinweise ===
Zeile 562: Zeile 436:
  
 
== 3D Text ==
 
== 3D Text ==
 +
[[Three.js - 3D Text]]
 
  http://gero3.github.io/facetype.js/ - Konvertieren von Fonts nach Facetype
 
  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 ===
 
=== Fontloader ===
 
Hinweis: Seit three.js 133 muss der Fontloader und die Fontgeometry importiert werden.
 
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
 
  https://threejs.org/docs/index.html?q=fontloa#examples/en/loaders/FontLoader
 
 
====Basic Example====
 
====Basic Example====
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Zeile 612: Zeile 480:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== Beispiel: Drehachse korrekt in Text zentrieren (Hard Way)====
+
== Lights ==
(BoundingBox + Bevel Korrektur)
+
[[Three.js - Lights]]
 +
=== Light Starters ===
 +
==== Neutral Starter ====
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
/**
 
/**
  * Fonts
+
  * Lights
 
  */
 
  */
const fontLoader = new FontLoader()
+
// Ambient light
 +
const ambientLight = new THREE.AmbientLight('#ffffff', 0.5)
 +
gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001)
 +
scene.add(ambientLight)
  
fontLoader.load(
+
// Directional light
    //'/fonts/helvetiker_regular.typeface.json',
+
const directionalLight = new THREE.DirectionalLight('#ffffff', 0.5)
    '/fonts/BebasNeueBook_Regular.json',
+
directionalLight.position.set(4, 5, - 2)
    (font) =>
+
gui.add(directionalLight, 'intensity').min(0).max(1).step(0.001)
    {
+
gui.add(directionalLight.position, 'x').min(- 5).max(5).step(0.001)
        console.log('font loaded')
+
gui.add(directionalLight.position, 'y').min(- 5).max(5).step(0.001)
        const bevelSize = 0.02
+
gui.add(directionalLight.position, 'z').min(- 5).max(5).step(0.001)
        const bevelThickness = 0.03
+
scene.add(moonLight)
        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,
 
        )
 
 
</syntaxhighlight>
 
</syntaxhighlight>
=== Drehachse zentrieren (easy way) ===
 
textGeometry.center()
 
  
== Lights ==
+
==== Moonlight Starter ====
[[Datei:Three.js-3D-Lighting.jpg|rahmenlos|zentriert]]
 
=== AmbientLight ===
 
The AmbientLight applies '''omnidirectional lighting''' on all geometries of the scene. Therefore no shadows thrown.
 
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
// AmbientLight - color, intensity
+
/**
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
+
* Lights
 +
*/
 +
// Ambient light
 +
const ambientLight = new THREE.AmbientLight('#b9d5ff', 0.5)
 +
gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001)
 
scene.add(ambientLight)
 
scene.add(ambientLight)
gui.add(ambientLight,'intensity',0,1).name('AmbientLight')
 
</syntaxhighlight>
 
  
=== DirectionalLight ===
+
// Directional light
The DirectionalLight will have a '''sun-like effect''' as if the sun rays were traveling in parallel.
+
const moonLight = new THREE.DirectionalLight('#b9d5ff', 0.5)
<syntaxhighlight lang="javascript">
+
moonLight.position.set(4, 5, - 2)
// DirectionalLight - color, intensity
+
gui.add(moonLight, 'intensity').min(0).max(1).step(0.001)
const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.3)
+
gui.add(moonLight.position, 'x').min(- 5).max(5).step(0.001)
directionalLight.position.set(1, 0.25, 0)
+
gui.add(moonLight.position, 'y').min(- 5).max(5).step(0.001)
scene.add(directionalLight)
+
gui.add(moonLight.position, 'z').min(- 5).max(5).step(0.001)
gui.add(directionalLight,'intensity',0,1).name('DirectionalLight')
+
scene.add(moonLight)
</syntaxhighlight>
 
 
 
=== 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.
 
<syntaxhighlight lang="javascript">
 
// 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')
 
</syntaxhighlight>
 
 
 
=== 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.
 
<syntaxhighlight lang="javascript">
 
// 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')
 
</syntaxhighlight>
 
 
 
=== 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
 
 
 
<syntaxhighlight lang="javascript">
 
// 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')
 
</syntaxhighlight>
 
 
 
=== 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:
 
 
 
<syntaxhighlight lang="javascript">
 
// 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')
 
</syntaxhighlight>
 
 
 
=== 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:
 
<syntaxhighlight lang="javascript">
 
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)
 
</syntaxhighlight>
 
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:
 
 
 
<syntaxhighlight lang="javascript">
 
const spotLightHelper = new THREE.SpotLightHelper(spotLight)
 
scene.add(spotLightHelper)
 
window.requestAnimationFrame(() =>
 
{
 
    spotLightHelper.update()
 
})
 
</syntaxhighlight>
 
 
 
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:
 
 
 
<syntaxhighlight lang="javascript">
 
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js'
 
//...
 
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
 
scene.add(rectAreaLightHelper)
 
</syntaxhighlight>
 
 
 
=== 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 ===
 
<syntaxhighlight lang="javascript">
 
// 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
 
</syntaxhighlight>
 
 
 
=== 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.
 
<syntaxhighlight lang="javascript">
 
directionalLight.shadow.mapSize.x = 1024
 
directionalLight.shadow.mapSize.y = 1024
 
</syntaxhighlight>
 
 
 
=== Schattenkamera einstellen. ===
 
Lichter benutzen ein eigenes Camera Objekt (OrthoCamera) um die Schatten zu berechnen.
 
==== CameraHelper ====
 
Für genaue Tweaks erstellen wie einen '''CameraHelper''' und übergeben diesem die Schattenkamera.
 
<syntaxhighlight lang="javascript">
 
// Shadow camera helper
 
const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera)
 
scene.add(directionalLightCameraHelper)
 
</syntaxhighlight>
 
 
 
==== Renderbereich einstellen ====
 
Mit den near, far, left, right, top, bottom Werten lassen sich die Grenzen der Kamera und damit auch für die Schattenerzeugung einstellen. Der Camerahelper hilft dabei. near und far helfen nicht die Qualität der Schatten zu verbessern. Falsche Werte können aber für verbuggte Schatten verantwortlich sein.
 
 
 
'''Schmale Kameraausschnitte verbessern das Ergebnis''', da die Mipmaps effektiver verwendet werden. Sind sie '''zu schmal können aber Schatten abgeschnitten werden'''.
 
<syntaxhighlight lang="javascript">
 
directionalLight.shadow.camera.near = 1
 
directionalLight.shadow.camera.far = 6
 
 
 
directionalLight.shadow.camera.left = -2
 
directionalLight.shadow.camera.right = 2
 
directionalLight.shadow.camera.top = 2
 
directionalLight.shadow.camera.bottom = -2
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== Shadow map algorithm ====
+
== Shadows / Schatten ==
Es gibt verschiedene Algorythmen für die Berechnung der ShadowMap:
+
[[Three.js - Shadows]] - Ausführliche Infos zu Schatten
 
 
THREE.BasicShadowMap Very performant but lousy quality
 
THREE.PCFShadowMap Less performant but smoother edges - DEFAULT
 
THREE.PCFSoftShadowMap Less performant but even softer edges
 
THREE.VSMShadowMap Less performant, more constraints, can have unexpected results
 
 
 
renderer.shadowMap.type = THREE.PCFSoftShadowMap
 
  
==== Komplettes Beispiel ====
+
=== Komplettes Beispiel ===
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
//...
 
//...
Zeile 905: Zeile 591:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Starters ==
 
  
=== Starter mit StandardMaterial, Lights, Animation, Resize Handler (webpack) ===
+
==== Shadow Baking ====
[[:File:15-lights-good-starter.zip]]
+
Wie Texturen kann man auch Schatten baken. Nachteil. Bei Bewegung des Objekts bewegt sich der
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
import './style.css'
 
import * as THREE from 'three'
 
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
 
import * as dat from 'lil-gui'
 
 
 
/**
 
/**
  * Base
+
  * Textures
 
  */
 
  */
 +
const textureLoader = new THREE.TextureLoader()
 +
const bakedShadow = textureLoader.load('/textures/bakedShadow.jpg')
 +
//...
 +
const plane = new THREE.Mesh(
 +
    new THREE.PlaneGeometry(5, 5),
 +
    new THREE.MeshBasicMaterial({
 +
        map: bakedShadow
 +
    })
 +
)
 +
// we don't need the rendered shadows in this case
 +
renderer.shadowMap.enabled = false
 +
</syntaxhighlight>
 +
 +
==== Dynamic Shadow Baking ====
 +
'''Beispiel Kugel mit animiertem Fake-Schatten'''
 +
<syntaxhighlight lang="javascript">
 +
const textureLoader = new THREE.TextureLoader()
 +
const simpleShadow = textureLoader.load('/textures/simpleShadow.jpg')
  
// Debug
+
// Sphere Shadow
const gui = new dat.GUI()
+
const sphereShadow = new THREE.Mesh(
 +
    new THREE.PlaneGeometry(1.5, 1.5),
 +
    new THREE.MeshBasicMaterial({
 +
        color: 0x000000,
 +
        transparent: true,
 +
        alphaMap: simpleShadow
 +
    })
 +
)
 +
sphereShadow.rotation.x = - Math.PI * 0.5
 +
sphereShadow.position.y = plane.position.y + 0.01
  
// Canvas
+
scene.add(sphere, sphereShadow, plane)
const canvas = document.querySelector('canvas.webgl')
 
  
// Scene
+
//...
const scene = new THREE.Scene()
 
  
 
/**
 
/**
  * Lights
+
  * Animate
 
  */
 
  */
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
+
const clock = new THREE.Clock()
scene.add(ambientLight)
 
  
const pointLight = new THREE.PointLight(0xffffff, 0.5)
+
const tick = () =>
pointLight.position.x = 2
+
{
pointLight.position.y = 3
+
    const elapsedTime = clock.getElapsedTime()
pointLight.position.z = 4
 
scene.add(pointLight)
 
  
/**
+
    // Update the sphere
* Objects
+
    sphere.position.x = Math.cos(elapsedTime) * 1.5
*/
+
    sphere.position.z = Math.sin(elapsedTime) * 1.5
 +
    sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))
  
// Material
+
    // Update the shadow accordingly
const material = new THREE.MeshStandardMaterial()
+
    sphereShadow.position.x = sphere.position.x
material.roughness = 0.4
+
    sphereShadow.position.z = sphere.position.z
 +
    sphereShadow.material.opacity = (1 - sphere.position.y) * 0.3
  
// Objects
+
    // Update controls
const sphere = new THREE.Mesh(
+
     controls.update()
     new THREE.SphereGeometry(0.5, 32, 32),
 
    material
 
)
 
sphere.position.x = - 1.5
 
  
const cube = new THREE.Mesh(
+
    // Render
     new THREE.BoxGeometry(0.75, 0.75, 0.75),
+
     renderer.render(scene, camera)
    material
 
)
 
  
const torus = new THREE.Mesh(
+
    // Call tick again on the next frame
     new THREE.TorusGeometry(0.3, 0.2, 32, 64),
+
     window.requestAnimationFrame(tick)
    material
+
}
)
 
torus.position.x = 1.5
 
  
const plane = new THREE.Mesh(
+
tick()
    new THREE.PlaneGeometry(5, 5),
+
</syntaxhighlight>
    material
 
)
 
plane.rotation.x = - Math.PI * 0.5
 
plane.position.y = - 0.65
 
  
scene.add(sphere, cube, torus, plane)
+
== Fog ==
 +
[[Three.js - Fog]]
 +
<syntaxhighlight lang="javascript">
 +
//...
 +
// Fog
 +
const fog = new THREE.Fog('#262837', 1, 15)
 +
scene.fog = fog
 +
//...
 +
renderer.setClearColor('#262837')
 +
</syntaxhighlight>
  
 +
== Particles ==
 +
Benötigen eine Geometry, ein Material, ein Points Objekt (statt wie sonst ein Mesh)
 +
[[Three.js - Particles]]
 +
'''Starter Examples'''
 +
<syntaxhighlight lang="javascript">
 
/**
 
/**
  * Sizes
+
  * Particles
 
  */
 
  */
const sizes = {
+
// Geometry
     width: window.innerWidth,
+
const particlesGeometry = new THREE.SphereBufferGeometry(1,32,32)
     height: window.innerHeight
+
// Material
}
+
const particlesMaterial = new THREE.PointsMaterial({
 +
     size: 0.02,
 +
     sizeAttenuation: true, //perspective smaller if far
 +
})
 +
// Points
 +
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
 +
scene.add(particles)
 +
</syntaxhighlight>
  
 +
'''Random Points Starter'''
 +
<syntaxhighlight lang="javascript">
 
/**
 
/**
  * Handle Resize
+
  * Fireflies
 
  */
 
  */
window.addEventListener('resize', () =>
+
const firefliesGeometry = new THREE.BufferGeometry()
 +
const firefliesCount = 30
 +
const positionArray = new Float32Array(firefliesCount * 3)
 +
 
 +
for(let i = 0; i < firefliesCount; i++)
 
{
 
{
     // Update sizes
+
     positionArray[i * 3 + 0] = ( Math.random() -0.5) * 4.5 // x from -0.5 to 4
     sizes.width = window.innerWidth
+
     positionArray[i * 3 + 1] = ( Math.random() + 0.05) * 3 // y from 0.05 to 2.95
    sizes.height = window.innerHeight
+
    positionArray[i * 3 + 2] = ( Math.random() ) * 4.5 // z from 0 to 4.5
 +
}
 +
 
 +
firefliesGeometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3))
 +
const firefliesMaterial = new THREE.PointsMaterial({ size: 0.1, sizeAttenuation: true })
 +
const fireflies = new THREE.Points(firefliesGeometry, firefliesMaterial)
 +
 
 +
scene.add(fireflies)
 +
</syntaxhighlight>
  
     // Update camera
+
== Models Importieren ==
     camera.aspect = sizes.width / sizes.height
+
[[Three.js - Import Models]]
     camera.updateProjectionMatrix()
+
 
 +
<syntaxhighlight lang="javascript">
 +
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
 +
//...
 +
const gltfLoader = new GLTFLoader()
 +
gltfLoader.load(
 +
     '/models/FlightHelmet/glTF/FlightHelmet.gltf',
 +
     (gltf) => {
 +
        console.log(gltf) //success - show what we got
 +
        const children = [...gltf.scene.children] // duplicate
 +
        for(const child of children){
 +
            scene.add(child)
 +
        }
 +
        //scene.add(gltf.scene)
 +
        //scene.add(gltf.scene.children[1])
 +
    },
 +
    () => {console.log('progress')},
 +
     () => {console.log('error')}
 +
)
 +
</syntaxhighlight>
 +
=== DRACO Loader nutzen ===
  
    // Update renderer
+
<syntaxhighlight lang="javascript">
    renderer.setSize(sizes.width, sizes.height)
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
})
+
//...
 +
const dracoLoader = new DRACOLoader()
 +
dracoLoader.setDecoderPath("/draco/") // get webassembly version (faster but you have to provide the appropriate draco files)
  
/**
+
const gltfLoader = new GLTFLoader()
* Camera
+
gltfLoader.setDRACOLoader(dracoLoader)
*/
+
gltfLoader.load(
// Base camera
+
    '/models/Duck/glTF-Draco/Duck.gltf',
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
+
    (gltf) => {
camera.position.x = 1
+
        scene.add(gltf.scene)
camera.position.y = 1
+
    }
camera.position.z = 2
+
)
scene.add(camera)
+
</syntaxhighlight>
  
// Controls
+
== Starters ==
const controls = new OrbitControls(camera, canvas)
+
[[Three.js - Starters]]
controls.enableDamping = true
 
  
 +
== Helpers ==
 +
=== Nützliche Renderer Settings ===
 +
<syntaxhighlight lang="javascript">
 
/**
 
/**
 
  * Renderer
 
  * Renderer
 
  */
 
  */
 
const renderer = new THREE.WebGLRenderer({
 
const renderer = new THREE.WebGLRenderer({
     canvas: canvas
+
     canvas: canvas,
 
})
 
})
 
renderer.setSize(sizes.width, sizes.height)
 
renderer.setSize(sizes.width, sizes.height)
 
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
 
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
 
+
renderer.setClearColor('#262837')
 +
</syntaxhighlight>
 +
=== Nützliche Animation Settings ===
 +
<syntaxhighlight lang="javascript">
 
/**
 
/**
 
  * Animate
 
  * Animate
Zeile 1.033: Zeile 787:
 
     const elapsedTime = clock.getElapsedTime()
 
     const elapsedTime = clock.getElapsedTime()
  
     // Update objects
+
     // Update material (used for own Shader only)
     sphere.rotation.y = 0.1 * elapsedTime
+
     material.uniforms.uTime.value = elapsedTime
    cube.rotation.y = 0.1 * elapsedTime
 
    torus.rotation.y = 0.1 * elapsedTime
 
  
    sphere.rotation.x = 0.15 * elapsedTime
+
     // Update controls (used for orbit controls only)
    cube.rotation.x = 0.15 * elapsedTime
 
    torus.rotation.x = 0.15 * elapsedTime
 
 
 
     // Update controls
 
 
     controls.update()
 
     controls.update()
  
Zeile 1.053: Zeile 801:
  
 
tick()
 
tick()
 +
</syntaxhighlight>
 +
=== Orbit Controls ===
 +
Don't forget to update in tick function, when animating
 +
<syntaxhighlight lang="javascript">
 +
// Controls
 +
const controls = new OrbitControls(camera, canvas)
 +
controls.enableDamping = true
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Optimization Helpers ==
+
=== Optimizations ===
=== Materialien und Geometrien wiederverwenden ===
+
==== Materialien und Geometrien wiederverwenden ====
 
Das Erstellen von komplexen Objekten kann zeit- und speicherintensiv sein.
 
Das Erstellen von komplexen Objekten kann zeit- und speicherintensiv sein.
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Zeile 1.081: Zeile 836:
 
console.timeend('donuts')
 
console.timeend('donuts')
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
=== Nützliche Schnipsel ===
 +
==== Objekte auf Ringbahn positionieren ====
 +
<syntaxhighlight lang="javascript">
 +
// Graveyard
 +
const graves = new THREE.Group()
 +
scene.add(graves)
 +
const graveGeometry = new THREE.BoxGeometry(0.6, 0.8, 0.2)
 +
const graveMaterial = new THREE.MeshStandardMaterial({color: '#b2b6b1'})
 +
 +
for (let i = 0; i  < 50; i++) {
 +
    const min = 4 // minimaler Radius
 +
    const max = 8.5 // maximaler Radius
 +
    const radius = min + Math.random() * (max-min)// Radius zwischen min und max
 +
    const angle = Math.random() * 2*Math.PI // 0 < angle < 2PI (voller Kreis im Bogenmaß)
 +
    // (Bogen)Winkel in x,z Koordinaten umrechnen. Ohne * radius wäre Abstand 1
 +
    const x = Math.sin(angle) * radius
 +
    const z = Math.cos(angle) * radius
 +
    const y = 0.35
 +
   
 +
    const grave = new THREE.Mesh(graveGeometry, graveMaterial)
 +
    grave.position.set(x,y,z)
 +
    // Setze Grabsteine leicht schief und verdreht
 +
    grave.rotation.y = (Math.random() - 0.5) * 0.4
 +
    grave.rotation.z = (Math.random() - 0.5) * 0.3
 +
   
 +
    graves.add(grave)
 +
}
 +
</syntaxhighlight>
 +
 +
==== Zufallsarrays für Particles sinnvoll aufbauen ====
 +
<syntaxhighlight lang="javascript">
 +
for(let i = 0; i < firefliesCount; i++)
 +
{
 +
    positionArray[i * 3 + 0] = Math.random() * 4
 +
    positionArray[i * 3 + 1] = Math.random() * 4
 +
    positionArray[i * 3 + 2] = Math.random() * 4
 +
}
 +
console.log(positionArray)
 +
</syntaxhighlight>
 +
 +
=== Cool Colors ===
 +
<pre>
 +
#ac8e82 - brown walls
 +
#a9c388 - green night grass
 +
#89c854 - green bush
 +
#b35f45 - greek roof red
 +
#b2b6b1 - grey tombstone
 +
#b9d5ff - blue moonlight
 +
#ff7d49 - orange warm light
 +
#262837 - blueish fog
 +
</pre>

Aktuelle Version vom 17. Februar 2022, 01:39 Uhr

Links[Bearbeiten]

ThreeJS
Three.js - Particles
Three.js - Shaders

Basics[Bearbeiten]

/**
 * Test cube
 */
 const cube = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshBasicMaterial()
)
scene.add(cube)

Helfer[Bearbeiten]

Axes Helper[Bearbeiten]

Koordinatenachsen anzeigen

const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );

Viewport Settings[Bearbeiten]

Handle Viewport Resizing[Bearbeiten]

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)[Bearbeiten]

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[Bearbeiten]

// 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[Bearbeiten]

Three.js - Animation
Three.js - Scroll Based Animation

Timebased Tick / Loop Function[Bearbeiten]

ThreeJS Clock Object[Bearbeiten]

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[Bearbeiten]

// 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[Bearbeiten]

Kreisbewegung / Circular Movement[Bearbeiten]

myObject.position.y = Math.sin(elapsedTime) //(-1 -> 1 -> -1 -> ...)
myObject.position.x = Math.cos(elapsedTime)

Cursor auswerten[Bearbeiten]

// 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[Bearbeiten]

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.

Bouncing Sphere + Shadow[Bearbeiten]

    // Update the sphere
    sphere.position.x = Math.cos(elapsedTime) * 1.5
    sphere.position.z = Math.sin(elapsedTime) * 1.5
    sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))

    // Update the shadow accordingly
    sphereShadow.position.x = sphere.position.x
    sphereShadow.position.z = sphere.position.z
    sphereShadow.material.opacity = (1 - sphere.position.y) * 0.3

Orbit Controls[Bearbeiten]

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[Bearbeiten]

Create Geometry / Geometry Objekt erzeugen[Bearbeiten]

Three.js - eigene Geometrie 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[Bearbeiten]

lil-gui[Bearbeiten]

// https://lil-gui.georgealways.com/#
import GUI from 'lil-gui'; 
/**
 * Debug
 */
const gui = new GUI({
    width:400
})
gui.close()
//...
// 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...
// ... and update material when this param changed:
gui.addColor(params,'color')
.onChange( ()=>
{
    material.color.set(params.color)
})
gui.add(params, 'spin')

Beispiel: Renderer ClearColor

// Debug
const params = {}
const gui = new dat.GUI({width: 400})
params.clearColor = '#2b4945'
//...
renderer.setClearColor(params.clearColor)
gui.addColor(params, 'clearColor').onChange( () => {renderer.setClearColor(params.clearColor)})

Textures[Bearbeiten]

https://threejs.org/docs/index.html?q=texture#api/en/constants/Textures
three.js - Textures - ausführliche Infos zu TextureLoader Callbacks, LoadingManager...
three.js - baked map texture

Beispiel[Bearbeiten]

/**
 * 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 doorNormalTexture = textureLoader.load('/textures/door/normal.jpg')
const doorMetalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const doorRoughnessTexture = textureLoader.load('/textures/door/roughness.jpg')

// HOW TO REPEAT TILES - uv wrapping
const repeat = 10;

const grassColorTexture = textureLoader.load('/textures/grass/color.jpg')
const grassNormalTexture = textureLoader.load('/textures/grass/normal.jpg')
const grassRoughnessTexture = textureLoader.load('/textures/grass/roughness.jpg')

grassColorTexture.repeat.set(repeat,repeat)
grassNormalTexture.repeat.set(repeat,repeat)
grassRoughnessTexture.repeat.set(repeat,repeat)

grassColorTexture.wrapS = THREE.RepeatWrapping
grassNormalTexture.wrapS = THREE.RepeatWrapping
grassRoughnessTexture.wrapS = THREE.RepeatWrapping

grassColorTexture.wrapT = THREE.RepeatWrapping
grassNormalTexture.wrapT = THREE.RepeatWrapping
grassRoughnessTexture.wrapT = THREE.RepeatWrapping

//...

/** 
 * Objects
 */

// Door
const door = new THREE.Mesh(
    new THREE.PlaneGeometry(2,2,100,100),// HEIGHTMAP NEEDS SOME SUBDIVISIONS
    new THREE.MeshStandardMaterial({
        map: doorColorTexture,
        transparent: true, // NEEDED FOR ALPHA TO WORK
        alphaMap: doorAlphaTexture,
        aoMap: doorAmbientOcclusionTexture,
        displacementMap: doorHeightTexture,
        displacementScale: 0.05,
        normalMap: doorNormalTexture,
        metalnessMap: doorMetalnessTexture,
        roughnessMap: doorRoughnessTexture,
        //wireframe: true
    })
)
// AOMAP NEEDS HIS OWN UV ATTRIBUTE (here we copy from geometry)
door.geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(door.geometry.attributes.uv.array, 2))

house.add(door)

// Floor
const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(20, 20),
    new THREE.MeshStandardMaterial({ 
        //color: '#a9c388',
        map: grassColorTexture,
        normalMap: grassNormalTexture,
        roughnessMap: grassRoughnessTexture    
    })
)

floor.rotation.x = - Math.PI * 0.5
floor.position.y = 0

scene.add(floor)

Materials[Bearbeiten]

three.js - materials - Mehr Info und weitere Materialien.

MeshBasicMaterial[Bearbeiten]

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

MeshMatcapMaterial[Bearbeiten]

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

MeshStandardMaterial[Bearbeiten]

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)
)

Environment Map[Bearbeiten]

Three.js - Environment Map (Panorama)
// 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)

Hinweise[Bearbeiten]

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[Bearbeiten]

Three.js - 3D Text
http://gero3.github.io/facetype.js/ - Konvertieren von Fonts nach Facetype

Fontloader[Bearbeiten]

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[Bearbeiten]

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)
    }
)

Lights[Bearbeiten]

Three.js - Lights

Light Starters[Bearbeiten]

Neutral Starter[Bearbeiten]

/**
 * Lights
 */
// Ambient light
const ambientLight = new THREE.AmbientLight('#ffffff', 0.5)
gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001)
scene.add(ambientLight)

// Directional light
const directionalLight = new THREE.DirectionalLight('#ffffff', 0.5)
directionalLight.position.set(4, 5, - 2)
gui.add(directionalLight, 'intensity').min(0).max(1).step(0.001)
gui.add(directionalLight.position, 'x').min(- 5).max(5).step(0.001)
gui.add(directionalLight.position, 'y').min(- 5).max(5).step(0.001)
gui.add(directionalLight.position, 'z').min(- 5).max(5).step(0.001)
scene.add(moonLight)

Moonlight Starter[Bearbeiten]

/**
 * Lights
 */
// Ambient light
const ambientLight = new THREE.AmbientLight('#b9d5ff', 0.5)
gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001)
scene.add(ambientLight)

// Directional light
const moonLight = new THREE.DirectionalLight('#b9d5ff', 0.5)
moonLight.position.set(4, 5, - 2)
gui.add(moonLight, 'intensity').min(0).max(1).step(0.001)
gui.add(moonLight.position, 'x').min(- 5).max(5).step(0.001)
gui.add(moonLight.position, 'y').min(- 5).max(5).step(0.001)
gui.add(moonLight.position, 'z').min(- 5).max(5).step(0.001)
scene.add(moonLight)

Shadows / Schatten[Bearbeiten]

Three.js - Shadows - Ausführliche Infos zu Schatten

Komplettes Beispiel[Bearbeiten]

//...
/**
 * Lights
 */
// Ambient light
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001)
scene.add(ambientLight)

// Directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight.position.set(2, 2, - 1)
gui.add(directionalLight, 'intensity').min(0).max(1).step(0.001)
gui.add(directionalLight.position, 'x').min(- 5).max(5).step(0.001)
gui.add(directionalLight.position, 'y').min(- 5).max(5).step(0.001)
gui.add(directionalLight.position, 'z').min(- 5).max(5).step(0.001)

// Add Shadow and mapsize
directionalLight.castShadow = true
directionalLight.shadow.mapSize.x = 1024
directionalLight.shadow.mapSize.y = 1024
// Shadow camera settings...
directionalLight.shadow.camera.near = 1
directionalLight.shadow.camera.far = 6
// ...important for quality
directionalLight.shadow.camera.left = -2
directionalLight.shadow.camera.right = 2
directionalLight.shadow.camera.top = 2
directionalLight.shadow.camera.bottom = -2
// adding a bit of a cheap blur
// directionalLight.shadow.radius = 4

scene.add(directionalLight)

// Shadow camera helper
const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera)
scene.add(directionalLightCameraHelper)

// ...

/**
 * Objects
 */
sphere.castShadow = true
// ...
plane.receiveShadow = true
// ...
scene.add(sphere, plane)

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

// Renderer Shadowmap settings
renderer.shadowMap.enabled = true 
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap


Shadow Baking[Bearbeiten]

Wie Texturen kann man auch Schatten baken. Nachteil. Bei Bewegung des Objekts bewegt sich der

/**
 * Textures
 */
const textureLoader = new THREE.TextureLoader()
const bakedShadow = textureLoader.load('/textures/bakedShadow.jpg')
//...
const plane = new THREE.Mesh(
    new THREE.PlaneGeometry(5, 5),
    new THREE.MeshBasicMaterial({
        map: bakedShadow
    })
)
// we don't need the rendered shadows in this case
renderer.shadowMap.enabled = false

Dynamic Shadow Baking[Bearbeiten]

Beispiel Kugel mit animiertem Fake-Schatten

const textureLoader = new THREE.TextureLoader()
const simpleShadow = textureLoader.load('/textures/simpleShadow.jpg')

// Sphere Shadow
const sphereShadow = new THREE.Mesh(
    new THREE.PlaneGeometry(1.5, 1.5),
    new THREE.MeshBasicMaterial({
        color: 0x000000,
        transparent: true,
        alphaMap: simpleShadow
    })
)
sphereShadow.rotation.x = - Math.PI * 0.5
sphereShadow.position.y = plane.position.y + 0.01

scene.add(sphere, sphereShadow, plane)

//...

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update the sphere
    sphere.position.x = Math.cos(elapsedTime) * 1.5
    sphere.position.z = Math.sin(elapsedTime) * 1.5
    sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))

    // Update the shadow accordingly
    sphereShadow.position.x = sphere.position.x
    sphereShadow.position.z = sphere.position.z
    sphereShadow.material.opacity = (1 - sphere.position.y) * 0.3

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

Fog[Bearbeiten]

Three.js - Fog
//...
// Fog
const fog = new THREE.Fog('#262837', 1, 15)
scene.fog = fog 
//...
renderer.setClearColor('#262837')

Particles[Bearbeiten]

Benötigen eine Geometry, ein Material, ein Points Objekt (statt wie sonst ein Mesh)

Three.js - Particles

Starter Examples

/**
 * Particles
 */
// Geometry
const particlesGeometry = new THREE.SphereBufferGeometry(1,32,32)
// Material
const particlesMaterial = new THREE.PointsMaterial({
    size: 0.02,
    sizeAttenuation: true, //perspective smaller if far
})
// Points
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)

Random Points Starter

/**
 * Fireflies
 */
const firefliesGeometry = new THREE.BufferGeometry()
const firefliesCount = 30
const positionArray = new Float32Array(firefliesCount * 3)

for(let i = 0; i < firefliesCount; i++)
{
    positionArray[i * 3 + 0] = ( Math.random() -0.5) * 4.5 // x from -0.5 to 4
    positionArray[i * 3 + 1] = ( Math.random() + 0.05) * 3 // y from 0.05 to 2.95
    positionArray[i * 3 + 2] = ( Math.random() ) * 4.5 // z from 0 to 4.5
}

firefliesGeometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3))
const firefliesMaterial = new THREE.PointsMaterial({ size: 0.1, sizeAttenuation: true })
const fireflies = new THREE.Points(firefliesGeometry, firefliesMaterial)

scene.add(fireflies)

Models Importieren[Bearbeiten]

Three.js - Import Models

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
//...
const gltfLoader = new GLTFLoader()
gltfLoader.load(
    '/models/FlightHelmet/glTF/FlightHelmet.gltf',
    (gltf) => { 
        console.log(gltf) //success - show what we got
        const children = [...gltf.scene.children] // duplicate
        for(const child of children){
            scene.add(child)
        }
        //scene.add(gltf.scene)
        //scene.add(gltf.scene.children[1])
    },
    () => {console.log('progress')},
    () => {console.log('error')}
)

DRACO Loader nutzen[Bearbeiten]

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
//...
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/") // get webassembly version (faster but you have to provide the appropriate draco files)

const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load(
    '/models/Duck/glTF-Draco/Duck.gltf',
    (gltf) => { 
        scene.add(gltf.scene)
    }
)

Starters[Bearbeiten]

Three.js - Starters

Helpers[Bearbeiten]

Nützliche Renderer Settings[Bearbeiten]

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor('#262837')

Nützliche Animation Settings[Bearbeiten]

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update material (used for own Shader only)
    material.uniforms.uTime.value = elapsedTime

    // Update controls (used for orbit controls only)
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

Orbit Controls[Bearbeiten]

Don't forget to update in tick function, when animating

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

Optimizations[Bearbeiten]

Materialien und Geometrien wiederverwenden[Bearbeiten]

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')

Nützliche Schnipsel[Bearbeiten]

Objekte auf Ringbahn positionieren[Bearbeiten]

// Graveyard
const graves = new THREE.Group()
scene.add(graves)
const graveGeometry = new THREE.BoxGeometry(0.6, 0.8, 0.2)
const graveMaterial = new THREE.MeshStandardMaterial({color: '#b2b6b1'})

for (let i = 0; i  < 50; i++) {
    const min = 4 // minimaler Radius
    const max = 8.5 // maximaler Radius
    const radius = min + Math.random() * (max-min)// Radius zwischen min und max
    const angle = Math.random() * 2*Math.PI // 0 < angle < 2PI (voller Kreis im Bogenmaß)
    // (Bogen)Winkel in x,z Koordinaten umrechnen. Ohne * radius wäre Abstand 1
    const x = Math.sin(angle) * radius
    const z = Math.cos(angle) * radius
    const y = 0.35
    
    const grave = new THREE.Mesh(graveGeometry, graveMaterial)
    grave.position.set(x,y,z)
    // Setze Grabsteine leicht schief und verdreht
    grave.rotation.y = (Math.random() - 0.5) * 0.4
    grave.rotation.z = (Math.random() - 0.5) * 0.3 
    
    graves.add(grave)
}

Zufallsarrays für Particles sinnvoll aufbauen[Bearbeiten]

for(let i = 0; i < firefliesCount; i++)
{
    positionArray[i * 3 + 0] = Math.random() * 4
    positionArray[i * 3 + 1] = Math.random() * 4
    positionArray[i * 3 + 2] = Math.random() * 4
}
console.log(positionArray)

Cool Colors[Bearbeiten]

#ac8e82 - brown walls
#a9c388 - green night grass
#89c854 - green bush
#b35f45 - greek roof red
#b2b6b1 - grey tombstone
#b9d5ff - blue moonlight
#ff7d49 - orange warm light
#262837 - blueish fog