도움말 - 글감 수집하기 (인용)

도움말 - 부분 리뷰 작성하기

ThreeJS를 배워보자 – Chapter 1

Packt사에서 나온 괜찮은 ThreeJS 교재를 발견했다. 대충 둘러보니 처음 배우기에 아주 괜찮아 보였다.

그래서 그걸 따라하며 여기에 도큐멘팅을 한다.

Chapter 1 – ThreeJS로 첫번째 씬을 만들어 보자.

우선 기본 html파일부터 작성해보자.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ThreeJS Practice 1</title>
<script type="text/javascript" src="libraries/three.js"></script>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<!-- Div for the threeJS scene -->
<div id="threejs_scene"></div>
<!-- My threeJS Javascript codes here -->
<script type="text/javascript" src="script.js"></script>
</body>
</html>

head안에 three.js라이브러리를 넣어놨고, 간단하게 css를 써서 웹브라우저 전체를 두루 쓸 수 있도록 body의 마진을 0으로 잡고 threeJS가 보일 canvas를 100%로 잡았다. 그리고나서 body안에 ThreeJS가 보일 div코드에 id를 threejs_scene으로 이름지어서 자리를 만들어놨고 바디가 끝나기 전에 앞으로 만들 script을 실행하도록 넣어놨다.

자! 이제 기본index.html셋팅은 끝났다.

이제 script.js를 열고 멋진 three.js씬을 만들어 나가보자.

처음 ThreeJs 시작하기 포스팅을 하며 배웠던, 3D Scene을 구현하기 위한 최소 세가지, Scene, Camera, 그리고 Renderer부터 정의내려보자.

function init() {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(80, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xEEEEEE);
renderer.setSize(window.innerWidth, window.innerHeight);
//Show Axis
var axes = new THREE.AxisHelper(10);
scene.add(axes);
//Let's make a plane
var planeGeometry = new THREE.PlaneGeometry(60,30,1,1);
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xCCCCCC});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
scene.add(plane);
camera.position.x = 0;
camera.position.y = 30;
camera.position.z = 30;
camera.lookAt(scene.position);
document.getElementById("threejs_scene").appendChild(renderer.domElement);
renderScene();
function renderScene() {
renderer.render(scene,camera);
}
}
window.onload = init();

제일 처음 작성한 코드를 보면,

웹페이지에서 모든걸 다 로딩하면 한번에 뜰 수 있게 모든 코드를 function init()안에 묶었고 제일 마지막줄에 window.onload = init; 을 넣었다.

init() 펑션안에서 첫줄부터 세번째줄까지는 ThreeJS의 가장 기본요소 Scene, Camera 그리고 Renderer를 정의하면서, 카메라는 80도의 화각을 가지고 브라우저 창에 맞춘 aspect ratio를 맞추게 한 ThreeJS의 가장 기본카메라인 perspective camera를 썼고, 렌더러는 WebGL랜더러를 사용하도록 했다.

그 다음 6번째 줄에서 씬 전체의 배경색을 아주 약한 그레이로 만들었고, 7번째줄에서는 랜더러의 사이즈를 브라우저 창에 맞게 정했다.

우선 카메라가 어디를 보고있는지를 도와줄 xyz좌표를 넣기위해 10번째 줄에서 ThreeJS의 AxisHelper를 넣었다. AxisHelper괄호안의 숫자는 각 xyz좌표의 길이를 정해준다. 그리고 11번째 줄에서 우리가 볼 수 있게 씬에다가 위에정의한 axes를 추가해주었다. 참고로 좌표의 색은 x축은 Red, y축은 Green 그리고 z축은 Blue이다. xyz = rgb로 외우면 쉽다. 이제 우리가 보는 뷰가 거꾸로 뒤집혔는지, 세로로 보고있는지 쉽게 알 수 있게 됬다.

이제 바닥판 하나를 만들어서 중앙에 위치시켜보자. THREE.PlaneGeometry(60,30,1,1)를 이용해서 가로 60, 세로 30짜리 크기의 가로세로 모두 1의 세그먼트를 가진 Plane 지오메트리를 만들고 MeshBasicMaterial를 사용해서 약한 회색의 메터리얼을 적용시키자. 디폴트로는 플래인이 세로로 벽처럼 서있기때문에 -180도 회전시켜서 바닥에 위치하도록 만들자. 180도가 아니라 -180도로 한 이유는 모든 3D 지오메트리들이 그렇듯, 디폴트로는 한면만 랜더를 하여 보이기때문에 180도로 돌리면 안보이는 면으로 돌리게 되어서 반대로 180도를 돌리자. 만약 양면모두 랜더를 하고싶다면, 메터리얼을 정할 때 다음과 같이 더블사이드로 지정해 주면된다: MeshBasicMaterial({color:0xCCCCCC, side: THREE.DoubleSide}); 하지만 괜히 더블사이드로 랜더를 하게 만들어 괜히 컴터에 부하를 더 주지말고 간단하게 -180도로 돌리도록 하자.

