[Three.js] Raycaster를 사용해서 물체 hover시 빙글빙글 돌리기
한 영역 안에서 만들어진 물체(Mesh)를 가만히 있는게 아니라 마우스를 가져다 댔을 떄, 빙글빙글 돌 수 있도록 만들어보는
연습을 해보았다.
마우스의 포인터를 가지고 현재 가리키고 있는 영역이나 물체를 알려줄 수 있는 Raycaster 클래스를 사용했다.
먼저 아래는 Three.js의 공식 문서에서 얘기하는 Raycaster
https://threejs.org/docs/index.html#api/en/core/Raycaster
three.js docs
threejs.org
Raycaster는 raycasting을 기반으로 만들어진 클래스다. Raycasting은 다른 여러 물체들 사이에서 마우스로 하나의 물체를 선택하는데 사용된다. (그러므로 무궁무진하게 많이 사용될수 있다!)
광선의 위치(마우스 포인터의 위치)에서 광선을 쏴서 광선이 닿는 물체를 감지하는 방법을 사용하는게 원리이다.
먼저 방과 오브젝트를 만들어보자.
import * as THREE from "three";
(() => {
const canvas = document.getElementById("canvas");
const renderer = new THREE.WebGLRenderer({ canvas });
//camera
const camera = new THREE.PerspectiveCamera(45, 2, 0.01, 300);
camera.position.set(0, 20, 50);
//scene
const scene = new THREE.Scene();
//light
const light = new THREE.PointLight("white", 1);
light.position.set(1, 120, 30);
scene.add(light);
//make floor
{
const floorSize = 100;
const geometry = new THREE.PlaneBufferGeometry(floorSize, floorSize);
const material = new THREE.MeshPhongMaterial({
color: "#d9d9d9",
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = Math.PI * -0.5;
scene.add(mesh);
}
//make wall
{
const wallSize = 100;
const geometry = new THREE.BoxBufferGeometry(wallSize, wallSize, wallSize);
const material = new THREE.MeshPhongMaterial({
color: "#FDF2E9",
side: THREE.BackSide
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, wallSize / 2 - 0.1, 0);
scene.add(mesh);
}
renderer.render(scene, camera);
})();
다음으로 오브젝트를 하나 만들어야한다. 무료로 obj 파일과 mtl 파일(재질 정보가 있는 파일)을 한번에 다운받을 수 있는 곳이 많아서
다운받으면 된다. (강아지가 최고이기 때문에 비글을 가지고 실습을 해보았다!)
let beagleObject;
{
//비글 오브젝트
const mtlLoader = new MTLLoader();
mtlLoader.load('obj/Beagle.mtl', (mtlParseResult) => {
const objLoader = new OBJLoader2();
const materials = MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult);
objLoader.addMaterials(materials);
objLoader.load('obj/Beagle.obj', (root) => {
root.scale.set(0.2, 0.2, 0.2);
root.position.set(10, 0, -40);
root.rotation.z = Math.PI * 0.2;
root.rotation.x = Math.PI * -0.5;
root.receiveShadow = true;
root.castShadow = true;
root.traverse(function (child) {
child.castShadow = true;
});
beagleObject = root;
scene.add(root);
});
});
}
rotation이나 scale이나 이런거는 obj파일을 직접 만들지 않았기 때문에 크기와 회전을 변경해줬다! (바닥에 닿게 하기 위해서)
먼저 준비가 되어야할 것은 마우스의 위치를 가져와야한다. 이건 mousemove라는 이벤트 리스터를 사용해서 움직일때 x와 y좌표를 받아서 데이터에 넣어주어야한다.
const mouse = new THREE.Vector2();
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 - 1;
}
window.addEventListenr('mousemove', onMouseMove, false);
마우스의 좌표를 정규화하는 형식으로 진행되는 건데 캔버스의 크기와 상관없이 왼쪽 끝이 -1, 오른쪽 끝이 +1인 벡터값이 필요하기 때문이다. 마찬가지로 아래쪽 끝은 -1, 위쪽 끝은 +1 으로 생각하면 된다.
이제 raycaster 클래스를 선언하고 raycaster를 적용할 대상을 넣을 빈 배열을 하나 선언한다,
const targetObject = [];
const raycaster = new THREE.Raycaster();
그리고 위에 썼던 renderer.render(scene, camera) 영역을 빙글빙글 돌게 하는건 캔버스에서 모션을 주기 위해서 requestAnimationFrame을 적용하기 위해 render라는 함수에 넣고 이를 호출하는 방식으로 수정을 해야한다.
function render() {
requestAnimationFrame(render);
}
requestAnimationFrame(render);
귀여운 비글의 object가 로드가 되면(얘는 비동기임!) 만들어뒀던 targetObject에 추가를 한다(여기가 raycaster를 적용할 대상을 넣은 배열)
function render() {
if (beagleObject && !targetObject.includes(beagleObject.children[0])) {
targetObject.push(beagleObject.children[0]);
}
requestAnimationFrame(render):
}
여기서 beagleObject.children[0]을 한 이유는 로드되는 오브젝트파일이 사람마다 만드는 방식이 다르기 때문에 실제 형태를 담고 있는 객체를 찾아야한다. 그래서 children[0]으로 해줘야한다.
그 다음 raycaster의 setFromCamera로 마우스와 카메라를 raycaster에 쓰기 위한 준비를 해준다.
raycaster.setFromCamera(mouse, camera);
raycaster의 intersectObjects라는 함수를 사용해서 targetObject에 있는 아이들 중에 마우스 포인터로 선택된 오브젝트를 특정 변수에 넣는다.
const intersects = raycaster.intersectObjects(targetObject);
그리고 intersects를 루프 돌려서 rotation을 돌리면된다.
intersects.map((target) => {
target.object.rotation.z += Math.PI * 0.005;
});
그러면 render 함수안의 내용은
function render() {
if(beagleObject && !targetObject.includes(beagleObject.children[0])){
targetObject.push(beagleObject.children[0]);
}
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(targetObject);
intersects.map((target) => {
target.object.rotation.z += Math.PI * 0.005;
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
그렇게 되면 최종적으로 마우스를 대면 빙글빙글 돌아가는 귀여운 비글이 만들어지게 된다.
아래는 요 비글이 실제로 실행되면 나타나는 그림이다!
(따로 빼서 정리는 나중에 해야겠다..! 귀여워 비글....)