Three.js 태양계 만들어보기

👍·2022년 5월 31일
0

1. 만들 것

  • 태양계
  • 태양계의 라벨

2. 코드

import * as THREE from "../build/three.module.js";
import { OrbitControls } from "../examples/jsm/controls/OrbitControls.js";
import {
	CSS2DRenderer,
	CSS2DObject,
} from "../examples/jsm/renderers/CSS2DRenderer.js";

class App {
	#domContainer;
	#renderer;
	#scene;
	#camera;
	#control;
	#solarSystem;
	#earthOrbit;
	#moonOrbit;
	#labelRenderer;

	constructor() {
		// DOM요소 참조
		const domContainer = document.querySelector("#webgl-container");
		this.#domContainer = domContainer;

		// 렌더러 설정
		const renderer = new THREE.WebGLRenderer({ antialias: true });
		renderer.setPixelRatio(window.devicePixelRatio); // 화소설정 : px하나를 화소 하나로 본다. 고해상도의 경우 2이상의 값이 나옴.
		domContainer.appendChild(renderer.domElement);
		this.#renderer = renderer;

		// 라벨 렌더러 설정
		const labelRenderer = new CSS2DRenderer();
		labelRenderer.domElement.style.cssText = `
			position: absolute;
			top: 0px; 
		`;
		domContainer.appendChild(labelRenderer.domElement);
		this.#labelRenderer = labelRenderer;

		// Scene 설정
		const scene = new THREE.Scene();
		this.#scene = scene;

		this.#setupCamera(); // 카뭬라
		this.#setupLight(); // 광원
		this.#setupModel(); // mesh
		this.#setupControls();

		// resize: 사이즈 업데이트
		window.onresize = this.resize.bind(this);
		this.resize(); // 최초 실행

		requestAnimationFrame(this.render.bind(this));
	}

	resize() {
		const width = this.#domContainer.clientWidth;
		const height = this.#domContainer.clientHeight;

		this.#camera.aspect = width / height; // resize시 변화하는 브라우저 크기에 맞게 업데이트
		this.#camera.updateProjectionMatrix(); // 변경된 값을 카메리아 적용

		this.#renderer.setSize(width, height);
		this.#labelRenderer.setSize(width, height);
	}

	#setupCamera() {
		const width = this.#domContainer.clientWidth;
		const height = this.#domContainer.clientHeight;

		const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);

		camera.position.set(0, 0, 10);
		this.#camera = camera;
	}

	#setupLight() {
		const color = 0xfffffff;
		const intensity = 1;
		const light = new THREE.DirectionalLight(color, intensity);
		light.position.set(-1, 2, 4); // 광원의 위치를 세팅
		this.#scene.add(light); // 광원은 장면의 구성요소이므로 부챠줌.
	}

	#setupModel() {
		// object3D는 좌표축
		// mesh = geometry + material
		// 여기서 #scene이 글로벌 좌표계

		const solarSystem = new THREE.Object3D(); // (Default) position: 000, rotation: 000, scale: 111 => solarSystem이 좌표계의 기준 축이 된다.
		this.#scene.add(solarSystem);
		solarSystem.add(new THREE.AxesHelper(20));

		// geometry - 하나의 geo를 달, 지구, 태양이 공유.
		const sphereGeometry = new THREE.SphereGeometry(1, 7, 7);

		// mesh - 태양
		const sunMaterial = new THREE.MeshPhongMaterial({
			emissive: 0xffff00, // 모델 자체가 발원하는 발광
			flatShading: true,
			opacity: 0.5,
			transparent: true,
		});
		const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
		solarSystem.add(sunMesh);
		sunMesh.add(new THREE.AxesHelper(1));
		sunMesh.scale.set(5, 5, 5); // 기준(solarSystem) 축에서의 5배

		// 태양 라벨
		const sunDiv = document.createElement("div");
		sunDiv.className = "label";
		sunDiv.textContent = "태양";
		const sunLabel = new CSS2DObject(sunDiv);
		sunLabel.position.set(0, 1.2, 0);
		sunMesh.add(sunLabel);

		// mesh - 지구
		const earthOrbit = new THREE.Object3D(); // 지구의 좌표축 생성
		earthOrbit.position.x = 10;
		solarSystem.add(earthOrbit);

		const earthMaterial = new THREE.MeshPhongMaterial({
			color: 0x2233ff,
			emissive: 0x112244,
			flatShading: true,
			opacity: 0.5,
			transparent: true,
		});
		const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
		earthMesh.add(new THREE.AxesHelper(1));
		earthOrbit.add(earthMesh);

		// 지구 라벨
		const earthDiv = document.createElement("div");
		earthDiv.className = "label";
		earthDiv.textContent = "지구구구";
		const earthLabel = new CSS2DObject(earthDiv);
		earthLabel.position.set(0, 3, 0);
		earthMesh.add(earthLabel);

		// mesh - 달
		const moonOrbit = new THREE.Object3D();
		moonOrbit.position.x = 2;
		earthOrbit.add(moonOrbit);
		const moonMaterial = new THREE.MeshPhongMaterial({
			color: 0x888888,
			emissive: 0x222222,
			flatShading: true,
			opacity: 0.5,
			transparent: true,
		});
		const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
		moonOrbit.add(moonMesh);

		moonMesh.scale.set(0.5, 0.5, 0.5);
		moonMesh.add(new THREE.AxesHelper(1));

		// 달 라벨
		const moonDiv = document.createElement("div");
		moonDiv.className = "label";
		moonDiv.textContent = "달달달달달달";
		const moonLabel = new CSS2DObject(moonDiv);
		moonLabel.position.set(0, 3, 0);
		moonMesh.add(moonLabel);

		this.#scene.add(new THREE.AxesHelper(20));

		this.#solarSystem = solarSystem;
		this.#earthOrbit = earthOrbit;
		this.#moonOrbit = moonMesh;
	}

	#setupControls() {
		const control = new OrbitControls(this.#camera, this.#domContainer); // 인자1: 카메라, 인자2: 마우스 컨트롤할 오브젝트
		this.#control = control;
	}

	update(time) {
		time *= 0.001; // MS -> Second로 변환

		this.#solarSystem.rotation.y = time / 2;
		this.#earthOrbit.rotation.y = time * 2;
		this.#moonOrbit.rotation.y = time * 6;

		this.#control.update();
	}

	// render 메서드: 3차원 요소로 시각화 해준다.
	render(time) {
		this.#renderer.render(this.#scene, this.#camera); // 어떤 장면을 어떤 카메라에 렌더를 할 것인지 인자로 넣음
		this.#labelRenderer.render(this.#scene, this.#camera);

		this.update(time);
		requestAnimationFrame(this.render.bind(this));
	}
}

// onload 이벤트 후에 객체를 생성해준다.
window.onload = function () {
	new App();
};
profile
따봉루피야 힘을내렴

0개의 댓글