Skip to content

Commit e524954

Browse files
committed
✨ Add threejs journey notes 3 and 4 draft
1 parent f98ba88 commit e524954

2 files changed

Lines changed: 360 additions & 1 deletion

File tree

content/posts/threejs-journey-notes-3-advanced-techniques.md renamed to content/posts/threejs-journey-notes-3-advanced-techniques-part1.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
draft: false
3-
title: Three.js Journey Notes 3 - Advanced Techniques
3+
title: Three.js Journey Notes 3 - Advanced Techniques Part1
44
date: 2025-03-11
55
categories: Learning
66
comments: true
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
---
2+
draft: false
3+
title: Three.js Journey Notes 4 - Advanced Techniques Part2
4+
date: 2025-03-16
5+
categories: Learning
6+
comments: true
7+
ShowToc: true
8+
isCJKLanguage: false
9+
---
10+
11+
课程链接:[three.js journey](https://threejs-journey.com/)
12+
13+
[Notes 1 - Basics](../threejs-journey-notes-1-basics)
14+
[Notes 2 - Classic Techniques](../threejs-journey-notes-2-classic-techniques)
15+
[Notes 3 - Advanced Techniques Part1](../threejs-journey-notes-3-advanced-techniques-part1)
16+
17+
18+
## Raycaster and Mouse Events
19+
20+
Raycaster is used to detect if there is a wall in front of the player, or test if the laser
21+
gun hit something, test if something is currently under the mouse to simulate mouse events, etc.
22+
23+
```js
24+
/**
25+
* Raycaster
26+
*/
27+
const raycaster = new THREE.Raycaster()
28+
const rayOrigin = new THREE.Vector3(- 3, 0, 0)
29+
const rayDirection = new THREE.Vector3(10, 0, 0)
30+
rayDirection.normalize() // make sure vector is 1 unit long
31+
32+
raycaster.set(rayOrigin, rayDirection)
33+
34+
// get objects intersect
35+
const intersect = raycaster.intersectObject(object2)
36+
console.log(intersect) // result is also array with only 1 object
37+
38+
const intersects = raycaster.intersectObjects([object1, object2, object3])
39+
console.log(intersects)
40+
```
41+
42+
If we need to test on each frame, we need to test in tick function like:
43+
44+
```javascript
45+
const clock = new THREE.Clock()
46+
47+
const tick = () =>
48+
{
49+
// ...
50+
51+
// Cast a ray
52+
const rayOrigin = new THREE.Vector3(- 3, 0, 0)
53+
const rayDirection = new THREE.Vector3(1, 0, 0)
54+
rayDirection.normalize()
55+
56+
raycaster.set(rayOrigin, rayDirection)
57+
58+
const objectsToTest = [object1, object2, object3]
59+
const intersects = raycaster.intersectObjects(objectsToTest)
60+
console.log(intersects)
61+
62+
// ...
63+
}
64+
```
65+
66+
### Handle Hovering
67+
68+
cast a ray from camera in the mouse's direction
69+
70+
```javascript
71+
/**
72+
* Mouse
73+
*/
74+
// simulate a mouse
75+
const mouse = new THREE.Vector2()
76+
77+
window.addEventListener('mousemove', (event) =>
78+
{
79+
mouse.x = event.clientX / sizes.width * 2 - 1
80+
mouse.y = - (event.clientY / sizes.height) * 2 + 1
81+
82+
console.log(mouse)
83+
})
84+
85+
86+
const tick = () =>
87+
{
88+
// ...
89+
// set camera from ouse
90+
raycaster.setFromCamera(mouse, camera)
91+
92+
const objectsToTest = [object1, object2, object3]
93+
const intersects = raycaster.intersectObjects(objectsToTest)
94+
95+
for(const intersect of intersects)
96+
{
97+
intersect.object.material.color.set('#0000ff')
98+
}
99+
100+
for(const object of objectsToTest)
101+
{
102+
if(!intersects.find(intersect => intersect.object === object))
103+
{
104+
object.material.color.set('#ff0000')
105+
}
106+
}
107+
108+
// ...
109+
}
110+
```
111+
112+
`mouseenter``mouseleave` events need to detect by yourself like: you have to keep the currently intersecting objects, and when any of them are not in intersects, then it's `mouseleave` event; when there are new intersects, then it's `mouseenter`.
113+
114+
```javascript
115+
const tick = () =>
116+
{
117+
// ...
118+
raycaster.setFromCamera(mouse, camera)
119+
const objectsToTest = [object1, object2, object3]
120+
const intersects = raycaster.intersectObjects(objectsToTest)
121+
122+
if(intersects.length)
123+
{
124+
if(!currentIntersect)
125+
{
126+
console.log('mouse enter')
127+
}
128+
129+
currentIntersect = intersects[0]
130+
}
131+
else
132+
{
133+
if(currentIntersect)
134+
{
135+
console.log('mouse leave')
136+
}
137+
138+
currentIntersect = null
139+
}
140+
141+
// ...
142+
}
143+
```
144+
145+
### Handle Click
146+
147+
```javascript
148+
window.addEventListener('click', () =>
149+
{
150+
if(currentIntersect)
151+
{
152+
switch(currentIntersect.object)
153+
{
154+
case object1:
155+
console.log('click on object 1')
156+
break
157+
158+
case object2:
159+
console.log('click on object 2')
160+
break
161+
162+
case object3:
163+
console.log('click on object 3')
164+
break
165+
}
166+
}
167+
})
168+
```
169+
170+
### Cast on Loaded Model
171+
172+
cast again `gltf.scene`.
173+
174+
## Blender Beginner
175+
176+
Make custom models by hand using [Blender](https://www.blender.org/).
177+
178+
Blender shortcuts: [Blender 2.8 - Shortcuts - Google 文件](https://docs.google.com/document/d/1wZzJrEgNye2ZQqwe8oBh54AXwF5cYIe56EGFe2bb0QU/edit)
179+
180+
完全手把手带着做一个汉堡,我跟做的版本:
181+
182+
![](https://static.zhuzi.dev/threejs-journey/blender-hamburger-1.png)
183+
184+
![](https://static.zhuzi.dev/threejs-journey/blender-hamburger-2.png)
185+
186+
187+
## Environment Map
188+
189+
It’s those images surrounding the scene that can be used as a background, but also directly on the objects as reflection and lighting. Environment maps can be used to light up the whole scene in a very realistic manner.
190+
191+
### Cube Texture Environment Map
192+
193+
composed of 6 images (faces of cube)
194+
195+
```javascript
196+
/**
197+
* Loaders
198+
*/
199+
// ...
200+
const cubeTextureLoader = new THREE.CubeTextureLoader()
201+
202+
/**
203+
* Environment map
204+
*/
205+
// LDR cube texture
206+
// the order is corresponding to `positive x`, `negative x`, `positive y`, `negative y`, `positive z`, and `negative z`
207+
const environmentMap = cubeTextureLoader.load([
208+
'/environmentMaps/0/px.png',
209+
'/environmentMaps/0/nx.png',
210+
'/environmentMaps/0/py.png',
211+
'/environmentMaps/0/ny.png',
212+
'/environmentMaps/0/pz.png',
213+
'/environmentMaps/0/nz.png'
214+
])
215+
216+
// set the background
217+
scene.background = environmentMap
218+
// use the environment map to light up model
219+
scene.environment = environmentMap
220+
```
221+
222+
### Environment Properties
223+
224+
```javascript
225+
/**
226+
* Environment map
227+
*/
228+
// high intersity make it more light
229+
scene.environmentIntensity = 4
230+
231+
// blur the background
232+
scene.backgroundBlurriness = 0.2
233+
234+
// if the environment resolution is quite low or if you want the user to focus on the object in the foreground
235+
// this control background brightness
236+
scene.backgroundIntensity = 5
237+
238+
// control rotation
239+
scene.backgroundRotation.x = 1
240+
scene.environmentRotation.x = 2
241+
```
242+
243+
244+
### HDRI Equirectangular Environment Map
245+
246+
> HDR stands for "High Dynamic Range" (we often say "HDRI" , where the "I" stands for "Image").
247+
> It's only one picture containing kind of a 360° view of the surrounding. The proper name for such projection is "equirectangular".
248+
> An HDR environment map doesn't have to be equirectangular, but it’s often the case and Three.js (like most 3D software packages, libraries and engines) supports this projection.
249+
250+
much heavier to load and render than cube texture.
251+
252+
```javascript
253+
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
254+
255+
/**
256+
* Loaders
257+
*/
258+
// ...
259+
const rgbeLoader = new RGBELoader()
260+
261+
/ HDR (RGBE) equirectangular
262+
rgbeLoader.load('/environmentMaps/0/2k.hdr', (environmentMap) =>
263+
{
264+
environmentMap.mapping = THREE.EquirectangularReflectionMapping
265+
266+
scene.background = environmentMap
267+
scene.environment = environmentMap
268+
})
269+
```
270+
271+
### Generate an Environment Map
272+
273+
online environment map resources: [HDRIs • Poly Haven](https://polyhaven.com/hdris)
274+
275+
- using Blender, first draw the scene you want, and then render the scene and it can be saved as HDR.
276+
- AI generated, like [NVIDIA Canvas](https://www.nvidia.com/en-us/studio/canvas/)
277+
- or [Skybox Lab](https://skybox.blockadelabs.com/)
278+
279+
### Ground Projected Environment Map
280+
281+
```javascript
282+
// Ground projected skybox
283+
import { GroundedSkybox } from 'three/addons/objects/GroundedSkybox.js'
284+
rgbeLoader.load('/environmentMaps/2/2k.hdr', (environmentMap) =>
285+
{
286+
environmentMap.mapping = THREE.EquirectangularReflectionMapping
287+
scene.environment = environmentMap
288+
289+
// Skybox
290+
const skybox = new GroundedSkybox(environmentMap, 15, 70
291+
// skybox.material.wireframe = true
292+
skybox.position.y = 15 // move it down can make the object looks like it's on the ground
293+
scene.add(skybox)
294+
})
295+
296+
```
297+
298+
### Real-time Environment Map
299+
300+
create a dynamic environment map where we render the scene on each frame and use the result as the `environment` directly in Three.js.
301+
302+
main idea is that we are going to render the scene inside our own environment map texture and that it’s going to be a cube texture.
303+
To render into a cube texture, we need to use a [WebGLCubeRenderTarget](https://threejs.org/docs/index.html#api/en/renderers/WebGLCubeRenderTarget). Render targets are textures in which we can store renders of any scene.
304+
305+
```javascript
306+
/**
307+
* Real time environment map
308+
*/
309+
// Base environment map
310+
const environmentMap = textureLoader.load('/environmentMaps/blockadesLabsSkybox/interior_views_cozy_wood_cabin_with_cauldron_and_p.jpg')
311+
environmentMap.mapping = THREE.EquirectangularReflectionMapping
312+
environmentMap.colorSpace = THREE.SRGBColorSpace
313+
314+
scene.background = environmentMap
315+
316+
317+
// Cube render target
318+
// 256 is the resolution
319+
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
320+
type: THREE.HalfFloatType // or THREE.FloatType
321+
// format: THREE.RGBAFormat,
322+
// generateMipmaps: true,
323+
// minFilter: THREE.LinearMipmapLinearFilter
324+
})
325+
326+
scene.environment = cubeRenderTarget.texture;
327+
328+
// since we need to render one texture for each face of a cube, we need
329+
// render 6 square textures. can use CubeCamera
330+
331+
// Cube camera
332+
const cubeCamera = new THREE.CubeCamera(0.1, 100, cubeRenderTarget)
333+
cubeCamera.layers.set(1); // it should only see holyDonut by using layer
334+
335+
// at last update on tick
336+
337+
const tick = () =>
338+
{
339+
// ...
340+
341+
// Real time environment map
342+
if(holyDonut)
343+
{
344+
holyDonut.rotation.x = Math.sin(elapsedTime) * 2
345+
346+
cubeCamera.update(renderer, scene)
347+
}
348+
349+
// ...
350+
}
351+
```
352+
353+
The above implementation make the objects are blocking the light.
354+
To fix it, one can use [Layers](https://threejs.org/docs/index.html#api/en/core/Layers). Layers work like categories, setting layers on a camera, the camera will only see objects matching the same layers.
355+
356+
As an example, if a camera has its layers set to `1` and `2`, it’ll only see objects that have layers set to `1` or `2`.
357+
By default, all objects and camera layers are set to `0`.
358+
359+
Real-time environmenrt maps will be a log in terms of performance. So need to use smallest possible resoliton on the render target.

0 commit comments

Comments
 (0)