π» Ray Casting μ΄λ?
Ray Casting
μ κ°μμ κ΄μ (ray)μ μ΄μ, μ΄λ€ 물체μ κ΅μ°¨νλμ§ νμΈνλ κΈ°μ μ
λλ€. ν΄λΉ κΈ°λ²μ μ¬μ©νμ¬, μΆ©λ κ°μ§, 물체μ μ ν λ° μνΈμμ©, κ°λ €μ§ λ©΄ νλ³λ±μ ꡬνν μ μμ΅λλ€.
π» Raycaster λ§λ€κΈ°
Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float )
three.js
μμ Raycaster
λ μ μμ±μλ₯Ό μ΄μ©ν΄μ λ§λ€ μ μμ΅λλ€. μμ±μ κ°κ°μ μΈμλ€μ λ€μκ³Ό κ°μ΅λλ€.
origin
Ray
(κ΄μ )κ° μμλλ μ’νλ‘Vector3
νμμ κ°μ΅λλ€.direction
Ray
μ λ°©ν₯μ λνλ΄λVector3
μ’νλ‘, λ°λμnormalize
ν¨μλ₯Ό νΈμΆν΄ μ κ·ν μμΌμΌ ν©λλ€.near
μ΅μκ°μ μ§μ νλ μΈμλ‘, λ°νλλ λͺ¨λ κ²°κ³Όλ€μnear
μμ μ€μ ν κ°λ³΄λ€ ν½λλ€.
κΈ°λ³Έκ°μ0
μ΄λ©°, μμλ λ μ μμ΅λλ€.far
μ΅λκ°μ μ§μ νλ μΈμλ‘, κ²°κ³Όλ€μfar
μ κ°λ³΄λ€ ν΄ μ μμ΅λλ€.
κΈ°λ³Έκ°μ 무νλ μ λλ€.
1
2
3
4
5
6
7
// ...μλ΅...
const rayOrigin = new THREE.Vector3(-5, 0, 0);
const rayDirection = new THREE.Vector3(10, 0, 0);
rayDirection.normalize();
const raycaster = new THREE.Raycaster(rayOrigin, rayDirection);
// ...μλ΅...
μ μ½λλ μμμ μ΄ (-5, 0, 0)
μ΄λ©° λ°©ν₯μ΄ (10, 0, 0)
μ ν₯νλ ray
λ₯Ό λ§λ μμμ
λλ€.
rayDirection
μ΄ λ¨μ벑ν°κ° μλλ―λ‘ normalize
λ₯Ό μ€νν κ²μ λ³Ό μ μμ΅λλ€.
RayCaster
λ₯Ό μμ±ν λ, μμ±μκ° μλ set
ν¨μλ₯Ό μ΄μ©νμ¬ λ§λ€ μλ μμ΅λλ€.
1
2
3
4
5
6
7
8
9
// ...μλ΅...
const raycaster = new THREE.Raycaster();
const rayOrigin = new THREE.Vector3(-5, 0, 0);
const rayDirection = new THREE.Vector3(10, 0, 0);
rayDirection.normalize();
rayCaster.set(rayOrigin, rayDirection);
// ...μλ΅...
π» Raycasterλ₯Ό νμ©νμ¬ κ΅μ°¨νλ 물체 μ°ΎκΈ°
Raycaster
μ ray
μ κ΅μ°¨νλ 물체λ₯Ό μ°ΎκΈ° μν΄μλ, intersectObject
μ intersectObjects
λ©μλλ₯Ό μ΄μ©ν΄μΌ ν©λλ€. λ ν¨μμ μ°¨μ΄λ ray
μ κ΅μ°¨λ¨μ νλ³νλ λ¬Όμ²΄κ° λ¨μμ΄λ, 볡μμ΄λ μ
λλ€.
intersectObject(object: Object3D, recursive: Boolean, optionalTarget: Array): Array
intersectObject
λ©μλ μΈμλ€μ μλ―Έλ μλμ κ°μ΅λλ€.
object
Ray
μ κ΅μ°¨λλμ§λ₯Ό νλ³νObject3D
μΈμ€ν΄μ€ μ λλ€recursive
true
λ‘ μ€μ ν κ²½μ°,ray
μobject
μΈμμ λͺ¨λ μμλ€μ΄ κ΅μ°¨νλμ§λ₯Ό νλ³ν©λλ€. κ·Έλ μ§ μμ κ²½μ°,object
μΈμλ§ νλ³ν©λλ€.
κΈ°λ³Έκ°μtrue
μ λλ€.optionalTarget
λ°νλ κ²°κ³Όλ₯Ό λ€λ₯Έ λ°°μ΄μ μ μ₯νκ³ μΆμ λ, μ§μ νλ μ΅μ μ λλ€.
λ§μ½ ν΄λΉ μΈμμ λ°°μ΄μ λ겨주μλ€λ©΄, νΈμΆμλ§λ€ ν΄λΉ λ°°μ΄μ λΉμμΌ ν©λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ...μλ΅...
const mesh1 = new THREE.Mesh(
boxGeometry,
new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff })
);
mesh1.position.x = -1;
const mesh2 = new THREE.Mesh(
boxGeometry,
new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff })
);
const mesh3 = new THREE.Mesh(
boxGeometry,
new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff })
);
mesh3.position.x = 1;
// ...μλ΅...
const rayOrigin = new THREE.Vector3(-5, 0, 0);
const rayDirection = new THREE.Vector3(10, 0, 0);
rayDirection.normalize();
const raycaster = new THREE.Raycaster(rayOrigin, rayDirection);
const intersect = raycaster.intersectObject(mesh1);
// ...μλ΅...
μ μ½λλ, κ°κ° (-1, 0, 0)
, (0, 0, 0)
, (1, 0, 0)
μ μμΉνλ λλΉ 0.5
μΈ μ μ‘면체 μΈκ° μ€, μ μΌ μΌμͺ½μ μ μ‘면체μ (-5, 0, 0)
μμ (10, 0, 0)
λ°©ν₯μΌλ‘ ν₯νλ ray
μμ κ΅μ°¨μ§μ μ ꡬνλ μμμ
λλ€.
intersect
μ κ²°κ³Όλ₯Ό μ½μμ μΆλ ₯ν΄λ³΄λ©΄ λ€μκ³Ό κ°μ΄ λμ΅λλ€.
κ²°κ³Όκ° λκ°μμ μ μ μμ΅λλ€. μ΄λ segment
μ΅μ
μ μ€μ νμ§ μκ³ μ μ‘면체λ₯Ό μμ±ν λ, νλ©΄μ λκ°μ μΌκ°ν face
λ‘ κ΅¬μ±λμ΄ μλλ°, ray
κ° λκ°μ μΌκ°νμ΄ λ§λΏμ κ³³μ ν΅κ³ΌνκΈ° λλ¬Έμ
λλ€. Segment
λ₯Ό λ³κ²½νκ²λλ©΄, κ²°κ³Όκ° λ¬λΌμ§ μ μμ΅λλ€.
μμ μ΄ν΄λ³΄μλ―, intersectObject
μ κ²°κ΄κ°μ λ°°μ΄μ΄λ©°, ray
μ μμμ κ³Όμ κ±°λ¦¬κ° κ°κΉμ΄ μμΌλ‘ μ λ ¬λ©λλ€. μμμ κ°μ²΄ μμ±μ μλ―Έλ λ€μκ³Ό κ°μ΅λλ€.
distance
Ray
μ μΆλ°μ κ³Ό κ΅μ°¨μ κ³Όμ 거리λ₯Ό λνλ΄λ μμ±μ λλ€.
μ μμμμ,ray
μ μΆλ°μ μ(-5, 0, 0 )
μ΄λ©° κ΅μ°¨λλ μ§μ μ μ’νλ(-1.25, 0, 0)
(μ€μ¬μ μ’νκ°(-1, 0, 0)
μ΄κ³ , νλ³μ κΈΈμ΄κ°0.5
μ΄κΈ° λλ¬Έ),3.75
κ° λμμ΅λλ€.point
κ΅μ°¨μ μ μ’νλ₯Ό μλ―Έν©λλ€.face
κ΅μ°¨λλ λ©΄(face
) μ μ 보λ₯Ό λνλ λλ€.faceIndex
κ΅μ°¨λλ λ©΄μindex
λ₯Ό λνλ λλ€.object
Ray
μ κ΅μ°¨λmesh
μ μ 보λ₯Ό λνλ λλ€.uv
κ΅μ°¨μ μuv
μ’νλ₯Ό μλ―Έν©λλ€.uv1
κ΅μ°¨μ μμμ λλ²μ§Έuv
μ’νλ₯Ό μλ―Έν©λλ€.normal
κ΅μ°¨μ μμμnormal
λ°±ν°λ₯Ό λνλ λλ€.instanceId
Ray
μInstancedMesh
κ° κ΅μ°¨ν λ, ν΄λΉ μΈμ€ν΄μ€μindex
λ²νΈλ₯Ό λνλ λλ€.
intersectObject
λ₯Ό μ¬μ©ν λ, μ£Όμν μ μ mesh
μ face
κ° ray
μ μμμ μ ν₯ν΄ μμ΄μΌ νλ€λ μ μ
λλ€.
1
2
3
4
5
6
7
8
9
// ...μλ΅...
const rayOrigin = new THREE.Vector3(-1, 0, 0);
const rayDirection = new THREE.Vector3(10, 0, 0);
rayDirection.normalize();
const raycaster = new THREE.Raycaster(rayOrigin, rayDirection);
// ...μλ΅...
const intersect = raycaster.intersectObject(mesh1);
// ...μλ΅...
Ray
μ μμμ’νλ₯Ό μ μΌ μΌμͺ½ μ μ‘면체μ λ΄λΆλ‘ μ§μ ν΄ λ³΄κ² μ΅λλ€.
μ μ½λμμ intersect
λ₯Ό μΆλ ₯ν΄λ³΄λ©΄, λΉλ°°μ΄μ΄ μΆλ ₯λ©λλ€. Ray
μ κ΅μ°¨νλ face
κ° μμμ μ΄ μλ λ°λ λ°©ν₯μ ν₯νκ³ μκΈ° λλ¬Έμ
λλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ material
μ side
μ΅μ
μ THREE.DoubleSide
λ‘ μ€μ νλ©΄ λ©λλ€.
π» Raycaster λ₯Ό νμ©νμ¬ μμ§μ΄λ 물체 κ΅μ°¨μ μ°ΎκΈ°
μμ§μ΄λ 물체μ κ΅μ°¨μ μ μ°ΎκΈ°μν΄μλ νλ μλ§λ€ intersectObject
λ₯Ό μ€νν΄ μ£Όμ΄μΌ ν©λλ€.
μμ΄λ νλ BoxGeometry
κ° ray
μ κ΅μ°¨ν λ, μμ΄ λ³νλ μ½λλ₯Ό μμ±ν΄ λ³΄κ² μ΅λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ...μλ΅...
const clock = new THREE.Clock();
function animate() {
const elapsedTime = clock.getElapsedTime();
mesh1.material.color.set("red");
mesh1.position.x = Math.sin(elapsedTime);
mesh1.position.y = Math.cos(elapsedTime);
const intersect = raycaster.intersectObject(mesh1);
for (const intersectMesh of intersect) {
intersectMesh.object.material.color.set("blue");
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// ...μλ΅...
μ μ½λλ₯Ό μ€νν κ²°κ³Όλ λ€μκ³Ό κ°μ΅λλ€.
π» Raycaster λ₯Ό νμ©νμ¬ ν¬μΈν°λ‘ 물체 μνΈμμ©νκΈ°
Raycaster
λ₯Ό μ΄μ©νμ¬, ν¬μΈν° (PC μμλ λ§μ°μ€)μ λ¬Όμ²΄κ° μνΈμμ©νλλ‘ κ΅¬ννλ λ°©λ²μ μμλ³΄κ² μ΅λλ€.
μνΈμμ©νκΈ° μν΄μλ, ν¬μΈν°μ μ’νκ° νμνλ° ν½μ
μ’νκ° μλ -1
μμ 1
μ¬μ΄μ μ κ·νλ μ’νκ° νμν©λλ€.
1
2
3
4
5
6
7
8
9
10
// ...μλ΅...
const pointerCoord = new THREE.Vector2();
canvas.addEventListener("pointermove", (e) => {
const { offsetX, offsetY } = e;
pointerCoord.x = (offsetX / window.innerWidth) * 2 - 1;
pointerCoord.y = -((offsetY / window.innerHeight) * 2 - 1);
});
// ...μλ΅...
μ μ½λλ ν¬μΈν°μ μ’νλ₯Ό -1
κ³Ό 1
μ¬μ΄μ κ°μΌλ‘ μ κ·ν μν€λ μ½λμ
λλ€.
ν¬μΈν°μ x
μ’νλ₯Ό μΊλ²μ€μ λμ΄λ‘ λλκ² λλ©΄, κ·Έ λ²μλ 0
κ³Ό 1
μ¬μ΄κ° λ©λλ€. μ¬κΈ°μ 2
λ₯Ό κ³±ν ν 1
μ λΉΌκ²λλ©΄ -1
κ³Ό 1
μ΄ λ©λλ€.
y
μ’νμ -1
μ κ³±ν μ΄μ λ μμͺ½ λ°©ν₯μ΄ μμμ΄κΈ° λλ¬Έμ
λλ€.
μ΄μ Ray
λ₯Ό μ κ·νλ μ’νλ₯Ό ν₯νλλ‘ λ§λ€μ΄μΌ νλλ°, μ΄λ μ¬μ©νλ λ©μλκ° setFromCamera
μ
λλ€. μ΄ ν¨μλ μμμ μ μΉ΄λ©λΌμ μμΉλ‘, λ°©ν₯μ ν¬μΈν°λ‘ νλ ray
λ₯Ό μμ±νλ ν¨μμ
λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ...μλ΅...
let INTERSECTED = null;
function animate() {
// ...μλ΅...
raycaster.setFromCamera(pointerCoord, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
if (INTERSECTED !== intersects[0].object) {
if (INTERSECTED) INTERSECTED.material.color.set(INTERSECTED.currentColor);
INTERSECTED = intersects[0].object;
INTERSECTED.currentColor = new THREE.Color(INTERSECTED.material.color);
INTERSECTED.material.color.set("yellow");
}
} else {
if (INTERSECTED) {
INTERSECTED.material.color.set(INTERSECTED.currentColor);
}
INTERSECTED = null;
}
}
// ...μλ΅...
μ μ½λλ ν¬μΈν°λ°©ν₯μΌλ‘ ν₯νλ ray
μ κ²ΉμΉλ mesh
κ° μμ λ, μμ λ
ΈλμμΌλ‘ λ³κ²½νκ³ , ν¬μΈν°κ° λ€λ₯Έ 물체λ₯Ό κ°λ₯΄ν€κ±°λ, μ무κ²λ κ°λ₯΄ν€μ§ μμλ μλμ μμΌλ‘ λμκ°κ²λ λμνλ μ½λμ
λλ€.