예전에 학교다닐때 졸업작품으로 opengl 을 사용해서 프로젝트를 진행한 적이 있었는데... 그때 생각이 나서 인프런 1분코딩 Three.js 강의 를 들으면서 정리하고 웹게임을 만들어 볼려고 한다.
아래 요소들을 모두 작성한 후에 renderer.render() 를 해주면 된다.
얘를 그냥 화면을 뿌려주는 tv 라고 생각하자. html 에 canvas 랑 연동해서 사용하는게 좋다고 하네
//이렇게 canvas 에 연결해서 사용하는게 나중에 좀 더 좋다고 한다.
const canvas = document.querySelector('#three-canvas');
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias:true,//계단현상 제거 속성
});
renderer.setSize(window.innerWidth, window.innerHeight);
무대라고 생각하자 여기에 이제 지형, 캐릭터, 카메라 ... 이런 것들을 추가하면 된다.
//Scene 생성
const scene = new THREE.Scene();
무대(scene) 를 바라보는 카메라의 위치를 생성해준다. scene.add() 를 해주면 된다.
//Camera 생성
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth/window.innerHeight,
0.1,
1000
);
//카메라를 모니터에서 내가 앉아있는 방향으로 (z축으로 양수만큼 이동)
camera.position.z = 5;
camera.position.y = 2;
camera.position.x = 1;
//Scene 에 카메라를 추가시켜준다.
scene.add(camera);
얘는 캐릭터, 건물, 지형, ... 이런거라고 생각하면 되는데, 프레임을 나타내는 geometry 와 재질을 나타내는 material 로 나뉘어 진다.
//Mesh 생성
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
});
const mesh = new THREE.Mesh(geometry, material);
//얘도 scene 에 추가해준다.
scene.add(mesh);
EventListener 에 아래 함수를 넣어준다. 창이 변경될 때마다 setSize() 를 호출해줘서 render 를 다시 해준다. 창 크기가 변경되니까 기본적으로... 카메라의 aspect 가 변경이 필요하다. 카메라의 설정이 바뀌는 경우는 updateProjectionMatrix() 를 호출해줘야한다.renderer 에 윈도우 창 크기 관련된 설정들도 변경해줘야하고
//이벤트 감지해서 동적 화면 변경 대응
function setSize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();//카메라의 변화가 있을 때 실행해줘야 하는 메서드
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
}
window.addEventListener('resize',setSize);
요즘 고해상도 모니터들이 많이 나오기 때문에 좀더 부드러운 설정을 위해서 다음과 같이 설정해준다. 보통 2정도만 되도 충분하다고 한다. 그래서 max 로 2로 설정해준다.
pc 의 픽셀 비율이 만약 2 라고 출력되면 100px 이미지를 표현할 때 200px 을 사용한다.
console.log(window.devicePixelRatio);
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);//2만되어도 충분하기 때문에 이렇게 함
renderer 를 우리가 보는 tv 화면이라고 생각하고 scene를 실제 무대라고 생각! scene의 배경색 관련해서 우선순위가 더 높다.
아래와 같이 코드를 작성하면 파랑색 배경이 나오고 renderer에 설정한 초록색 배경은 무시된다.
renderer.setClearAlpha(0.5);//투명 정도를 설정할 수 있다.
renderer.setClearColor(0x00ff00);//이렇게 배경색을 입힐 수도 있다.
//Scene 생성
const scene = new THREE.Scene();
//렌더러 (우리가 보는 tv화면) 보다 scene (실제 무대) 가 배경색에서 더 서선순위가 있다.
//렌더러 위에 scene가 덧칠을 했다고 생각하자
scene.background = new THREE.Color('blue');
Date.now() 를 이용해서 성능 보정에 사용할 수 있다. 단위가 밀리세컨드라 0.0001 을 곱해서 단위 보정이 필요
//js 의 date.now() 이용
let oldTime = Date.now();
function draw() {
const newTime = Date.now();
const deltaTime = newTime - oldTime;
oldTime = newTime;
//라디안 기반이라 3.14 * 2 = 360도를 나타냄
mesh.rotation.y += deltaTime * 0.001;//단위가 밀리초라서 되게 큰수가 나온다
renderer.render(scene, camera);
// window.requestAnimationFrame(draw);
renderer.setAnimationLoop(draw); //이거도 내부적으로 requestAnimationFrame 을 호출한다고 함
}
clock.getElapsedTime() 을 이용하면 실행 후 흐르고 있는 시간을 확인할 수 있다.
요걸 이용해서 빨리감기 같은걸 이용할 수 있을까?
//그리기
const clock = new THREE.Clock();
function draw() {
// console.log(clock.getElapsedTime());
const time = clock.getElapsedTime();//절대 경과 시간
//라디안 기반이라 3.14 * 2 = 360도를 나타냄
mesh.rotation.y = time;
renderer.render(scene, camera);
// window.requestAnimationFrame(draw);
renderer.setAnimationLoop(draw); //이거도 내부적으로 requestAnimationFrame 을 호출한다고 함
}
다음과 같이 안개를 설정할 수 있다. 랜덤값을 줘서 mesh 들을 생성해주고 scene 에 각각 추가해준다. 그리고 meshes에도 추가해주고 이 배열을 가지고 draw() 에서 활용한다.
//Scene 생성
const scene = new THREE.Scene();
scene.fog = new THREE.Fog('blue',3 , 7)//색,near,far
const meshes = [];
let mesh;
for (let i = 0; i < 10 ; i++) {
mesh = new THREE.Mesh(geometry, material);
mesh.position.x = Math.random() *5 -2.5;
mesh.position.z = Math.random() * 5 -2.5;
scene.add(mesh);
meshes.push(mesh);
}
//그리기
const clock = new THREE.Clock();
//js 의 date.now() 이용
let oldTime = Date.now();
function draw() {
const newTime = Date.now();
const deltaTime = newTime - oldTime;
oldTime = newTime;
meshes.forEach(item => {
item.rotation.y += deltaTime * 0.001;
})
renderer.render(scene, camera);
// window.requestAnimationFrame(draw);
renderer.setAnimationLoop(draw); //이거도 내부적으로 requestAnimationFrame 을 호출한다고 함
}
그냥 만들 수도 있을거 같은데... 매번하기 번거로우니까 참고하면 될듯
npm i stats.js 설치를 우선 하고 나서 Stats 객체 생성한 후에 body 에 stats.domElement 를 추가해준 후에 draw() 에서 계속 update를 해주면 된다.
//Stats
let stats = new Stats();
document.body.append(stats.domElement);
function draw() {
stats.update();//draw 에서 update 를 해주면 출력된다.
renderer.setAnimationLoop(draw);
}
npm i dat.gui 을 설치하고 아래와 같이 사용할 수 있다. 이렇게 하는게 테스트 할때 간편할 거 같기도 하고~
//Dat GUI
let gui = new dat.GUI();
gui.add(mesh.position, 'y', -5, 5, 0.01).name('mesh.y 이동');
gui.add(camera.position, 'y')
.min(-5)
.max(5)
.step(0.01)
.name('카메라.y 이동');
//원점으로부터 떨어진 거리
console.log(mesh.position.length());
//특정 좌표 사이의 거리
console.log(mesh.position.distanceTo(new THREE.Vector3(5, 0, 0)));
이거도 두가지 방식으로 가능
mesh.scale.set(2, 1, 1);
mesh.scale.y = 3;
회전도 두가지 방식으로 가능하고 회전하기 전에 reorder() 메서드를 통해서 어떤축을 먼저 회전할지를 정할 수 있다. default 는 xyz 순서로 회전하는듯? 나중에 회전이 생각처럼 안되면 한번 참고해보자
mesh.rotation.reorder('YXZ')
mesh.rotation.set(10, 20, 0);
mesh.rotation.reorder('YXZ');
mesh.rotation.x = 10;
mesh.rotation.y = 20;
그룹생성을 통해서 구현을 조금 더 쉽게 할 수 있는듯 태양,지구,달 을 만든다고 할때 각각의 위치와 회전을 제어하려고 하면 상당히 번거로울거 같은데, group 을 통해서 간단하게 할 수 있넹
group1 에 태양과 group2(지구,달) 이 있다고 하면 group1 을 회전시키면 group2 에 있는 매쉬들도 같이 회전이 되는걸 알 수 있다.
//group 생성
let group1 = new THREE.Group();
let group2 = new THREE.Group();
group2.position.x = 2;
let group3 = new THREE.Group();
group3.position.x = 0.5;
//매쉬 생성
const sun = new THREE.Mesh(geometry, material);
const earth = new THREE.Mesh(geometry, material);
earth.scale.set(0.3, 0.3, 0.3);
const moon = new THREE.Mesh(geometry, material);
moon.scale.set(0.15, 0.15, 0.15);
group3.add(moon);
group2.add(earth, group3);
group1.add(sun, group2);
scene.add(group1);
// 그리기
const clock = new THREE.Clock();
function draw() {
const delta = clock.getDelta();
group1.rotation.y += delta;
group2.rotation.y += delta * 3;
group3.rotation.y += delta * 6;
renderer.render(scene, camera);
renderer.setAnimationLoop(draw);
}