Intro
๋๋ฉ ํ์ด์ง๋ฅผ 3D๋ฅผ ์ด์ฉํด ์๊ฐ์ ์ผ๋ก ๋์ฑ ์์๊ฒ ๋ง๋ค๊ณ ์ถ์ด three.js๋ฅผ ๋ฐฐ์ฐ๊ฒ ๋์์ต๋๋ค. ์ต๊ทผ ๊นํ ๋๋ฉ ํ์ด์ง์์ ํ๊ตญ์ด ์ง์์ ์์ํ์๋๋ฐ , ์ ๊ธฐ ๋์ค๋ 3D ์ง๊ตฌ๋ณธ๋ ๋ฐ๋ก ์ด three.js๋ฅผ ์ด์ฉํ์ฌ ๋ง๋ ๊ฒ์ ๋๋ค..!!
GitHub: Where the world builds software
Three.js
What is Three.js
Three.js๋ WebGL์ ์ด์ฉํ์ฌ ์นํ์ด์ง์ 3D ๊ฐ์ฒด๋ฅผ ์ฝ๊ฒ ๋ ๋๋ง ํ๋๋ก ๋์์ฃผ๋ ์๋ฐ์คํฌ๋ฆฝํธ 3D ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ฌ๊ธฐ์ WebGL์ html์ ์บ๋ฒ์ค ์์๋ฅผ ์ด์ฉํ์ฌ ์น ๋ธ๋ผ์ฐ์ ์์ ์ธํฐ๋ ํฐ๋ธ ํ 3D ๊ทธ๋ํฝ์ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ๋๊ตฌ์ ๋๋ค. ์น ๋ธ๋ผ์ฐ์ ์์ CPU๊ฐ ์๋ GPU๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ฉด์ ๋ ๋๋ง ํ์ฌ ๊ต์ฅํ ๋น ๋ฆ ๋๋ค! ๊ทธ๋์ ์ด WebGL์ ๊ฐ์ง๊ณ ์น ๊ฒ์ , ์ธํฐ๋ ํฐ๋ธ ํ์ด์ง, VR ์ฝํ ์ธ ๋ฑ ์ฌ๋ฌ 3D ์์ ๋ฌผ์ ๋ง๋ค ์ ์์ต๋๋ค.
"Hello Cube"
ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ๋ฐฐ์ธ ๋ ๊ฐ์ฅ ๋จผ์ "Hello World!"๋ฅผ ์ถ๋ ฅํ๋ค๋ฉด, Three.js์์๋ 3์ฐจ์ ์ธ๊ณ์์ ์ ์ก๋ฉด์ฒด ํ๋ธ๋ฅผ ๋ง๋๋ ๊ฒ์ด ์์์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
๋จผ์ ์ด๋ ๊ฒ three.js๋ฅผ ์ฌ์ฉํ html ํ์ผ ํ๋์ three.js ์ฝ๋๊ฐ ๋ค์ด๊ฐ script.js ํ์ผ ํ๋๋ฅผ ์์ฑํ๊ณ <body> ํ๊ทธ ์์ ๋ถ๋ฌ์์ค๋๋ค.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>my first three.js app</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script type ="module" src="./js/script.js"></script>
</body>
</html>
์ด๊ฒ์ Three.js ์ฑ์ ๊ตฌ์กฐ๋ฅผ ๋์ํํ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ ์ฒ์ ๋ณด๋ฉด ์ดํด๊ฐ ์ ๋ ์๋ ์์ง๋ง ์ฝ๋๋ฅผ ์ง์ ์ง ๋ณด๊ณ ๋ค์ ์ด ๊ตฌ์กฐ๋๋ฅผ ๋ณด๋ฉด ๋ฌด์จ ์๋ฆฌ์ธ์ง ๋ฐ๋ก ์ดํด๊ฐ ๋ ๊ฒ์ ๋๋ค.
Three.js๋ npm์ ํตํ์ฌ ์ค์นํ๊ฑฐ๋, CDN์ด๋ static ํธ์คํ ์ผ๋ก ๋น ๋ฅด๊ฒ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. Three.js ๊ณต์ ๋ฌธ์์์๋ npm์ ํตํ ์ค์น๋ฅผ ๊ถ์ฅํ์ง๋ง ์ ๋ UNPKG CDN์ ์ด์ฉํ์ฌ ์ฌ์ฉํ์์ต๋๋ค.
npm install --save three
three.js๋ก ๋ฌด์ธ๊ฐ๋ฅผ ํํํ๋ ค๋ฉด scene, camera ๊ทธ๋ฆฌ๊ณ renderer๊ฐ ํ์ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์นด๋ฉ๋ผ๋ก ์ฅ๋ฉด์ ๊ตฌํํ ์ ์์ต๋๋ค.
์นด๋ฉ๋ผ์ ์ฒซ ๋ฒ์งธ ์์ฑ์ **field of view(์์ผ๊ฐ)**์ ๋๋ค. FOV(์์ผ๊ฐ)๋ ํด๋น ์์ ์ ํ๋ฉด์ด ๋ณด์ด๋ ์ ๋๋ฅผ ๋ํ๋ ๋๋ค. ๊ฐ์ ๊ฐ๋ ๊ฐ์ผ๋ก ์ค์ ํฉ๋๋ค.
๋ ๋ฒ์งธ ์์ฑ์ **aspect ratio(์ข ํก๋น)**์ ๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์์์ ๋์ด์ ๋๋น์ ๋ง์ถ์ด ํ์ํ๊ฒ ํ ํ ๋ฐ, ๊ทธ๋ ์ง ์์ผ๋ฉด ์์ด๋์คํฌ๋ฆฐ์ ์๋ ์ํ๋ฅผ ํธ๋ ๊ฒ์ฒ๋ผ ์ด๋ฏธ์ง๊ฐ ํ์ด์ ธ ๋ณด์ผ ๊ฒ์ ๋๋ค.
๋ค์ ๋ ์์ฑ์ near์ far ์ ๋จ๋ฉด์ ๋๋ค. ๋ฌด์จ ๋ป์ธ๊ฐ ํ๋ฉด, far ๊ฐ ๋ณด๋ค ๋ฉ๋ฆฌ ์๋ ์์๋ near ๊ฐ๋ณด๋ค ๊ฐ๊น์ด ์๋ ์ค๋ธ์ ํธ๋ ๋ ๋๋ง ๋์ง ์๋๋ค๋ ๋ป์ ๋๋ค. ์ง๊ธ ์์ ์์ ์ด๊ฒ๊น์ง ๊ณ ๋ คํ ํ์๋ ์์ง๋ง, ์ฑ ์ฑ๋ฅ ํฅ์์ ์ํด ์ฌ์ฉํ ์ ์์ต๋๋ค.(์ ๋ด์ฉ์ three.js ๊ณต์๋ฌธ์ ๋ด์ฉ์ ๋๋ค)
import * as THREE from "https://unpkg.com/three@0.126.1/build/three.module.js";
const scene = new THREE.Scene();
//์ฅ๋ฉด ์์ฑ
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
//์นด๋ฉ๋ผ ์ธ๋ถ ์ค์
const renderer = new THREE.WebGLRenderer({ antialias: true });
//renderer ์์ฑ
renderer.setSize(window.innerWidth, window.innerHeight);
//ํ๋ฉด ์ฌ์ด์ฆ ์ค์
document.body.appendChild(renderer.domElement);
//html ํ๋ฉด์ ๊ตฌํ
์ด์ ํ๋ธ๋ฅผ ์ถ๊ฐํด๋ด ์๋ค.
const geometry = new THREE.BoxGeometry();
//BoxGeometry ์์ฑ , ์ด ์์ vertices(๊ผญ์ง์ )๊ณผ faces(๋ฉด)์ด ํฌํจ
const material = new THREE.MeshBasicMaterial( { color: 0x00ff80 } );
// MeshBasicMatreial ์์ฑ , color: ํ๊ด์
const cube = new THREE.Mesh( geometry, material );
//Mesh๋ฅผ ์ด์ฉํ์ฌ geometry์ material์ ์ ์ฉํ๊ณ ์์ ๋กญ๊ฒ ์์ง์ผ ์ ์๋๋ก ํจ
scene.add( cube );
// ์ฅ๋ฉด์ cube๋ฅผ ์ถ๊ฐํจ
camera.position.z = 5;
//๊ธฐ๋ณธ ์นด๋ฉ๋ผ ์
ํ
์ (0,0,0)์ด๋ฏ๋ก ์นด๋ฉ๋ผ์ z ๊ฐ์ 5๋ก ํจ
์ด์ ํ์ด์ง๋ฅผ ์ด์ด๋ณด๋ฉด ๋๋๊ฒ๋ ์๋ฌด ์ผ๋ ์ผ์ด๋์ง ์์ ๊ฒ์ ๋๋ค. ์๋ํ๋ฉด ์์ง ์๋ฌด๊ฒ๋ ๋ ๋๋ง ํ์ง ์์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด ์ฝ๋๋ก ์ธํด 1์ด์ 60๋ฒ์ฉ ํ๋ฉด์ด ๋ ๋๋ง ๋ ๊ฒ์ด๊ณ , ํ๋ธ๋ 1์ด์ 60๋ฒ์ฉ ์ ๋๋ฉ์ดํ ํ ๊ฒ์ ๋๋ค! ์ฌ๊ธฐ๊น์ง๊ฐ ๊ณต์๋ฌธ์์ ๋์์๋ ํฌ๋ก ํ๋ธ์ด์ง๋ง ๊ฐ๋จํ ์ฝ๋ ๋ช ๊ฐ๋ง ๋ ์จ์ ์นด๋ฉ๋ผ๊ฐ ์ฌ์ฉ์์ ์๊ตฌ์ ๋ง๊ฒ ์์ง์ด๋๋ก ํด๋ณด๊ฒ ์ต๋๋ค.
function animate() {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
๋จผ์ js ๋งจ ์์ค์ CDN์ ์ด์ฉํ OrbitControls ๋ชจ๋์ ๊ฐ์ ธ์์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝ๋์ ์๋ 3์ค์ ์จ์ค๋๋ค.
function animate() {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
๊ทธ ํ animate ํจ์์ controls.update();๋ฅผ ์ถ๊ฐํด์ค๋๋ค.
const animate = function () {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
controls.update(); // ์ถ๊ฐ !
renderer.render(scene, camera);
};
์ด์ ์คํ์ ํด๋ณด๋ฉด ๋์๊ฐ๋ ํ๊ด์ ์ ์ก๋ฉด์ฒด ํ๋ธ์ ์ปจํธ๋กคํ ์ ์๋ ์นด๋ฉ๋ผ๊ฐ ๋นฐ!!
How to make my landing page
์ค์์ ๋๋ ๋ชจ์์ผ๋ก ๋์๊ฐ๋ ๋ํ๊ณผ ๋๋ค์ผ๋ก ํผ์ ธ์๋ ์๋ค์ด ๋ง์น ์ฐ์ฃผ ๊ฐ์ ๋๋์ ์ค ๊ฒ์ ๋๋ค. script.js ํ์ผ์ ์๊น ํ๋ธ๋ฅผ ๋ง๋ค ๋์ ๊ฐ์ด scene, camera, renderer๋ฅผ ๋ง๋ค์ด ์ค๋๋ค.
// Scene
const scene = new THREE.Scene();
//size
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
// ํ๋ฉด ๋ง์ถค์ ์ํด sizes๋ผ๋ ์์ ์ ์
};
//camera
const camera = new THREE.PerspectiveCamera(
75,
sizes.width / sizes.height,
0.1,
100
);
camera.position.z = 2;
scene.add(camera);
//renderer
const renderer = new THREE.WebGLRenderer({
antialias: true,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
//canvas๊ฐ ํ๋ฆฟํ๊ฒ ์ถ๋ ฅํด์ฃผ๋ ๊ฒ์ ๋ง์
renderer.setClearColor(new THREE.Color("#21282a"), 1);
// ๋ฐฐ๊ฒฝ์์ ์ด๋์ gray๋ก ์ค์
document.body.appendChild(renderer.domElement);
๊ทธ๋ฆฌ๊ณ ๋๋ฉํ์ด์ง๋ฅผ ๋ง๋ค ๋ ์ฌ์ฉ์๊ฐ ํ๋ฉด์ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ๋ ๊ฒ์ ๋์ํ๊ธฐ ์ํด ๋ฐ์ํ์ผ๋ก ๋ง๋ค๊ธฐ ์ํด resize event๋ฅผ ์ถ๊ฐํด์ฃผ์์ต๋๋ค.
window.addEventListener("resize", () => {
// Update sizes
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
// Update camera
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
// Update renderer
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
์ด์ ๋๋ํ ๋ํ*(๋ง๋ ํํ์ธ์ง ๋ชจ๋ฅด๊ฒ ์)*์ ๋ง๋ค ์ฐจ๋ก์
๋๋ค. TorusGeometry๋ฅผ ์ด์ฉํ๋ฉด ๋๋ํ ๋ํ์ด ๋ง๋ค์ด์ง๋๋ค. TorusGeometry์ ์ฒซ ๋ฒ์งธ ์์ฑ์ radius์
๋๋ค. ๋น์ด์๋ ์์ ๋ฐ์ง๋ฆ์ ๋ปํฉ๋๋ค. ๋ ๋ฒ์งธ ์์ฑ์ tube์
๋๋ค. ์ด๋ tube(๋น์ด์์ง ์์ ๋ถ๋ถ)์ ๋ฐ์ง๋ฆ์
๋๋ค. ์ธ ๋ฒ์งธ ์์ฑ์ radialSegments์ด๊ณ ๋ค ๋ฒ์งธ ์์ฑ์ tubularSegments์
๋๋ค. ์ด๋ ์ง์ ์์น๋ฅผ ์กฐ์ ํด๊ฐ๋ฉด์ ๋๋์ ์ก์๊ฐ๋ ๊ฒ์ ์ถ์ฒํฉ๋๋ค. ์๊น๋ Mesh๋ฅผ ์ด์ฉํ์ฌ ํํํ์์ง๋ง ์ด๋ฒ์๋ ์ ์ผ๋ก ๋ง๋๋ ๊ฒ์ด๊ธฐ์ Points๋ผ๋ ๊ฒ์ ์ด์ฉํด์ค๋๋ค. ์ด๋ ๊ฒ ํด์ฃผ๋ฉด ์ค์์ ์๋ ๋๋ํ ๋ํ์ ๋ค ๋ง๋ ๊ฒ์
๋๋ค! (animate์์ ๋์๊ฐ๊ฒ๋ง ํ๋ฉด)
const geometry = new THREE.TorusGeometry(0.7, 0.2, 16, 100);
const material = new THREE.PointsMaterial({
size: 0.005,
color: 0x87a7ca,
});
const sphere = new THREE.Points(geometry, material);
์ด์ ์ฐ์ฃผ ๊ฐ์ ๋๋์ ์ฃผ๊ธฐ ์ํด paticles์ ๋ง๋ค์ด์ฃผ๋ฉด ๋ฉ๋๋ค. paticlesGemotry๋ BufferGeometry๋ฅผ ์ด์ฉํด์ ๋ง๋ค์ด์ค๋๋ค. BufferGeometry๋ ์ฌ์ฉ์ ์ ์ ๋ํ์ด๊ณ , ์ฌ์ฉ์๊ฐ ์ง์ vertices์ position, normals, colors, uv ๋ค์ ์ ์ํ ์ ์์ต๋๋ค. ๋ํ three.js์์ ๋ณ ๋ชจ์ material์ ์๊ธฐ์ ํฌํ ์ต์ ์ด์ฉํ์ฌ ์ง์ pngํ์ผ๋ก ๋ณ ๋ชจ์์ ๋ง๋ค์ด ์ค ๋ค์์ material์ maping ํด์ฃผ์์ต๋๋ค.
const particlesGeometry = new THREE.BufferGeometry();
const loader = new THREE.TextureLoader();
const star = loader.load("./star.png");
const particlesmaterial = new THREE.PointsMaterial({
size: 0.005,
map: star,
transparent: true,
});
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ณ๋ค์ ๋๋ค์ผ๋ก ๋ฟ๋ฆด ์ฐจ๋ก์ ๋๋ค. ๋จผ์ ๋ณ 700๊ฐ์ xyz ๊ฐ์ ๋๋ค ํ๊ฒ ๋๋ฆฌ๊ธฐ ์ํด posArray์ 2100๊ฐ์ง๋ฆฌ ๋ฐฐ์ด์ 32๋นํธ ๋ถ๋ ์์์ ๋ฐฐ์ด์ ๋ง๋ญ๋๋ค. ๊ทธ๋ค์ ๋ฐ๋ณต๋ฌธ์ผ๋ก ๋๋ค ํ๊ฒ ์์น๋ฅผ ๋๋ ค์ค๋๋ค. posArray[i] = Math.random() ์ด๋ฐ ์์ผ๋ก ๋๋ฆฌ๊ฒ ๋๋ค๋ฉด ๋ณ๋ค์ด ํ ๊ณณ์ ๋ญ์น๊ณ ๋ค๋ฅธ ์ด์ํ ๊ณณ์ ์์นํ ๊ฒ์ ๋๋ค. posArray[i] = Math.random() - 0.5 ์ด๋ ๊ฒ ํด์ค๋ค๋ฉด ๊ฐ์ด๋ฐ๋ก ๋ญ์น๊ธฐ๋ ํ์ง๋ง ๋ณ๋ค์ด ๋ถํฌํ์ง ์์ต๋๋ค. ์ถ๊ฐ ์ค๋ช ์ ํ์๋ฉด Math.random ํจ์๋ ๊ธฐ๋ณธ ๋ฒ์๊ฐ 0 ์ด์ 1 ๋ฏธ๋ง์ ๋๋ค. ์ฌ๊ธฐ์ -0.5๋ฅผ ํจ์ผ๋ก์จ ๋ฒ์๋ -0.5 ์ด์ 0.5 ๋ฏธ๋ง์ด ๋ฉ๋๋ค. three.js์์ ๊ธฐ๋ณธ position์ (0,0,0)์ด ์ค์์ด๋ฏ๋ก ๋ณ๋ค์ ์ค์์์ ๋ญ์ณ์์ ๊ฒ์ ๋๋ค. posArray[i] = (Math.random() - 0.5) * 5 ์ด๋ ๊ฒ ํด์ฃผ๋ฉด ๋ญ์ณ์๋ ๊ฒ์ด 5๋ฐฐ ์ปค ๋ณด์ฌ ๊ฑฐ์ ์์ฐ์ค๋ฝ์ง๋ง ์ด๊ฒ์ ๊ทธ๋ฅ ๋จ์ํ ์๊น ์ ์ฝ๋๋ฅผ ํฌ๊ฒ ํด ์ค ๊ฒ์ผ ๋ฟ์ ๋๋ค. posArray[i] = (Math.random() - 0.5) * (Math.random() * 5) ๋ง์นจ๋ด ์ด ์ฝ๋๋ฅผ ์จ์ฃผ๋ฉด ๋ณ๋ค์ ์ค์์์ ์์ฐ์ค๋ฝ๊ฒ ํผ์ง ๊ฒ์ ๋๋ค!
const particlesGeometry = new THREE.BufferGeometry();
const loader = new THREE.TextureLoader();
const star = loader.load("./star.png");
const particlesmaterial = new THREE.PointsMaterial({
size: 0.005,
map: star,
transparent: true,
});
์ฌ์ค ์ด์ ๋ ๋๋ง๋ง ํ๋ฉด ์์ฑ์ด์ง๋ง ์ด๋ ๊ฒ ํ๋ฉด web์ ์ธํฐ๋ ํฐ๋ธ ํ ๋ง์ด ์์ต๋๋ค. ๊ทธ๋์ ์ ๋ ๋ง์ฐ์ค๊ฐ ์์ง์ผ ๋๋ง๋ค ๋ณ๋ค์ด ์์ง์ด๋ event๋ฅผ ๋ฃ์ด์ฃผ์์ต๋๋ค. ์ด๋ ๊ฒ ์ค์ ์ ํด์ฃผ๊ณ animate ํจ์์์ ํด์ฃผ๋ฉด ๋ฉ๋๋ค!
document.addEventListener("mousemove", animateParticles);
let mouseX = 0;
let mouseY = 0;
function animateParticles(event) {
mouseY = event.clientY;
mouseX = event.clientX;
}
์ด์ animate ํจ์๋ฅผ ์์ฑํ๊ณ ์ฌ๊ธฐ์ ๋ํ๋ค์ ์ ๋๋ฉ์ดํ ํด์ฃผ๋ฉด ๋ชจ๋ ๊ฒ์ด ๋!! ์ด ์ธ ๋ค๋ฅธ ๋ถ๋ถ์ three.js ๊ฐ ์๋ CSS ๋ง์ ์ด์ฉํ์ฌ ๋ง๋ ๊ฒ์ด๊ธฐ์ ์ด ํฌ์คํธ ๋ด์ฉ์์๋ ์ ์ธํ์ต๋๋ค.
const clock = new THREE.Clock();
const animate = () => {
window.requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
// ๊ฒฝ๊ณผ์๊ฐ
sphere.rotation.y = 0.5 * elapsedTime;
//๋๋ํ ๋ํ์ y์ถ์ ํ์
particlesMesh.rotation.y = -1 * (elapsedTime * 0.1);
//๋ณ๋ค์ด ๊ฒฝ๊ณผ์๊ฐ๋ง๋ค ์์ ๋ฐฉํฅ์ผ๋ก ์ด๋
if (mouseX > 0) {
//์ฌ์ฉ์๊ฐ ๋ง์ฐ์ค๋ฅผ ์์ง์ด๋ฉด ๊ทธ์ ๋ง์ถฐ ์ธํฐ๋ ํฐ๋ธํ๊ฒ ์์ง์
particlesMesh.rotation.x = -mouseY * (elapsedTime * 0.00008);
particlesMesh.rotation.y = -mouseX * (elapsedTime * 0.00008);
}
renderer.render(scene, camera);
};
animate();
Reference
Three.js - JavaScript 3D library
Working with Three.js Particle Systems - They're AWESOME!
๋ณธ ํฌ์คํธ๋ ์ ์ ํ๋ธ ๋ด์ฉ์ ์ ๊ฐ ํด์ํ ๋๋ก ์ด ๊ฒ์ ๋๋ค.
'Develop ๐ป > JavaScript' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
JS ์๋ฃ ๊ณต์ / JS ๊ธฐ์ด / ์ฝ๋๋ฒ ์ด์ปค๋ฆฌ (0) | 2021.09.01 |
---|---|
[JavaScript] ES7 - 12 ๋ฌธ๋ฒ / ECMAScript 2016 ~ 2021 (2) | 2021.07.25 |
๋๊ธ