Three.js 반응형 canvas + 전체화면 만들기
사전 학습
Node.js로 Three.js 시작하기
Three.js 학습을 시작하며, 처음에는 js 파일을 다운로드해서 쓰거나 cdn을 이용하여 간단한 기능을 구현해보았다. 하지만 node 모듈을 깔지 않고는 사용할 수 없는 기능들이 있었다. 그래서 결국 Node
facet.tistory.com
지난 시간에 Node.js 기반 Three.js의 설치에 대해 알아보았다. 이번 글에서는 사전 작업을 바탕으로 three.js 코드가 올라갈 canvas를 html에 추가하고, 이 위에 three.js로 기본 도형인 박스 하나를 구현하며, 구현된 화면 비율이 인터넷 브라우저의 크기 변경과 상관 없이 유지될 수 있도록 canvas와 three.js코드에 반응형 설정을 위한 코드를 추가하고자 한다.
※ 첨부된 코드파일들은 압축해제 후 npm install 을 해서 사용하면 된다. 자세한 방법은 위 "Node.js로 Three.js 시작하기"에 기술되어 있다.
CONTENTS
- Three.js 올라갈 Canvas 추가하기
- Three.js 기본 박스 구현하기
- Three.js 반응형 화면 세팅
- 캔버스 조정하기 - 몰입감을 주는 전체화면으로
Three.js 올라갈 Canvas 추가하기
1. 스타일 시트 추가하기
- index.html이 있는 디렉터리에 'style.css' 파일을 생성한다.
- index.html 파일에 만들어 둔 style.css를 링크하는 코드를 추가한다.
<link rel="stylesheet" href="./style.css">
2. 캔버스 추가하기
- index.html 파일에 <canvas> 태그를 아래와 같이 추가해준다.
- 여기서 주의할 점은, three.js가 담겨 있는 <script> 태그가 <canvas> 태그 이전에 오도록 배치해야 한다.
<body>
<script type="module" src="./script.js"></script>
<canvas class="myCanvas"></canvas>
</body>
3. 서버 구동시키기
- VSCode 에서 아래에 빨간색으로 마크된 부분을 클릭 + 드래그로 끌어 올리면 열려 있는 Terminal 이 올라온다.
- PoserShell로 되어 있다면 아래와 같이 Command Prompt 를 추가해준다.
- 서버를 구동시킨다. 사용하는 인터넷 브라우저에 빈 화면이 뜨고 F12를 눌렀을 때 Module 이 출력되어 있으면 성공.
npm run dev
Three.js 기본 박스 구현하기
Three.js에서 3D환경 조성을 위해 꼭 필요한 4가지 요소가 있으며, 그 중 객체를 만들기 위해서는 3가지 요소가 필요하다.
- 장면 scene
- 객체 mesh = geometry + material
- 카메라 camera
- 렌더러 renderer
만들어둔 script.js 파일 안에 console.log(THREE)는 지우고 아래와 같이 요소들을 하나씩 추가해준다.
1. 장면(scene)
// Scene
const scene = new THREE.Scene()
2. 객체(mesh)
- 기본적인 상자 1개를 만들 예정이라 Box.Geometry를 사용한다.
- 도형정보 담당인 geometry와 색상/재질 정보 담당인 material을 각각 생성해준 뒤 mesh에 둘을 갖다 바친다.
- 그리고 완성된 mesh를 scene에 추가해야 한다.
// Object
const geometry = new THREE.BoxGeometry(1, 1, 1) // (width, height, depth)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
3. 카메라
- 카메라 비율과 캔버스 사이즈를 통합적으로 변경해주기 위해서 size를 별도 변수로 지정한다.
- Three 모듈에서 사용 가능한 카메라 중 가장 대중적인 PerspectiveCamera를 사용하도록 한다.
// Sizes
const sizes = {
width: 800,
height: 600
}
// Camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height)
- 위와 같이 최초 생성된 카메라, 객체 등은 화면 정 중앙에(0,0,0) 모두 생기므로, 카메라가 물체 안에 들어가게 되어 화면에 객체는 안보이고 검은 색만 보일 수도 있다. 따라서 카메라의 위치를 옮겨 객체와의 거리를 확보해준다.(본 샘플 코드에서는 z축으로 이동)
- 그 이후 scene에 생성한 camera를 추가해준다.
camera.position.z = 3
scene.add(camera)
4. 렌더러
- 마지막으로 렌더링 모듈인 WebGLRenderer를 사용하기 위해서는 html에 마련해둔 canvas 객체를 가져와야 한다. 여기서는 document.querySelector를 사용하도록 한다.
- 그리고 renderer의 폭과 높이 사이즈는 카메라 코드 작성시에 생성한 sizes 변수값을 가져와 통일시킨다.
// Canvas
const canvas = document.querySelector('canvas.myCanvas')
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
- 마지막으로 renderer.render() 메소드에 그동안 축적해온 scene과 camera 객체를 넣어 실행해준다.
renderer.render(scene, camera)
[결과 화면]
- 위의 코드를 모두 입력하면 다음과 같이 800px * 600px 화면에 빨간색 박스 하나가 나타난다. 3D 박스 지오메트리를 만들었으나 정면에서 바라보는 각도 때문에 마치 2D 정사각형을 만든 것처럼 보인다.
[코드 파일]
Three.js 반응형 화면 세팅
위와 같이 캔버스의 크기를 고정된 사이즈로 만들면 사용자마다 다른 웹브라우저의 크기에 융통성 있게 대응할 수 없을 뿐더러, 웹브라우저의 크기가 변경되었을 때 화면이 보이는 영역 바깥으로 캔버스가 넘어가게 될 수도 있다. 따라서 캔버스의의 사용자가 사용하는 웹브라우저 크기에 맞추도록 변경하고, 또한 그 창의 크기를 변경할 때마다 우리 캔버스의 사이즈, 카메라 종횡비 등을 함께 변경시킬 수 있도록 하는 "반응형" 코드를 작성해주어야 한다.
Three.js 캔버스를 반응형으로 만들기 위한 2 체크리스트
- 캔버스 기본 사이즈 - 사용자의 웹브라우저로부터
- 리사이즈 이벤트 - 이벤트 리스너와 내부 요소 업데이트
- 화면 해상도 보정하기
1. 캔버스 기본 사이즈 - 사용자의 웹브라우저로부터
- 위 코드에서 800*600으로 고정되어 있었던 sizes 변수의 width 및 height의 값을 웹브라우저의 폭과 높이로 대체한다. 이에 따라 사용자의 해상도 세팅에 맞는 캔버스가 생성되게 된다.
// Sizes
const sizes = {
width: window.innerWidth, // <--- 800
height: window.innerHeight // <--- 600
}
2. 리사이즈 이벤트 - 이벤트 리스너와 내부 요소 업데이트
- 한편, 초기 렌더링 후 사용자가 웹브라우저의 크기를 변경할 때 캔버스 크기는 이전 창의 크기에 맞게 남아 있는 문제가 생긴다. 따라서 script.js 파일 내에 resizing 함수를 구현해줘야 한다.
1) Resize 이벤트 리스너 추가
- 웹브라우저의 크기가 변경됨을 감지하는 event listner에 잇따라 나오는 콜백함수들을 넣어주면 된다. 기본적으로 추가될 resize 이벤트 리스너의 모양은 다음과 같다.
window.addEventListener('resize', () => {});
2) 기본 sizes변수값 변경
- resize 이벤트 리스너의 콜백 함수로서, 캔버스의 크기와 카메라의 종횡비에 영향을 미치는 기본 sizes 변수의 width값과 height 값을 새로운 창 크기에 맞게 변경해주도록 코드를 작성한다.(아래 코드창 참조)
3) Camera 속성 업데이트
- 또한 sizes변수의 값을 바꾼 것과는 별도로, 이에 영향을 받고 있는 다른 요소인 camera의 종횡비(aspect) 또한 수동으로 업데이트시켜 주어야 한다.
- 이렇게 카메라 속성이 일부라도 변경될 경우 카메라의 프로젝션을 연산하는 매트릭스 또한 업데이트를 시켜주어야 하는데, 이때 camera 객체의 멤버함수 중 updateProjectionMatrix()가 그 역할을 담당한다.
- 카메라 속성들의 업데이트가 누락되면 캔버스 크기는 변경되더라도 내부 객체의 비율이 찌그러지는 현상이 발생할 수 있다.
4) 렌더사이즈 변경 + 최종 렌더링
- 마지막으로 렌더러의 사이즈만 바꿔주고
- 그리고 렌더링 함수를 다시 호출해준다.
// 1) resize 이벤트 리스너 추가
window.addEventListener('resize', () =>
{
// 2) 기본 size 업데이트
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// 3) camera 종횡비 업데이트
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix() //<-- 카메라의 프로젝션 매트릭스 업데이트
// 4) renderer 사이즈 업데이트
renderer.setSize(sizes.width, sizes.height)
// 4) final 렌더링
renderer.render(scene, camera);
})
[결과 화면]
- 왼쪽처럼 창을 좁게 줄이거나, 오른쪽처럼 창을 늘렸을 때 캔버스의 크기가 따라서 변경되며 내부에 구현된 객체도 비율이 깨지지 않는 모습을 확인할 수 있다.
3. 화면 해상도 보정하기
- 위까지 완성했으면 반응형 캔버스는 이미 다 만든 상태이다. 하지만 요즘 컴퓨터, 휴대폰, 태블릿 등 기기들의 웹브라우저 해상도가 각양 각색으로 달라지면서, 해상도가 너무 낮거나 높은 경우 캔버스 내에 구현된 객체들이 뿌옇게 보이는 현상 등이 나타날 수 있다.
- pixel ratio 가 1 상승할수록 화면을 그리는 데에 필요한 연산은 제곱으로 늘어나는 데에 비해, pixel ratio가 2를 넘는 경우 사람이 느끼는 시각적 차이는 크지 않다. 특히 최신 휴대폰 같은 경우 불필요하게 pixel ratio가 높아서(~5까지도 나왔다고 함) 로딩 속도 저하와 배터리 소모에 영향을 줄 수 있다. 이 때문에 사족 같지만, canvas의 pixel ratio를 2를 넘지 않도록 맞춰주는 것이 구동 효율성 향상과 일관성 있는 해상도 확보를 위해 필요하다.
- 이를 수행하는 함수는 renderer에 포함되어 있으며, setPixelRatio()라고 한다. 이 코드를 renderer.render()함수들 앞에 한 줄씩, 총 2번 추가하면 해상도 보정 부분은 가볍게 마무리된다. 참고로 괄호 안에 들어가는 Math.min() 함수는 기기의 PixelRatio가 1일 때에는 1을 반환하고, 2보다 클 때에는 2를 반환하도록 짜여져 있다.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
...
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.render(scene, camera);
...
window.addEventListener("resize", () => {
....
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.render(scene, camera);
})
[코드 파일]
캔버스 조정하기 - 몰입감을 주는 전체화면으로
위 결과 화면을 자세히 보면 캔버스의 가로/세로 사이즈를 브라우저의 사이즈와 동일하게 맞추었음에도, padding과 margin 값 등이 기본으로 설정되어 있어서 scroll 바가 생긴 것을 볼 수 있다. 이와 같이 몰입감을 떨어뜨리는 불필요한 css 기본 세팅들을 제거하고, 더불어 사용자가 캔버스를 더블클릭하면 전체화면으로 전환되도록 기능을 구현해보도록 한다.
1. 몰입감을 저해하는 CSS 기본 세팅 제거
- 처음에 만들어둔 style.css 파일에 다음과 같이 코드를 작성한다.
1) html 내부 모든 요소들의 여백을 0으로
*
{
margin: 0;
padding: 0;
}
2) 캔버스의 위치를 왼쪽 상단에 고정. 외곽선 없애기
.myCanvas
{
position: fixed;
top: 0;
left: 0;
outline: none;
}
3) canvas가 웹브라우저 크기보다 크더라도 걍무시(스크롤바 X)
html,
body
{
overflow: hidden;
}
[결과 화면]
- 검은 캔버스 왼쪽과 상단에 있던 흰 여백들이 모두 사라지고, 자동으로 생성되던 가로/세로 스크롤바도 모두 사라졌다. 깔끔 그 잡채.
[코드파일(css만)]
2. 더블클릭시 전체 화면으로
- 게임 등의 화면을 구동하기 위해서는 전체화면으로 전환이 필요할 때가 있다. 전체화면으로의 전환 기능은 필요시에 아래와 같이 구현할 수 있다.
1) 이벤트 리스너 추가
- 마우스 더블클릭을 감지하기 위해서 아래와 같은 이벤트 리스너를 추가한다.
window.addEventListener('dblclick', () => {})
2) 토글 기능 구현
- 더블클릭 이벤트가 발생했을 때 이미 전체화면인 상태이면 화면 전체화면 종료를, 그렇지 않으면 전체화면으로 전환하는 토글 기능을 if 조건문으로 구현한다.
window.addEventListener('dblclick', () =>
{
if(!document.fullscreenElement)
{
canvas.requestFullscreen()
}
else
{
document.exitFullscreen()
}
})
3) 애플 호환용 코드 추가
- 위 코드에서 애플 기기에서 호환될 수 있도록 webkit로 시작하는 코드를 추가한다.
window.addEventListener('dblclick', () =>
{
const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement
if(!fullscreenElement)
{
if(canvas.requestFullscreen)
{
canvas.requestFullscreen()
}
else if(canvas.webkitRequestFullscreen)
{
canvas.webkitRequestFullscreen()
}
}
else
{
if(document.exitFullscreen)
{
document.exitFullscreen()
}
else if(document.webkitExitFullscreen)
{
document.webkitExitFullscreen()
}
}
})
[결과 화면]
- 전체화면 토글 기능이 잘 작동한다.
[코드파일]