지오메트리를 해석하면 '기하학'으로 검색된다. 유클리드 이름까지 나오는데, 뭔가 복잡해질 것 같으니 그냥 '도형들;'이라고 생각해두쟈~ 사각형부터 도너츠까지 geometry라는 이름으로 각종 도형들을 쉽게 만들 수 있다. 그런데 왜? three.js나 기타 3D 프로그램에서는 사각형, 구, 도넛과 같은 도형을 자동으로 생성하도록 설계했을까?
`귀찮아서..`
위의 사진을 보면, 정육면체는 여덟 개의 점(vertex)을 갖고 있다. 아니,, 사진을 안 봐도 알겠지만, 확실한 건, 만일 정육면체를 3D 프로그래밍으로 만든다면 8개의 벡터값을 하나하나 찍어야 한다는 사실이다. 귀찮다. 물론 createBox( )같은 함수 하나 생성해서, 계속 넣으면 되겠지만.. 3D 프로그램이나 API에 있는 기본 도형은 진짜 귀찮음을 고려한 일종의 배려가 아닐까 한다.
기본 도형들 중에서 내가 좋아하는 도넛을 위주로 한번 생성해보자. 도넛 기본도형을 진짜 'new THREE.Donuts'라고 작성하면 안 된다. 도넛의 공식 이름은 'torus'다.
const createDonut = () => {
let geometry = new THREE.TorusGeometry(1,0.5,5,30);
let material = new THREE.MeshBasicMaterial({ color:0xffffff*Math.random() })
let donut = new THREE.Mesh(geometry,material);
donut.position.x = randomRange(-15,15);
donut.position.z = randomRange(-15,15);
donut.position.y = 15;
scene.add(donut)
donuts.push(donut);
}
|
[ trackballTest.html]
기본 도형을 만들 때는 항상 '지오메트리'와 '메트리얼(재질)'을 지정해줘야 한다. 그 이유는 아래 사진에 있다.
지오메트리에 어떠한 Material을 씌우느냐에 따라 쇠공이 되기도 고무공이 되기도 한다. 또한 텍스처와 재질(Material)에 따라서 지오메트리는 변한다. 그래서 new THREE.Mesh([지오메트리], [재질])를 실행한다.
원리를 대략 파악했따면, 본격적으로 도넛을 활용한 간단한 테스트를 진행해보자.
1 구조
코딩에 앞서 구조를 생각해보자. 간단한 코드도 실력을 쌓으려면, 항상 대략적인 구조를 정리할 필요가 있다.
1. 변수 선언 : 프로그램에 따라 변해야 하는 것들 |
2. 중요 함수 선언 [랜덤 함수, 지오메트리 생성함수] : 프로그램의 성격에 따라 변하는 내용 |
3. init 함수 선언 [카메라,랜더러, domElement 설정] : 공통적으로 반드시 들어가야 할 내용 |
4. 루프 함수 선언 [화면에서 동적으로 계속 업데이트 되는 내용] |
5. init() , render()[재귀] 선언 |
변수는 아래와 같이 정의한다.
<script type="module"> let scene, camera, renderer, trackballCtrl;
import TrackballControls from 'https://cdn.jsdelivr.net/npm/three-trackballcontrols@0.9.0/index.min.js'
const donuts = [];
const add = 0.1;
let clock;
let webgl_BG = document.getElementById('webgl-geo');
const randomRange = (from,to) =>{
let x = Math.random() * (to - from);
return x + from;
}
</script> |
[ 변수선언]
3D 장면에 필수적인 변수들(씬, 카메라, 랜더러)을 선언하고, 도넛을 배열로 선언한다. 그리고 랜덤 속도를 위한 'add'값을 변수로 선언한다. 1번의 변수 선언 부분은 거의 변하지 않고 계속 사용된다. 3D 장면에서 없어서는 안 될 부분이기 때문이다.
2 지오메트리 생성
이제 본격적으로 도넛을 생성해보자. 이 부분은 상황에 따라 다른 geometry를 넣어도 상관없다. 하지만 이번 포스팅에는 맛있는 도넛을 넣어보자.
<script type="module">
// 중략... (변수선언)
const createDonut = () => {
let geometry = new THREE.TorusGeometry( 1, 0.5, 5, 30);
let material = new THREE.MeshBasicMaterial({ color:0xffffff * Math.random() })
let donut = new THREE.Mesh(geometry, material);
donut.position.x = randomRange(-15, 15);
donut.position.z = randomRange(-15, 15);
donut.position.y = 15;
scene.add(donut)
donuts.push(donut);
}
</script>
|
[ 도넛 생성]
let으로 선언한 배열 'donuts'에 'donut'이란 프로퍼티명으로 차곡차곡 쌓자. ES6에서 변경 or 추가된 여러가지 배열메서드는 무작위로 뭔가를 생성해야 하는 3D 프로그래밍에서 요긴하게 사용할 수 있다. 특수효과 구현을 위해서라도 배열 메서드 부분을 정확하게 알아놓자.
3 init( )
init()함수는 전체 구조에서 '척추'역할을 한다. 다른 부분은 상황에 따라 변경이 발생하지만 init함수의 내용은 거의 변경이 발생하지 않는다.
<script type="module">
// 중략... const init = () => {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 20, -10);
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(
window.innerWidth * 0.375,
window.innerHeight * 0.375
);
webgl_BG.appendChild(renderer.domElement);
trackballCtrl = new TrackballControls(camera, renderer.domElement);
clock = new THREE.Clock()
}
</script>
|
[ trackballTest.html]
4 반복
도넛이 1프레임마다 계속 움직이려면 루프 함수가 필요하다. 루프함수는 곧 재귀를 의미한다. 루프 함수에서는 움직임의 범위나 속도와 같은 '애니메이팅'을 제어한다. 자동차로 생각한다면 '엔진'이다.
<script type="module">
// 중략... const render = () => {
let x = Math.random();
if(x < 0.1) createDonut();
donuts.forEach( dunkin => dunkin.position.y -= add );
trackballCtrl.update(clock.getDelta());
renderer.render(scene,camera);
requestAnimationFrame(render);
}
init();
render();
</script>
|
[ trackballTest.html]
'let x'변수에 random값을 넣은 이유는, 랜덤 초마다 한번씩 도넛을 표현하기 위해서다. 매초 하나씩 도넛이 등장하는 장면은 재미가 없다. 특수효과의 기초가 될 수 있는 위의 코드에서 가장 중요한 부분은 'donuts.forEach(함수)'구문이다. forEach(함수x)에서 함수x의 파라미터를 지정한 뒤 프로미스 구문과 결합했을 때, 엄청난 양의 데이터들을 3D로 시각화할 수 있기 때문이다.
전체 코드를 실행해보면 아래처럼 도넛이 뚝뚝 떨어지는 장면을 볼 수 있다.
5 전체코드
전체코드는 아래와 같다.
<!DOCTYPE html> <html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trackball Test</title>
</head>
<body>
<h1>TrackballTEst Three.js</h1>
<div id="webgl-geo"></div>
<script type="module">
let scene, camera, renderer, trackballCtrl;
import TrackballControls from 'https://cdn.jsdelivr.net/npm/three-trackballcontrols@0.9.0/index.min.js'
const donuts = [];
const add = 0.1;
let clock;
let webgl_BG = document.getElementById('webgl-geo');
const randomRange = (from,to) =>{
let x = Math.random() * (to - from);
return x + from;
}
const createDonut = () => {
let geometry = new THREE.TorusGeometry(1,0.5,5,30);
let material = new THREE.MeshBasicMaterial({ color:0xffffff*Math.random() })
let donut = new THREE.Mesh(geometry,material);
donut.position.x = randomRange(-15,15);
donut.position.z = randomRange(-15,15);
donut.position.y = 15;
scene.add(donut)
donuts.push(donut);
}
const init = () => {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 20, -10);
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(
window.innerWidth * 0.315,
window.innerHeight * 0.375
);
webgl_BG.appendChild(renderer.domElement);
trackballCtrl = new TrackballControls(camera, renderer.domElement);
clock = new THREE.Clock()
}
const render = () => {
let x = Math.random();
if(x < 0.1) createDonut();
donuts.forEach( dunkin => dunkin.position.y -= add );
trackballCtrl.update(clock.getDelta());
renderer.render(scene,camera);
requestAnimationFrame(render);
}
init();
render();
</script>
</body>
</html>
|
[ trackballTest.html]
'코드 스터디' 카테고리의 다른 글
자바스크립트 문법 (2) 배열 Method (0) | 2019.04.29 |
---|---|
three.js 9편 "토성 만들기" (0) | 2019.04.29 |
three.js 7편 scene 메서드 정리(안개 만들기) (0) | 2019.04.27 |
웹기획 (4) 벤치마킹 (0) | 2019.04.26 |
웹기획 (3) 웹기획 당위성 찾기 & 시장분석 (0) | 2019.04.26 |
댓글