다중 장면, 다중 캔버스 만들기

이런 HTML 구조가 있다고 했을 때..

이런 HTML 구조가 있다고 했을 때..

여러 개의 캔버스가 위와 같은 사각형 내에 각각 들어간다고 상상해보자 어떻게 만들 수 있을까? 언뜻 보기에는 3개의 canvas 위에 three.js를 입히면 되지않을까? 싶지만 각 캔버스마다 자원을 공유할 수 없어서 3개의 별도 three.js가 굴러가게 된다. 즉 엄청 무거워진다. 비효율적.. 따라서 여러 개를 사용하면서 효율적인 방법을 찾아야 한다.

한 판에 여러 개의 Three.js 즉, 여러 개의 Scene을 넣어보자. (다중 캔버스, 다중 장면 만들기 참고) 우리가 사용할 방법은 html 엘리먼트로 위치와 크기를 잡아 놓은 뒤 하나의 canvas에 해당하는 위치와 크기를 이용해 영역을 지정해서 해당하는 영역만 렌더링 시켜주는 방법이다. 각각의 조명과 mesh들을 가지고 있겠지만, 하나의 renderer를 사용할 수 있게 되고, 자원을 공유할 수 있게되어서 효율적이라고 할 수 있음

비치는 사각형 영역만 렌더링..

비치는 사각형 영역만 렌더링..

multicanvas 프로젝트 환경 세팅을 마친 뒤 초기 코드는 아래와 같다.

multicanvas/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>three.js</title>
	<link rel="stylesheet" href="./main.css">
</head>
<body>
	<canvas id="three-canvas"></canvas>
	<ul class="view-list">
		<li class="view-item">
			<div class="canvas-placeholder a"></div>
			<p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Nisi, neque.</p>
		</li>
		<li class="view-item">
			<div class="canvas-placeholder b"></div>
			<p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Nisi, neque.</p>
		</li>
		<li class="view-item">
			<div class="canvas-placeholder c"></div>
			<p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Nisi, neque.</p>
		</li>
	</ul>
</body>
</html>

multicanvas/src/main.js

import * as THREE from 'three';

// ----- 주제: 여러개의 캔버스 사용하기

// 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);

// 그리기
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();

위 코드는 scene, light, mesh 등이 설정되어있지 않은 상태이다. 우선 위 이미지의 배치를 위해 css 부터 추가해보고자 한다.

src/main.css

body {
	margin: 0;
}

li {
	list-style: none;
}

#three-canvas {
	position: absolute;
	left: 0;
	top: 0;
	z-index: -1; /* canvas는 html보다 뒤에 있어야 하므로 */
	background: gray; /* canvas 확인을 위해 색깔 추가 */
}

.view-item {
	display: flex;
	margin: 30px;
}

.view-item:nth-child(even) .canvas-placeholder {
	order: 2;
}

.canvas-placeholder {
	width: 300px;
	height: 200px;
	margin-right: 30px;
	border: 1px solid black;
}

이제 스크립트를 본격적으로 작성해본다. 위 main.js에서 renderer는 한개만 쓰지만 들어갈 박스마다 scene은 각각 생성할 것이다. 이를 공통 모듈로 만들어보고자 함

scene을 따로 만든다는 것은 각 scene마다 카메라도 각각 배치되어야 하므로 setSize 안에 존재하는 camera 설정도 삭제해준다. 즉, setSize 에는 renderer.setSize(window.innerWidth, window.innerHeight)만 존재하게됨. draw 함수의 camera 설정도 제거해준다.

/src/main.js

// ..

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);
}

이제 정말 각 scene을 CreateScene이라는 클래스 모듈로 구현해본다. 먼저 해당 클래스에 어떤 값이 들어가야할까 고민해보면 아래와 같다.

src/main.js

import { CreateScene } from "./CreateScene";

// Renderer..
const scene1 = new CreateScene({
  renderer,
  bgColor: "pink",
  placeholder: ".canvas-placeholder.a"
});

// ...