본문 바로가기

「three.js」웹에서 파편을 3D로 구현해보자

by Recstasy 2019. 5. 3.

 

흩어지거나 폭발하는 코딩은 게임 특수효과의 단골이다.

 

하지만 유니티나 언리얼 엔진 같은 게임엔진 덕분에 개발자가 직접 3D 효과를 코딩해야 하는 경우는 거의 없다. 단, 원리를 알고 있어야 응용이 가능하기에 이번 포스팅에서는 물리량 계산과 같은 방법에 관해 알아보자.

 

 

| 뼈대 파일 구축

let scene,camera,renderer;
let fragments = [] 

let createGeometry = funciton(){
}

let init = funciton(){
   //..중략
   createGeometry()
}

let render = function(){
    fragments.forEach(f => f.move()) // 배열 속 파편들이 움직임
   //.. 중략
}

 

구현 전에는 항상 시나리오를 미리 작성하는 편이 좋다. 여기서 시나리오는 "파편이 폭발하며 튀어나가는 장면"이다. 

 


1. init()함수 실행 :: 파편들이 화면에 나타남 [createGeometry()실행]

2. render()함수 실행 :: 파편들이 움직임 [fragments배열 속의 모든 데이터들에 움직임이 필요함]


 

위의 2번을 실행하려면, 우선 '가속도', '위치'와 같은 값들이 필요하다. 이와 관련된 Fragment클래스와 실행함수를 생성해준다.

 

class Fragment{
       constructor() { }
       move() 
       {
          //값 입력
       }
}

let createTriangle = function(){
}

let createGeometry = function(){
}

 

* createGeometry() : 1) 기능 : 3D 오브젝트를 생성 2) 필요항목 : Mesh정보(geometry,material)가 담긴 배열 

* Fragment클래스: 1) 기능 : 3D Mesh를 생성하고, 위치, 가속도 값, 파편 움직임 정의 2) 필요항목: geometry정보 

* createTriangle() : 1) 기능 : geometry를 생성하고 값을 반환  2) 필요항목 : 벡터값 3개

 

 

 

 

 

 구현

전체 코드가 꽤 길지만 하드코딩이므로 원리는 복잡하지 않다. 쉽게 말해, '8개의 삼각형 Mesh들이 튕겨져 나가는 씬'이다. 

 

let fragments = [];
let dt = 0.02;
let ADD = 0.05;

clsss Fragment{
      constructor(position, velocity, geometry){
           this.velocity = velocity;
           this.velocity.multplyScalar(dt);

           let material = new THREE.MeshPhongMaterial({
                     side : THREE.doubleSide,
                     color : 0xababab,
                     emissive : 0xfafafa,
                     emissiveIntensity : 0,4,
                     shininess : 100,
                     specular : 0x9d0a00,
                     vertexColors : true
          });

          this.shape = new THREE.Mesh(geometry,material);
          this.shape.position.copy(position);
     }

      move(){
          this.shape.position.add(this.velocity);
          this.shape.rotation.x += ADD;
      }

} 


let createTriangle = function(p1,p2,p3){
    let geometry = new THREE.Geometry();
        geometry.vertices.push(p1,p2,p3);
        geometry.faces.push(new THREE.Face3(0,1,2));
        geometry.computeFaceNormals();
        geometry.computeVertexNormals();
        return geometry;
}

let createGeometry = function(){
    let p1 = new THREE.Vector3(0,1,0);
    let p2 = new THREE.Vector3(1,0,1);
    let p3 = new THREE.Vector3(-1,0,1);
    let p4 = new THREE.Vector3(-1,0,-1);
    let p5 = new THREE.Vector3(1,0,-1);
    let p6 = new THREE.Vector3(0,-1,0);

//하드코딩
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(0,0,6),createTriangle(p1,p2,p3) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(-2,4,0),createTriangle(p1,p3,p4) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(0,5,-4),createTriangle(p1,p4,p5) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(2,3,0),createTriangle(p1,p5,p2) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(0,-5,3),createTriangle(p3,p2,p6) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(-4,-3,0),createTriangle(p6,p3,p4) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(0,-4,-4),createTriangle(p6,p4,p5) ) )
    fragments.push(new Fragment(new Vector3(0,0,0),new THREE.Vector3(3,-3,0),createTriangle(p6,p2,p5) ) )

    fragments.forEach( f=> scene.add(f.shape));

}

 

 

결론적으로 fragments라는 배열 속에 쌓여있는 3D Mesh들을 활용하는 코드다. 여기서 중요한 데이터는 위치값이며, Fragment 클래스가 이 부분을 책임진다. Fragment 클래스에 들어가는 geometry값을 받기 위해서 createTriangle()함수를 생성해서 geometry값을 반환한다. createGeometry()함수에 들어가는 위치값을 넣기 위해 p1 ~ p6까지 벡터값을 임의적으로 생성하며, fragments.forEach()에서 배열의 3D Mesh값을 하나씩 처리한다.

 

 

 

 

 

render()함수

render() 함수의 핵심은 Fragment클래스의 매서드 부분이다. move라는 매서드를 통해 파편을 움직이는 동작을 반복한다.

 

 fragments.forEach(f=>f.move());

 

init()함수에는 createGeometry()함수만 포함시키면 된다. p1 ~ p6까지의 값을 변경하면서 테스팅하다보면 재미있는 결과물을 얻을 수 있다. 

 

 

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일