Recstasy 2023. 1. 12. 23:05

『rayCast 구현』

 

 

 

 

canvas로 그래픽을 구현할 때는 x와 y축 두 개의 좌표값을 사용한다. 하지만 3D는 원근감을 표현해야 하기에, 깊이감에 따라서 화면이 달라져야 한다. 이때 raycast는 z값을 반환함으로써 2차원 좌표를 3차원으로 계산해준다.

 

 

 

[ raycast ]

 

x, y축에서 보면 윗 그림의 사면체와 원기둥이 겹친다. 3차원과 2차원의 차이점은 z축이다. 평면적인 화면에 입체감을 만들고, 실사처럼 연출하도록 만드는 값은 'z값'이며, raycast는 라이팅(광원)에서 오브젝트들(원기둥,사면체)에 부딪치는 거리를 통해 z축을 계산한다. 라이트의 광원과 오브젝트들이 부딪치는 거리를 통해 카메라에서 오브젝트들간의 거리(z축)를 알 수 있다.

 

three.js에서는 Raycast()를 활용하여 z축을 계산한다.

 

 

 

 

그러므로 rayCaster()를 사용하려면 먼저 ".setFromCamera( mouse, camera )"를 통해 (오브젝트와 광원)거리값을 계산해줘야 한다. 이후, raycast가 활성화 되면, ".intersectObjects()"메서드를 통해 화면에서 광원과 부딪치는 모든 오브젝트를 알아낼 수 있다. 

 

 

 

 

 


| 배경 설정

구현하려는 장면의 스토리는 다음과 같다.

 


1) 구와 정사면체를 생성한다.

2) 마우스를 클릭했을 때 '구'와 '정사면체'의 색상이 녹색으로 변한다.

3) 평소 정사면체와 구의 색상은 각각 파란색과 빨간색이다.


 

배경은 최소한으로 설정해준다. 아래와 같이 구와 박스를 생성하는 오브젝트 함수와 init()을 구현한다. 

 

let scene, camera, renderer, light1, light2, rayCast, control, mouse;
    let sphere, cube;
    let ele = document.getElementById('webdolRayCaster');


    let createGeometry = function () 
    {
        let geometry = new THREE.BoxGeometry(4, 4, 4);
        let material = new THREE.MeshLambertMaterial({
            color: 0xabcdef
        });
    
        cube = new THREE.Mesh(geometry, material);
        cube.position.set(0, 2, 3);
    
        scene.add(cube);
    
        geometry = new THREE.SphereGeometry(4, 30, 30);
        material = new THREE.MeshLambertMaterial({
            color: 0xabcdef
        });
    
        sphere = new THREE.Mesh(geometry, material);
        sphere.position.set(5, 2, 3);
    
        scene.add(sphere);
    }

    let init = function () {
    
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0x000000);
    
        camera = new THREE.PerspectiveCamera(75, ele.clientWidth / ele.clientHeight, 0.01, 10000);
        camera.position.set(5, 10, 15);
        camera.lookAt(scene.position);
    
        light1 = new THREE.DirectionalLight(0xffffff, 1);
        light2 = new THREE.DirectionalLight(0xffffff, 1);
        light2.position.set(0, -5, 2);
    
        scene.add(light1);
        scene.add(light2);
    
        createGeometry();
    
        rayCast = new THREE.Raycaster();
    
        mouse = new THREE.Vector2();
        mouse.x = mouse.y = -1;
    
        renderer = new THREE.WebGLRenderer();
        renderer.setSize( ele.clientWidth, ele.clientHeight );

        control = new OrbitControls( camera, renderer.domElement );
        control.update();
    
        ele.appendChild( renderer.domElement );
        renderer.domElement.addEventListener( "click", onMouseClick, false);
    }

 

 

rayCast 변수에 three.Raycaster()를 선언한 후, 마우스는 2차원 벡터값으로 x, y 좌표를 설정한다. webGL에서 원점을 사분면의 좌측 하단(-1, -1) 로 인식하기에 '-1'로 설정해준다. 

 

 

 

 

 

 


| 마우스 이벤트 설정

마우스를 클릭했을 때의 반응을 만들어보자. click이벤트에서 설정한 onMouseClick함수는 다음 두 가지 데이터를 요구한다.

 

 

1) 사용자의 마우스 좌표값

2) rayCast.setFromCamera( camera, mouse )

 

 

위에서 언급했듯, rayCast()는 ".setFromCamera()메서드"를 통해 깊이값을 추출한다. 따라서 camera와 mouse를 파라미터에 넣어야 한다. DOM내의 좌표값을 '-1 ~ 1'좌표로 변환하는 과정은 아래와 같다.

 

    let onMouseClick = function (e) {
    
        let gapX = e.clientX - e.offsetX;
        let gapY = e.clientY - e.offsetY;

        mouse.x = ((e.clientX - gapX) / ( ele.clientWidth )) * 2 - 1;
        mouse.y =  - ((e.clientY - gapY) / ( ele.clientHeight )) * 2 + 1;
        rayCast.setFromCamera( mouse, camera );

    }

 

 

 

 

 

 

 


| 루프

루프 함수는 마우스를 클릭했을 때 '구'와 '정사면체'의 색상을 설정한다. 우선 정사면체와 구의 색상은 각각 파란색과 빨간색이며, rayCast값이 있을 경우(광원과 충돌이 발생한 오브젝트 존재) 해당 요소의 색상을 녹색으로 변경한다.

 

    let mainLoop = function () {
    
        sphere.material.color.set( 0x0450fb );
        cube.material.color.set( 0xff4500 );
    
        let intersects = rayCast.intersectObjects( scene.children );
    
        intersects.forEach( obj => obj.object.material.color.set(0x00ff00) );
    
        requestAnimationFrame( mainLoop );
        renderer.render( scene, camera );

    }

 

 

mainLoop에 의해, '구'와 '박스'의 색상은 매 프레임마다 빨간색과 파란색을 갖는다. 만일, 마우스 이벤트가 발생하면 intersects에 scene의 하위객체가 생성되며, 해당 material의 색상은 녹색(0x00ff00)이 된다. 또, 오브젝트가 아닌 빈 공간에 마우스 이벤트가 발생한다면, intersects는 null값을 갖는다. 그 결과, sphere와 box는 다시 처음 상태(파란색과 빨간색)가 된다. 

 

 

전반적으로 간단한 코드지만 rayCast를 이해하기에 좋은 예제다.