3D ํ์ ๋ชฉ๋ง ๊ตฌํ
Three.js๋ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ด์ฉํด์ ์นํ์ด์ง์ 3D ์ค๋ธ์ ํธ๋ฅผ ํํํ ์ ์๋๋ก ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
์์ ์ด๋ฏธ์ง๋ Three.js๋ก ํ์ ๋ชฉ๋ง๋ฅผ ๊ตฌํํ ๊ฒ์ธ๋ฐ, ๊ทธ ๊ณผ์ ์ ๊ธฐ๋กํด๋ณด๊ณ ์ ํ๋ค.๐
๋์์ธ ๋ ํผ๋ฐ์ค๋ก๋ ์๋ ์ฝ๋ํ์ ์ฐธ๊ณ ํ๋ค.
Three.js ํ๊ฒฝ ๊ตฌ์ฑ
์๋๋ Three.js ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ๋ฐ ํ๊ฒฝ์ ๋ง๊ฒ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ๋ค์ด๋ค. ์ด๋ฒ์๋ ์ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ ๋ค์ด๋ก๋ ๋ฐ์์ ์ํฌํธํ๋ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๋ค.
npm ํจํค์ง๋ก ์ค์นํ๋ ๋ฐฉ๋ฒ
# three.js
npm install --save three
# vite
npm install --save-dev vite
CDN์ผ๋ก ํธ์ถํ๋ ๋ฐฉ๋ฒ
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@<version>/build/three.module.js",
"three/addons/": "https://unpkg.com/three@<version>/examples/jsm/"
}
}
</script>
๊ณต์ ํํ์ด์ง์์ ๋ค์ด๋ก๋ํ๋ ๋ฐฉ๋ฒ
์ข์ธก ๋ฉ๋ด๋ฅผ ๋ณด๋ฉด download ํญ๋ชฉ์ด ์๋ค.
HTML ํ์ผ๊ณผ CSS ํ์ผ ์ถ๊ฐ
์น ํ์ด์ง์ ๊ธฐ๋ณธ์ด ๋๋ HTMLํ์ผ์ ์์ฑํ๋ค. #container ์์ ๋ด๋ถ์๋ ํ์ ๋ชฉ๋ง๋ฅผ ๊ทธ๋ฆด Canvas๊ฐ ๋ค์ด๊ฐ๊ฒ ๋๋ค. ํ๋ฉด ์ ์ฒด๋ฅผ ๊ฐ๋์ฐจ๊ฒ ๋ง๋ค๊ธฐ ์ํด ์คํ์ผ๋ ์ถ๊ฐํด์ฃผ์๋ค. three.js ์ฝ๋๋ฅผ ์คํํ๊ธฐ ์ํ JavaScript ํ์ผ๋ ์์ฑํด์ HTML ํ์ผ์ ์ถ๊ฐํด์ฃผ์.
<style>
#container {width: 100%; height: 100vh;}
</style>
<div id="container"></div>
๊ธฐ๋ณธ ๊ตฌ์ฑ ์์ ์ถ๊ฐ (Scene, Renderer, Camera)
Three.js์ ๊ธฐ๋ณธ ๊ตฌ์ฑ ์์์ธ Scene, Renderer, Camera๋ฅผ ์์ฑํ๋ค.
class App {
constructor() {
const divContainer = document.querySelector("#container");
this._divContainer = divContainer;
const renderer = new THREE.WebGLRenderer({
antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
divContainer.appendChild(renderer.domElement);
this._renderer = renderer;
const scene = new THREE.Scene();
this._scene = scene;
this._setupCamera();
this._renderer.render(this._scene, this._camera);
}
_setupCamera() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
camera.position.set(0, 15, 35);
this._camera = camera;
}
}
window.onload = function () {
new App();
};
Three.js์ WebGLRenderer๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๋๋ฌ๋ฅผ ์์ฑํด์ฃผ์๋ค. ์ด ๋ antialias ์ต์ ์ true๋ก ์ค์ ํด์ ์กฐ๊ธ ๋ ๋ถ๋๋ฌ์ด ๋๋์ผ๋ก ๋ ๋๋ง ๋๋๋ก ํ๋ค.
PerspectiveCamera๋ ์๊ทผ๊ฐ์ ํํํ๋ ์นด๋ฉ๋ผ๋ก '์์ผ๊ฐ', '์นด๋ฉ๋ผ ์ข ํก๋น', '์นด๋ฉ๋ผ๋ก๋ถํฐ์ ๊ฑฐ๋ฆฌ ์์ชฝ', '์นด๋ฉ๋ผ๋ก๋ถํฐ์ ๊ฑฐ๋ฆฌ ๋ค์ชฝ'์ ์ธ์๋ก ๋ฐ๋๋ค. ํ์ฌ #container ์์๋ฅผ ์ปจํ ์ด๋๋ก ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์ข ํก๋น๋ ์์์ ๊ฐ๋ก/์ธ๋ก ๊ฐ์ผ๋ก ๊ณ์ฐํด์ ๋ฃ์ด์ฃผ์๋ค.
๊ฒฐ๊ณผ๋ฅผ ํ์ธํด๋ณด๋ฉด ์์ ๊ฒ์ ๋ค๋ชจ ์์๊ฐ ์์ฑ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. #container ์์ ๋ด๋ถ์ ๋ ๋๋ฌ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ ๋ค์ด๊ฐ๋ค.
๋ ๋๋ฌ์ ์ฌ์ด์ฆ ์กฐ์
๋ ๋๋ฌ๋ฅผ #container ์์์ ์ฌ์ด์ฆ์ ๋ง์ถ์ด ๋๋ ค์ฃผ์๋ค.
์นด๋ฉ๋ผ์ ์ข ํก๋น๋ฅผ ๊ณ์ฐํ๊ณ ์ด์ ๋ง์ถ์ด ๋ ๋๋ฌ์ ์ฌ์ด์ฆ๋ฅผ ์ค์ ํ๋ resize() ๋ฉ์๋๋ฅผ ์์ฑํ๋ค.
resize() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
this._renderer.setSize(width, height);
}
์์ฑ์์์ ๋ฉ์๋๋ฅผ ์คํํ๋ค. ์๋์ฐ ์ฐฝ์ ์ฌ์ด์ฆ๋ฅผ ์กฐ์ ํ์ ๋์๋ ์ด์ ๋ง์ถ์ด ๋ ๋๋ฌ๊ฐ ๋ฆฌ์ฌ์ด์ง์ด ๋๋๋ก window.onresize์๋ ์ด๋ฒคํธ๋ฅผ ๋ถ์ฌ์ฃผ์๋ค. ์๋์ฐ ์ฐฝ์ ๋ง์ถ์ด ๋ ๋๋ฌ์ ์ฌ์ด์ฆ๊ฐ ๋์ด๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
constructor() {
...
window.onresize = this.resize.bind(this);
this.resize();
...
}
mesh ์์ฑ (๊ธฐ๋ฅ)
_setupModel() {
const textureLoader = new THREE.TextureLoader();
const core = new THREE.Object3D();
this._scene.add(core);
const coreMap = textureLoader.load("./images/window.png", texture => {
texture.repeat.x = 8;
texture.repeat.y = 5;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.offset.x = 0;
texture.offset.y = -1.5;
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestFilter;
});
}
ํ์ ๋ชฉ๋ง์ ์ค์ง์ ์ธ ๋ฉ์ฌ๋ฅผ ๋ง๋๋ ๋ก์ง์ ๋ด์ _setupModel() ๋ฉ์๋๋ฅผ ์์ฑํ๋ค. core๋ผ๋ 3์ฐจ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ์ฃผ์๋๋ฐ, ์ด๋ ๊ธฐ๋ฅ ๋ฉ์ฌ๋ฅผ 3์ฐจ์ ๊ณต๊ฐ ์์ ๊ตฌ์ฑํ๋ ์ญํ ์ ํ๊ฒ ๋๋ค.
_setupModel() {
...
const coreGeometry = new THREE.CylinderGeometry(4, 4, 10, 8, 1, true);
const coreMaterial1 = new THREE.MeshStandardMaterial({
color: "#dbd3d3",
metalness: 0.2,
roughness: 0.5,
flatShading: true,
});
const coreMaterial2 = new THREE.MeshStandardMaterial({
map: coreMap,
transparent: true,
roughness: 1,
});
const materials = [coreMaterial1, coreMaterial2];
const coreMesh = new THREE.Mesh(coreGeometry, materials);
coreMesh.receiveShadow = true;
coreMesh.castShadow = true;
core.add(coreMesh);
}
Three.js์ CylinderGeometry() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ํ์ ๋ชฉ๋ง์ ๊ธฐ๋ฅ์ด ๋๋ ์ํต์ ๋ง๋ค์ด๋ณด์. CylinderGeometry() ๋ฉ์๋๋ ์์๋๋ก '์๋ฉด ๋ฐ์ง๋ฆ ํฌ๊ธฐ', '๋ฐ๋ฉด ๋ฐ์ง๋ฆ ํฌ๊ธฐ', '์ํต์ ๋์ด', '์ํต ๋๋ ๋ถํ ๊ฐ์', '์ํต ๋์ด ๋ถํ ๊ฐ์', '์ํต ์ํ๋จ ๋ซ๋ฆผ ์ฌ๋ถ', '์ํต ์์ ๊ฐ', '์๋ฟ ์ฐ์ฅ ๊ฐ'์ ์ธ์๋ก ๋ฐ๋๋ค. ์ํต ๋๋ ๋ถํ ๊ฐ์์ ๋ฐ๋ผ ์ํต์ ๊ฐ์ ๊ฐ์๊ฐ ์ ํด์ง๋๋ฐ ์ซ์๊ฐ ๋์ ์๋ก ์ํ์ ๊ฐ๊น์ด ์ํต์ด ๋ง๋ค์ด์ง๋ค. ํ์ ๋ชฉ๋ง์ ๊ธฐ๋ฅ์ 8๊ฐํ์ผ๋ก ๋ง๋ค์ด์ฃผ๊ธฐ ์ํด ์ธ์๋ 8์ ์ฃผ์๋ค.
์ด ๊ธฐ๋ฅ์ core 3์ฐจ์ ๊ฐ์ฒด์ ์ถ๊ฐํด์ฃผ์.
๊ด์ ์์ฑ
์ด๋ ๊ฒ ๋ฉ์ฌ๋ฅผ ์ถ๊ฐํด์ฃผ์ด๋ ํ๋ฉด์์๋ ์๋ฌด๊ฒ๋ ๋ณด์ด์ง ์๋๋ค. ๊ทธ ์ด์ ๋ scene์ ์กด์ฌํ๋ ๋ฌผ์ฒด์ ๋ช ์์ ๊ตฌ๋ถํ ๋น์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. _setupLight() ๋ฉ์๋๋ฅผ ์์ฑํ์ฌ ๊ด์์ ์ถ๊ฐํด๋ณด์. ๋ณด๋ค ๋ ๋์ ๊ฒฐ๊ณผ๋ฌผ์ ์ํด 3๊ฐ์ ๊ด์์ ์ถ๊ฐํ๋ค.
_setupLight() {
const hemisphereLight = new THREE.HemisphereLight("#b0d8f5", "#ffdec1", 0.3);
this._scene.add(hemisphereLight);
const pointLight = new THREE.PointLight("#ffffff", 0.5);
pointLight.position.set(0, 15, 35);
this._scene.add(pointLight);
const spotLight = new THREE.SpotLight("#e070bb", 0.5);
spotLight.position.set(0, 30, 8);
spotLight.target.position.set(0, 0, 0);
spotLight.angle = THREE.Math.degToRad(30);
spotLight.penumbra = 0.2;
this._scene.add(spotLight);
this._scene.add(spotLight.target);
}
๊ด์์ ์ถ๊ฐํด์ฃผ๋ฉด ์ด์ ์๋ ๋ณด์ด์ง ์์๋ ๊ธฐ๋ฅ์ด ๋ณด์ธ๋ค
mesh ์์ฑ (์ง๋ถ, ๋ฐ๋ฅ, ๋ชฉ๋ง)
์ง๋ถ๊ณผ ๋ฐ๋ฅ, ๋ชฉ๋ง๋ ๊ฐ์ ๋ฐฉํฅ, ๊ฐ์ ์๋๋ก ํ์ ํ๊ธฐ ๋๋ฌธ์ carouselStytem 3์ฐจ์ ๊ฐ์ฒด๋ก ๋ฌถ์ด์ ๋ค๋ฃจ์ด ์ค๋ค.
const carouselSystem = new THREE.Object3D();
this._scene.add(carouselSystem);
this._carouselSystem = carouselSystem;
์ง๋ถ
LatheGeometry()๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฝ๋ณ๊ณผ ๊ฐ์ด Y์ถ์ ๊ธฐ์ค์ผ๋ก ๋์นญ๋๋ ๋ฉ์ฌ๋ฅผ ์์ฑํ ์ ์๋ค. ๋ฉ์ฌ์ ์์ ํฌ์ธํธ๊ฐ 0,0์ด๋ผ๋ฉด ์๋์ ๊ฐ์ด ์๋ฟ์ฒ๋ผ ๋์ด ๋ชจ์ด๋ ํํ๋ฅผ ๊ฐ์ง๋ค.
const hat = new THREE.Object3D();
hat.position.set(0, 10, 0);
carouselSystem.add(hat);
const hatSegment = 18; // ์์ฑํ ์์ฃผ์ ์ธ๊ทธ๋จผํธ์ ์
const hatRadius = 10;
const points = [];
points.push(new THREE.Vector2(0.0, 0.0));
points.push(new THREE.Vector2(hatRadius, -5.0));
const hatGeometry = new THREE.LatheGeometry(points, hatSegment, 0, Math.PI * 2);
const hatMaterial = new THREE.MeshStandardMaterial({
metalness: 0.2,
});
const hatMesh = new THREE.Mesh(hatGeometry, hatMaterial);
hatMesh.receiveShadow = true;
hatMesh.castShadow = true;
hat.add(hatMesh);
๊ฐ๋๋
์ง๋ถ์ ๊ฐ๋๋๋ฅผ ๋ฌ์์ฃผ๋ ๊ฒ์ CircleGeometry๋ฅผ ์ด์ฉํด์ ๋ฐ์์ ์์ฑํ๊ณ , ์ง๋ถ์ ๋๋ ์ ๋ง๊ฒ ์์น ์ขํ๋ฅผ ์ก์์ฃผ๋ฉด ๊ฐ๋จํ๋ค. ์ง๋ถ์ ๋ง๋ค ๋ ์ฌ์ฉํ๋ ๋ณ์ hatSegment (์ง๋ถ์ ์ธ๊ทธ๋จผํธ ์)๋ฅผ ์ด์ฉํด์ ์์ ๋ฐ์ง๋ฆ ํฌ๊ธฐ๋ฅผ ๊ตฌํ๋ค๋ฉด ํฌ๊ธฐ๋ฅผ ์ข ๋ ์ ๋์ ์ผ๋ก ์์ ํ ์ ์๋ค.
const garlandRadius = (Math.sin((360 / hatSegment / 2) * (Math.PI / 180)) / hatRadius) * 100;
const garlandGeometry = new THREE.CircleGeometry(garlandRadius, 24, Math.PI, Math.PI);
๋ฐ๋ฅ
๋ฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์๋ ๋๊ป๊ฐ์ด ์์ผ๋ฉด์๋ ์์ด ๋น ์๊ธฐ๋ฅ์ ๋ง๋ค์ด์ผ ํ๋ค. Three.js์์ ์ด๋ฐ ํํ์ ๋ํ์ ๋ฐ๋ก ์ ๊ณตํ๋ ๋ฉ์๋๋ ์๊ธฐ ๋๋ฌธ์ Shape()์ holes์ ExtrudeGeometry()๋ฅผ ์ด์ฉํด์ ์ํ๋ ๋ชจ์์ ๊ตฌํํด์ฃผ๋ ค๊ณ ํ๋ค.
๋จผ์ Shape()๋ฅผ ์ด์ฉํด์ 2d์์ ๋ง๋ ๋ค. 2d์์ ๊ตฌ๋ฉ์ ์ ์ํ๋ paths ๋ฐฐ์ด์ ์ถ๊ฐํ๋ฉด ์๋์ ๊ฐ์ด ๊ตฌ๋ฉ์ด ๋ซ๋ฆฐ ์์ ์์ฑํ ์ ์๋ค.
const floorOuterRadius = 10;
const floorInnerRadius = 5;
const arcShape = new THREE.Shape();
arcShape.moveTo(floorOuterRadius * 2, floorOuterRadius);
arcShape.absarc(floorOuterRadius, floorOuterRadius, floorOuterRadius, 0, Math.PI * 2, false);
const holePath = new THREE.Path();
holePath.moveTo(floorOuterRadius + floorInnerRadius, floorOuterRadius);
holePath.absarc(floorOuterRadius, floorOuterRadius, floorInnerRadius, 0, Math.PI * 2, true);
arcShape.holes.push(holePath);
์ ๋ฉ์ฌ์ ExtrudeGeometry๋ฅผ ์ด์ฉํด์ ๋๊ป๊ฐ์ ๋ถ์ฌํ๋ฉด ์๋์ ๊ฐ์ด ์ค์์ด ๋ซ๋ ค์๋ ์๊ธฐ๋ฅ์ด ๋ง๋ค์ด์ง๋ค.
const floorGeometry = new THREE.ExtrudeGeometry(arcShape, {depth: 1});
floorGeometry.center();
๋ชฉ๋ง
๋ชฉ๋ง์ ๊ฒฝ์ฐ ๋จ์ํ ์์ด๋ ์ฌ๊ฐํ๊ฐ์ ๋ํ์ผ๋ก ๊ตฌํํ๊ธฐ์๋ ์ด๋ ค์์ด ์๊ธฐ ๋๋ฌธ์ Three.js์ Shape() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ๊ตฌํํ๊ธฐ๋ก ํ๋ค. Shape()๋ฅผ ์ด์ฉํ๋ฉด 2d ํ๋ฉด์ ์ ์ํ ์ ์๋๋ฐ, ์ด ๋ 2d ํ๋ฉด์ ์ ์ํ๋๋ฐ ํ์ํ ์ขํ๋ฅผ ๊ตฌํ๋ ๋ถ๋ถ์ HTML5 Canvas Path Creator ์ฌ์ดํธ๋ฅผ ํ์ฉํ๋ค. ์๋์ ์ฌ์ดํธ์์ ๋ชฉ๋ง ์ด๋ฏธ์ง๋ฅผ ํธ์ถํด์ ํจ์ค๋ฅผ ๋ฐ๋ผ ๊ทธ๋ ค์ค๋ค. ์์ฑํ Shape ๋ฉ์ฌ์ ๋ชฉ๋ง ์ด๋ฏธ์ง๋ฅผ ํ ์ค์ณ๋ก ๋ฃ์ด์ฃผ๋ฉด ์ํ๋๋๋ก ๋ชฉ๋ง๋ฅผ ๊ตฌํํ ์ ์๋ค.
HTML5 Canvas Path Creator
์บ๋ฒ์ค์ ์ , ๋ฒ ์ง์ด ๊ณก์ ๋ฑ์ ๊ทธ๋ฆฌ๋ฉด ํจ์ค๋ฅผ ์์ฑํ๋ ํจ์๋ก ๋ณํํด์ฃผ๋ ์ฌ์ดํธ
https://adriencastex.github.io/HTML5-Canvas-Path-Creator/
for ๋ฌธ์ ์ด์ฉํด์ ๋ฐฐ์นํด์ฃผ๋ฉด ๋ชฉ๋ง๋ ์์ฑ์ด ๋๋ค.
const horses1 = new THREE.Object3D();
const horses2 = new THREE.Object3D();
horses.add(horses1);
horses.add(horses2);
this._horses1 = horses1;
this._horses2 = horses2;
for (let i = 0; i < 14; i++) {
const horse = new THREE.Object3D();
const horseMesh = new THREE.Mesh(horseGeometry, horseMaterial);
if (i % 2) {
horseMesh.position.z = 8;
horse.rotation.y = THREE.Math.degToRad((360 / 14) * i);
horse.add(horseMesh);
horses1.add(horse);
} else {
horseMesh.position.z = 9;
horse.rotation.y = THREE.Math.degToRad((360 / 14) * i);
horse.add(horseMesh);
horses2.add(horse);
}
ํจ์ค๋ฅผ ๋ฐ๊ณ ํ ์ค์ณ๋ฅผ ์ ํ์ฃผ๋ ๊ณผ์ ์ด ์ด๋ ต๋ค๋ฉด ์ฌ๊ฐํ 2d ๋ฉ์ฌ๋ฅผ ํฌ๋ช ํ๊ฒ ๋ฐ๊พผ ๋ค์ ์ด๋ฏธ์ง ํ ์ค์ณ๋ฅผ ์ ํ์ฃผ๋ ๋ฐฉ๋ฒ๋ ๊ฐ๋ฅํ๋ค. ๋ค๋ง ์ด๋ฐ ๋ฐฉ๋ฒ์ผ๋ก ์์ ํ๋ค๋ฉด ๊ทธ๋ฆผ์๋ ์ฌ๊ฐํ์ผ๋ก ํํ๋๊ธฐ ๋๋ฌธ์ ์์ฑ๋ ์๋ ๊ทธ๋ฆผ์ ํํ์ด ์ด๋ ค์์ง๋ค.
ํ์ ํ๋ ๋ชจ์ ์ถ๊ฐ
Three.js์๋ Clock์ด๋ผ๋ ํด๋์ค๊ฐ ๋ด์ฅ๋์ด ์๋ค. ์๊ฐ์ ๊ด๋ จ๋ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ ์ ๋๋ฉ์ด์ ์ ํ์ฉํด ์ค ๊ฒ์ด๋ค. Renderer, Camera์ ํจ๊ป ์์ฑ์์ Clock์ ์ถ๊ฐํด์ค๋ค.
constructor() {
...
const clock = new THREE.Clock();
this._clock = clock;
...
}
์ ๋๋ฉ์ด์ ์ ์ ๋ฐ์ดํธํ๋ update ํจ์๋ฅผ ์ถ๊ฐํ๋ค. ์ด ํจ์๋ ์๊ฐ์ ์ธ์๋ก ๋ฐ์์ ์ ๋๋ฉ์ด์ ์ ๋ฐ์ํด์ค๋ค. ์ง๋ถ, ๋ฐ๋ฅ, ๋ชฉ๋ง๊ฐ ํจ๊ป y์ถ์ ๊ธฐ์ค์ผ๋ก ํ์ ํด์ผํ๋ฏ๋ก _carouselSystem์ ๋กํ ์ด์ ํด์ฃผ์๋ค. ์ด ๋ ๋ชฉ๋ง๋ ๋ฌผ๊ฒฐ์น๋ฏ ์์๋๋ก ์์ง์ด๋๋ก ํ๊ธฐ ์ํด Math.sin์ ์ด์ฉํด์ y์ถ ์์น๋ฅผ ๋ฐ๋ก ์กฐ์ ํด์ฃผ์๋ค.
update(time) {
this._carouselSystem.rotation.y = -time / 2;
this._horses1.position.y = (Math.sin((time + 1.05) * 3) + 1) / 2;
this._horses2.position.y = (Math.sin(time * 3) + 1) / 2;
}
Clock์ getElapsedTime() ๋ฉ์๋๋ Clock์ด ์์ฑ๋ ํ ๋ช ์ด๊ฐ ์ง๋ฌ๋์ง๋ฅผ ๋ฐํํ๋ค. ์ด ์๊ฐ์ update ํจ์๋ฅผ ์คํํ ๋ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ค. ์ด์ ํ์ ํ๋ ๋ชฉ๋ง๋ฅผ ํ์ธํ ์ ์๋ค.
render() {
const elapsedTime = this._clock.getElapsedTime();
this.update(elapsedTime);
this._renderer.render(this._scene, this._camera);
requestAnimationFrame(this.render.bind(this));
}
๋ง์ฐ์ค ์ปจํธ๋กค๋ฌ ์ถ๊ฐ
์ถ๊ฐ์ ์ผ๋ก OrbitControls๋ฅผ ์ฌ์ฉํ๋ฉด ์นด๋ฉ๋ผ๋ฅผ ๋ง์ฐ์ค๋ก ์กฐ์ ํด์ ํ์ , ์ค์ธ, ์ค์์์ด ๊ฐ๋ฅํ๋ค. examples์์ OrbitControls๋ฅผ import ํด์ฃผ๊ณ , ์์ฑ์์ ์นด๋ฉ๋ผ ๊ฐ์ฒด์ ์บ๋ฒ์ค๋ก ์ฌ์ฉํ๋ DOM ์์๋ฅผ ๋๊ฒจ์ค๋ค.
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
_setupControls() {
new OrbitControls(this._camera, this._divContainer);
}
์์ฑ
Three.js๋ก ๊ตฌํํ ํ์ ๋ชฉ๋ง
๊นํ๋ธ
https://github.com/sduu/canvas-sketch/tree/main/carousel