다음으로 정 중앙에 위치해서 아무것도 못보는 우리의 카메라를 밖으로 위치시켜보자.

20번째부터 22번째 줄까지 카메라를 y축으로 30만큼, 그리고 z축으로 30만큼 이동시켰고 23번째 줄에서 카메라가 쳐다보는 곳은 Scene에서 0,0,0 즉 정중앙을 쳐다보도록 했다.

이제 만든 이 모든것을 HTML에서 만들어놓은 div태그중 id를 threejs_scene이라고 이름지어준 곳에 들어가서 캔버스를 펼 쳐 주도록 25번째 줄처럼 추가시킨다.

그리고 난후 이 모든것을 랜더 시키기 위해 renderScene()이라고 이름지은 function안에 지금까지 만든 scene과 camera를 집어넣은 랜더러를 넣고 renderScene()을 실행시키도록 하자.


자 이제 기본적인 판이 만들어 졌다.

다음으로 가운데에 빙글빙글 돌고 있는 큐브와 통통 튀는 공 하나를 만들어 보겠다.

//Let's make a cube  
var cubeGeometry = new THREE.BoxGeometry(6,6,6);
var cubeMeterial = new THREE.MeshBasicMaterial({color: 0x0089A0});
var cube = new THREE.Mesh(cubeGeometry, cubeMeterial);
cube.position.x = 0;
cube.position.y = 10;
cube.position.z = 10;
scene.add(cube);
//Let's make a sphere
var sphereGeometry = new THREE.SphereGeometry(4,32,32);
var sphereMeterial = new THREE.MeshBasicMaterial({color: 0xFE98A0});
var sphere = new THREE.Mesh(sphereGeometry, sphereMeterial);
sphere.position.x = -15;
sphere.position.y = 2;
sphere.position.z = 0;
scene.add(sphere);

위와같이 중앙에 큐브하나, 왼쪽편으로 공 하나를 넣었다.


내가 쫌 이런 컬러들을 좋아해서 컬러를 다 저렇게 만들어 놨다. 아무튼, 이제 저 큐브는 빙글빙글 돌게 만들어 주도록 하자.

아래의 코드를 renderScene() 펑션안에다가 넣어 주도록 하자.

requestAnimationFrame(renderScene); 
//cube animation
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
cube.rotation.z += 0.01;

첫줄인 requestAnimationFrame(renderScene); 을 삽입함으로서, 프레임이 계속해서 흘러가도록 만들어주고, 그 다음 코드들을 넣어줌으로서 각각의 방향으로 0.01씩 매 프레임마다 돌게 만들어 줬다. 쉽다!!


다음으로 공을 통통 튀겨보도록 하자.

우선, 숫자가 계속해서 더해질 변수를 step이라고 이름짓고 init()펑션 안쪽 처음 시작하는 부분 어딘가에 선언해주고,

var step = 0;

에니메이션이 이루어질 renderScene펑션 안쪽에 다음과 같이 써주자.

step += 0.1;
sphere.position.y = 9 + (5 * Math.cos(step));

step 은 공이 튈 속도를 정하게 되고, cosine함수 (그렇다. 우리가 수학시간에 배웠던 그 코사인함수이다. 그래프가 어떻게 되는지는 이 사이트를 참조해 보도록 하자!) 를 이용해서 공이 아래위로 스무스하게 튀게 만들어 주는데, 앞에 5를 곱해줌으로서 공이 5만큼 위,아래로 튀게 만들어줬고, 앞에 5만큼 튀어오르니까 5를 더하고, 구의 반지름으로 정한만큼, 그러니까 4만큼 더해서 올려주어서 바닥에서 부터 공이 위로 10까지 튀게 만들어 주었다.


이제 큐브는 빙글빙글 돌고있고 공은 통통 튀고있다. 하지만 뭔가 그림이 좀 평면적이다. 그림자가 없어서이다.

자 이제 조명을 하나 넣어주고 그림자를 넣어줘서 조금 더 입체감 있게 만들어보자!

우선 Spot조명을 하나 넣어보자.

var spotLight = new THREE.SpotLight(0xFFFFFF);
spotLight.position.set(-40,60,30);
scene.add(spotLight);

