Three.js 강의를 들으면서 기본 개념을 배우고 실습을 진행했다. 내가 원하는 3D 오브젝트를 자유자재로 구현하려면 좀 더 공부하고 연습하는 과정이 필요하다는 생각이 들어서 공식 문서를 보면서 배운 내용을 다시 복습하고 정리해 보았다.
1. 기본 환경 세팅
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import printTangerine from "./mesh/tangerine.js";
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xe4eaf1);
// 카메라
const camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);
// renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 바닥
const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0xaac69f,
});
const ground = new THREE.Mesh(groundgeo, groundMaterial);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
// 빛 (빛이 없으면 mesh가 검은색으로 보인다.)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 5, 0);
function animate() {
controls.update(); // 마우스로 카메라 회전 가능
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
2. 한라봉 오브젝트 만들기
(1) 모양 잡기
원하는 오브젝트의 모양을 만들기 위해 오브젝트를 작은 오브젝트 단위로 나누고, 이를 위치와 각도를 맞춰 하나의 그룹으로 조합한다. 한라봉을 크게 잎 부분과 열매 부분으로 나누고, 그 안에서 다시 분리해서 구현한다.
(createTangerine.js)
import * as THREE from "three";
export default function createTangerine() {
// 한라봉 그룹
const tangerine = new THREE.Group();
// 열매 그룹
const body = new THREE.Group();
const bodyMaterial = new THREE.MeshStandardMaterial({
color: 0xffb48c,
});
// 뚱뚱한 아랫부분
const bottomGeometry = new THREE.DodecahedronGeometry(2, 1);
const bottom = new THREE.Mesh(bottomGeometry, bodyMaterial);
body.add(bottom);
// 작은 윗부분
const topGeometry = new THREE.TetrahedronGeometry(0.8, 3);
const top = new THREE.Mesh(topGeometry, bodyMaterial);
top.position.y = 1.7;
body.add(top);
// 잎
const leaves = new THREE.Group();
const leafMaterial = new THREE.MeshStandardMaterial({
color: 0x6ca06e,
side: THREE.DoubleSide,
});
// 작은 줄기
const stemGeometry = new THREE.CylinderGeometry(0.08, 0.1, 0.4);
const stem = new THREE.Mesh(stemGeometry, leafMaterial);
stem.position.y = 2.5;
leaves.add(stem);
// 잎
const leafGeometry = new THREE.SphereGeometry(0.5, 32, 16, 0, Math.PI / 3);
const leaf = new THREE.Mesh(leafGeometry, leafMaterial);
leaf.position.set(-0.5, 2.4, -0.1);
leaf.rotation.z = Math.PI / -2;
leaves.add(leaf);
tangerine.add(body);
tangerine.add(leaves);
return tangerine;
}
(index.js)
...
import createTangerine from "./createTangerine.js";
const tangerine = createTangerine();
tangerine.position.set(0, 2, -1);
scene.add(tangerine);
...
입체감을 주기 위해 직사광인 directionalLight를 추가한다. 이는 주로 태양을 표현할 때 사용된다.
(index.js)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(-6, 10, 10);
scene.add(directionalLight);
(2) 그림자 추가하기
오브젝트에 그림자를 추가하려면 renderer, directionalLight, 바닥(그림자가 생기는 Mesh), 그림자를 가질 Mesh에 그림자 관련 설정을 추가해야 한다.
renderer에 그림자 설정 추가
renderer.shadowMap.enabled = true;
directionalLight에 그림자 설정 추가
directionalLight.castShadow = true;
directionalLight.shadow.radius = 5; // 블러 효과 추가
바닥 Mesh에 그림자 설정 추가
ground.receiveShadow = true;
한라봉 Mesh에 그림자 설정 추가
import * as THREE from "three";
export default function createTangerine() {
const tangerine = new THREE.Group();
(...)
for (const mesh of body.children) {
mesh.castShadow = true;
mesh.receiveShadow = true;
}
for (const mesh of leaves.children) {
mesh.castShadow = true;
mesh.receiveShadow = true;
}
return tangerine;
}
for 반복문을 통해 모든 Mesh에 그림자 설정을 추가해 준다. 그림자가 생기는 Mesh에는 receiveShadow를, 그림자를 만드는 Mesh에는 castShadow를 true로 설정한다.
(3) 텍스처 지정하기
실제 한라봉과 유사하게 만들기 위해 텍스처(질감)을 추가한다. TextureLoader를 통해 loader를 생성하고, loader로 텍스처 이미지를 로드하면 이미지 파일을 텍스처로 변환해 준다.
import * as THREE from "three";
export default function createTangerine() {
const loader = new THREE.TextureLoader();
const baseColor = loader.load("../textures/tangerine/tangerine_color.jpg");
const normal = loader.load("../textures/tangerine/tangerine_norm.jpg");
const rough = loader.load("../textures/tangerine/tangerine_rough.jpg");
const tangerine = new THREE.Group();
const body = new THREE.Group();
const bodyMaterial = new THREE.MeshStandardMaterial({
color: 0xffb48c,
map: baseColor,
normalMap: normal,
roughness: 0.2,
roughnessMap: rough,
});
(...)
};
* 사용된 texture 이미지
map 속성은 ColorMap으로 Material의 색상을 결정한다.
normalMap 속성은 설정 시 실제로 형체가 변하지 않지만, 표면의 빛을 왜곡시켜 입체감 있는 표현이 가능하다.
roughnessMap 속성은 거칠기 또는 광택 등의 질감에 따른 빛의 굴곡을 표현한다. 밝은 부분은 매끈하게, 어두운 부분은 거칠게 표현하며 roughness 속성을 조절하여 표면에 반사되는 빛의 세기를 조절할 수 있다.
최종 결과물
개념 자체는 크게 복잡하지 않지만, 원하는 3D 오브젝트를 만들기 위해 어떤 Geometry를 어떤 값과 함께 사용하고, 각도와 위치를 어떻게 설정할지 정하는 것이 어렵게 느껴졌다. 많은 연습을 통해 익숙해지도록 노력해야겠다! 얼른 내가 원하는.. 귀여운 오브젝트들을 많이 만들어 보고 싶다.
'Develop > JavaScript' 카테고리의 다른 글
[Jest] mock vs doMock (+ Mock modules) (0) | 2024.12.30 |
---|---|
[JavaScript] Vanilla JavaScript에 PWA 적용하기 with Webpack, Workbox (0) | 2024.05.18 |
[JavaScript] Firestore 검색 구현하기 with Aloglia (0) | 2024.04.19 |
[JavaScript] 카카오맵 API를 이용한 장소 검색 및 위치 추가 구현하기 (0) | 2024.03.19 |
Vanilla JS로 SPA 구현하기 (0) | 2024.03.04 |