스크롤에 따라 3D 애니메이션이 구현되는 예제를 만들어본다.
우선 아래의 기본 구조에서 시작해본다.
scroll_page/src/main.js
import * as THREE from 'three';
// ----- 주제: 스크롤에 따라 움직이는 3D 페이지
// Renderer
const canvas = document.querySelector('#three-canvas');
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
// Scene
const scene = new THREE.Scene();
// Camera
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.y = 1.5;
camera.position.z = 4;
scene.add(camera);
// Light
const ambientLight = new THREE.AmbientLight('white', 0.5);
scene.add(ambientLight);
// Light ..
// Mesh ..
// 그리기
const clock = new THREE.Clock();
function draw() {
const delta = clock.getDelta();
renderer.render(scene, camera);
renderer.setAnimationLoop(draw);
}
function setSize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
}
// 이벤트
window.addEventListener('resize', setSize);
draw();
우선 Light는 기존 DirectionalLight를 사용하지 않고 spotLight를 적용한다.
// Renderer, Scene, Camera, Light
const ambientLight = new THREE.AmbientLight('white', 0.5);
scene.add(ambientLight);
// spot Light 추가
const spotLight = new THREE.SpotLight("white", 0.7);
spotLight.position.set(0, 150, 100);
spotLight.castShadow = true; // 그림자 효과
spotLight.shadow.mapSize.width = 1024; // 그림자의 Quality
spotLight.shadow.mapSize.height = 1024; // 그림자의 Quality
spotLight.shadow.camera.near = 1; // 그림자를 찍을 카메라의 Near
spotLight.shadow.camera.far = 200; // 그림자를 찍을 카메라의 Far
scene.add(spotLight);
// Mesh ..
다음으로는 Mesh를 그려주는데, 땅 위에 집이 있는 구조이므로 땅을 먼저 그려주면 아래와 같음.
// Renderer, Scene..
const scene = new THREE.Scene();
scene.background = new THREE.Color("white"); // background Scene 추가
// Camera, Light, SpotLight..
// Mesh
const floorMesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshStandardMaterial({ color: "white" }));
floorMesh.rotation.x = -Math.PI / 2;
scene.add(floorMesh);
위와 같이 추가해주면 바닥면과 하얀색 하늘이 표현됨. 바닥을 굳이 넣는 이유는 그림자 표현을 위해서이다. 이제 스크롤에 따라 보여줄 html을 추가해준다.
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./main.css">
</head>
<body>
<canvas id="three-canvas"></canvas>
<div class="sections">
<section class="section">
<h2>01</h2>
</section>
<section class="section">
<h2>02</h2>
</section>
<section class="section">
<h2>03</h2>
</section>
<section class="section">
<h2>04</h2>
</section>
<section class="section">
<h2>05</h2>
</section>
</div>
</body>
</html>
src/main.css
body {
margin: 0;
}
#three-canvas {
position: fixed;
left: 0;
top: 0;
}
.sections {
position: relative;
z-index: 1;
}
.section {
height: 100vh;
padding: 5rem;
box-sizing: border-box;
}
.section h2 {
margin: 0;
font-size: 7vmin; /* vmin = viewport minimum, 가로, 세로 사이즈 중 작은 값 */
}
.section:nth-child(odd) {
text-align: right;
}
이제 배경을 흰색으로 돌려주고, 집을 놓아보자. 모듈로 집을 구현하려고 한다. 먼저 집을 구현해야할 때 전달해야 하는 인자가 어떤 것들이 있을지 생각해보자.
src/main.js
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { House } from "./House";
// Renderer, Scene, Camera, Light, SpotLight..
// Mesh ..
scene.add(floorMesh);
const gltfLoader = new GLTFLoader();
const houses = [];
houses.push(new House({
gltfLoader, // house.glb 로드를 위함
scene, // scene에 추가를 위함
modelSrc: "/models/house.glb", // House 객체에 적용될 glb 이미지
x: 0, // x 좌표
z: 0, // z 좌표
height: 2 // mesh 별 height 별도로 주입
}));
위와 같이 전달인자를 세팅해주었다. House 클래스를 구현해본다.