조명을 넣었지만 아무 변화가 없다. 그이유는 조명을 받아주는 곳, 그림자를 받아주는 곳을 지정해 줘야 하기 때문이다. 그리고 우선 그이전에 우리가 쓴 MeshBasicMaterial은 그림자를 생성하지 못한다. 그래서 매터리얼도 MeshLambertMaterial이나 MeshPhongMaterial타입의 매터리얼로 바꿔줘야 한다. 우리는 여기서 이름이 좀 더 친숙한 Phong매터리얼로 바닥판, 큐브, 그리고 공의 매터리얼들을 바꿔주도록 하자.


벌써 뭔가 입체감이 생기기 시작한다.

이제 그림자를 생성시키기 위해 필요한 작업들을 해보자.

우선 renderer의 배경색과 사이즈을 정해준 코드 바로 밑에 한줄 더 추가해주자.

renderer.shadowMap.enabled = true;

그리고 그림자를 받을 바닥판에는 receiveShadow를 그림자가 생성될 쉐입인 큐브와 공과 조금전에 만든 spot조명에 모두 castShadow를 지정해주도록 하자.

. . .
plane.receiveShadow = true;
. . .
cube.castShadow = true;
. . .
sphere.castShadow = true;
. . .
spotLight.castShadow = true;

자, 이제 그림자가 생기고 훨씬더 삼차원공간같이 느껴진다.


하지만 그림자가 무슨 깍두기마냥 각지게 생겼다.. 그 이유는 그림자의 mapSize가 디폴트로 512 x 512로 지정되어 있기 때문인데 그 해상도를 높여주면 부드러워지게 된다. 현재는 그리 랜더링에 부하를 주는게 없으니 통크게 10배크기의 mapSize로 지정해서 보도록 하자. 아래의 코드를 아까 spotLight쪽에 추가했던 castShadow 밑에다가 써주도록 하자.

spotLight.shadow.mapSize.width = 5120;
spotLight.shadow.mapSize.height = 5120;


그림자가 훨씬 부드러워 졌다.

이제 다음편에서는 지금 보고있는 브라우저의 Frame rate을 확인할 수 있는 조그마한 창과 실시간으로 값을 변경할 수 있는 GUI를 추가하는 법을 배워보겠다.

그리고 복습겸 한번 지금까지 위에서 설명했던것을 참조로 하여 혼자 쓸쓸히 튀고있는 공 오른쪽에 엇박자로 튀는 공 하나를 더 만들어 보도록 해보자.


지금까지 만든 script.js 전체 코드는 아래에 있다.

function init() {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(80, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
//For bouncing balls;
var step = 0;
renderer.setClearColor(0xEEEEEE);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
//Show Axis
var axes = new THREE.AxisHelper(10);
scene.add(axes);
//Let's make a plane
var planeGeometry = new THREE.PlaneGeometry(60,30,1,1);
var planeMaterial = new THREE.MeshPhongMaterial({color: 0xCCCCCC});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -0.5 * Math.PI;
scene.add(plane);
//Let's make a cube
var cubeGeometry = new THREE.BoxGeometry(6,6,6);
var cubeMeterial = new THREE.MeshPhongMaterial({color: 0x0089A0});
var cube = new THREE.Mesh(cubeGeometry, cubeMeterial);
cube.castShadow = true;
cube.position.x = 0;
cube.position.y = 10;
cube.position.z = 10;
scene.add(cube);
//Let's make a spheres
var sphereGeometry = new THREE.SphereGeometry(4,32,32);
var sphereMeterial = new THREE.MeshPhongMaterial({color: 0xFE98A0});
var sphere = new THREE.Mesh(sphereGeometry, sphereMeterial);
sphere.castShadow = true;
sphere.position.x = -15;
sphere.position.y = 4;
sphere.position.z = 0;
scene.add(sphere);
var sphereMeterial2 = new THREE.MeshPhongMaterial({color: 0xFEE721});
var sphere2 = new THREE.Mesh(sphereGeometry, sphereMeterial2);
sphere2.castShadow = true;
sphere2.position.x = 15;
sphere2.position.y = 4;
sphere2.position.z = 0;
scene.add(sphere2);
var spotLight = new THREE.SpotLight(0xFFFFFF);
spotLight.position.set(-40,60,30);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 5120;
spotLight.shadow.mapSize.height = 5120;
scene.add(spotLight);
camera.position.x = 0;
camera.position.y = 30;
camera.position.z = 30;
camera.lookAt(scene.position);
document.getElementById("threejs_scene").appendChild(renderer.domElement);
// renderScene();
var renderScene = new function renderScene() {
requestAnimationFrame(renderScene);
//cube animation
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
cube.rotation.z += 0.01;
//sphere animation
step += 0.1;
sphere.position.y = 9 + (5 * Math.cos(step));
sphere2.position.y = 9 + (5 * Math.cos(step+3));
renderer.render(scene,camera);
}
}
window.onload = init();

리뷰