본문 바로가기
자바스크립트와 캔버스

3차원 웹, 이미지를 부르다, 캔버스와 함께하는 자바스크립트, 15번째 시간

반응형

※ 2020. 8. 4 : 크로스 도메인 이슈로 크레이의 웹서버에서 이미지를 불러오지 못하는 이슈가 수정되었습니다. 

지난 시간에는 canvas 에서 3차원 공간의 개념에 대해 다루어 보았었지요?
그리고 예제 하나를 구경해보았었습니다.

https://itadventure.tistory.com/45

 

3차원 공간의 개념, 캔버스와 함께하는 자바스크립트, 14번째 시간

지난 시간에는 HTML 표준 기술 캔버스에서 자바스크립트를 이용하여 3차원 모양의 정육면체를 회전시켜보는 예제를 보았었습니다 :) ​https://blog.naver.com/ephraimdrlee/221570755838 3D세계의 창조, 캔버스와..

itadventure.tistory.com

이번시간에는 물체의 좌표와 카메라 제어에 대해 살펴볼텐데요.

지난 내용을 살펴보셨다면, 3차원 공간을 표현할 기본 틀에 대해 이해하셨을 겁니다.

이제 3차원 공간을 빛과 물체로 꾸밀 시간입니다.

아래 그림을 하나 살펴볼텐데요.

그림에서 회색 상자는 X좌표, Y좌표, Z좌표값을 가집니다.

 

기본적으로 모든 물체, 즉 오브젝트(Object)는 생성하는 즉시 좌표값이 (0, 0, 0) 입니다.

그러니까 X, Y, Z 3축의 교차점에 물체가 생겨나게 됩니다.

빛도 예외가 아니지요.

빛을 만드는 명령어는 아래와 같습니다.

var light1 = new THREE.PointLight( 0xffffff, 1, 100 );

이 명령어를 어디에 넣느냐하면 기본 소스에서 이 부분 아래에 넣는다고 설명드린 바 있지요 :)

document.body.appendChild( renderer.domElement );
          :

빛을 만든 다음 아래처럼 3차원 장면에 추가하면,

scene.add( light1 );

기본적으로 (0, 0, 0) 좌표에 위치합니다.
빛은 모양이 없지만 이해를 돕기 위해 공 모양으로 표시합니다 :)

이 빛의 위치를 X좌표축으로 -5만큼 이동하고,

Y 좌표축으로 2만큼 이동합니다.

그리고 Z좌표축으로 1만큼 이동하면 이와 같은 그림이 되겠지요.

이 좌표를 ( -5, 2, 1) 이라고 지칭합니다.

이렇게 좌표를 지정하는 방법은 2가지가 있습니다.

첫번째 방법은 아래와 같지요. 금방 그림에서처럼 x, y, z 좌표를 하나씩 이동하는 방법이 있고,

light1.position.x = -5;
light1.position.y = 2;
light1.position.z = 1;

두번째 방법은 x, y, z 좌표를 한꺼번에 지정하는 방법입니다.

결과는 똑같습니다.

light1.position.set( -5, 2, 1 );

여러분은 어떤 걸로 하시겠어요 ?

당연히 2번째가 편리합니다 :) 크레이도 2번째 방법을 선택하겠습니다.

이런 방법으로 빛과 물체를 추가하는 겁니다.

머리속에서 3차원 좌표를 생각해 보기도 하고,

결과 화면을 보기도 하면서 계속 헤메다 보면 감이 생깁니다.

크레이도 처음에는 전혀 감이 오지 않더라구요 :)

지금 추가한 빛을 포인트 라이트라고 하는데요.

근방의 있는 물체 표면에 빛을 비추는 겁니다.

단점은, 거리가 멀어지면 거의 빛을 비추지 않는게 단점이랄까요?

아래 2개의 상자가 그 차이를 보여주는데요 확연히 달라보이지요?

이번 예제에서는 근처에 바짝 붙어서 빛을 잘 비춰주니 걱정 않으셔도 됩니다 :)

포인트 라이트는 물체의 반대쪽은 빛을 비추지 않기 때문에 3군데나 빛을 주었습니다.

아래 3번씩이나 라이트를 주는 이유는 바로 그 때문이지요.

물론 좌표는 다 다르지요.

// 빛을 생성해서
var light1 = new THREE.PointLight( 0xffffff, 1, 100 );
// 위치를 적당한 지점에 놓고
light1.position.set( 5, 5, 5 );
// 장면에 추가합니다.
scene.add( light1 );

// 빛을 또한 생성해서
var light2 = new THREE.PointLight( 0xffFFFF, 1, 100 );
// 위치를 적당한 지점에 놓고
light2.position.set( 7, -5, 6 );
// 장면에 추가합니다.
scene.add( light2 );

// 빛을 또한 생성해서
var light3 = new THREE.PointLight( 0xffffff, 1, 100 );
// 위치를 적당한 지점에 놓고
light3.position.set( -7, 3, 3 );
// 장면에 추가합니다.
scene.add( light3 );

자, 그 다음에 사물을 추가할 차례인데요.

이번엔 좀 색다른걸 하나 추가해보겠습니다.

바로 물체의 표면에 이미지를 넣어보도록 하겠습니다.

이를 위해 텍스쳐 로더 ( Texture Loader ) 라는 게 필요한데요.

참고로 3차원 그래픽에서는 이미지를 텍스쳐라고 부릅니다.

이미지를 불러들여 3차원 공간에서 쓸 수 있게 해주는 친구이지요.

아래와 같이 정의하면, 텍스쳐 로더가 생겨납니다.

var loader = new THREE.TextureLoader();

텍스쳐 로더를 정의했으니 이제 이미지를 불러볼까요?

텍스쳐 로더에서 이미지를 부르는 방법은 아래와 같습니다.

거의 정형화된 방법으로 생각하시면 좋은데요.

loader.load(
	'이미지 파일의 url', 
	function ( texture ) {
		:  // 이미지 로딩이 완료되었을때 할일
	}
);
       :

이미지 로딩은 사실 시간이 걸립니다.

그래서 점점 ( : ) 으로 표시된 부분은 대부분 이미지가 모두 로딩되기 전에 실행됩니다.

그러다 보면 물체 표면에 이미지를 입혀도 잘리거나 입혀지지 않는 문제가 발생할 수 있지요. 그래서 이미지가 모두 로딩되면 물체 표면에 이미지를 입혀주는 방법을 많이 사용하는데요. function ( texture ) { .. } 안쪽은 이미지 로딩이 끝났을 때 실행되는 부분이니 여기에 관련 기능을 만들어 주면 됩니다.

	function ( texture ) {
		:  // 이미지 로딩이 완료되었을때 할일
	}

상자를 만들고 상자 표면을 텍스쳐로 입히는 소스는 아래와 같습니다.

그리고 3차원 장면에 추가하면 되겠지요.

var mesh = new THREE.Mesh(
	new THREE.BoxGeometry(3, 3, 3), 
	new THREE.MeshStandardMaterial({map: texture})
);
scene.add(mesh);

THREE.BoxGeometry 는 정육면체를 의미합니다.

그리고 THREE.MeshStandardMaterial({map: texture}) 는 금방 이미지를 로딩이 끝나고 파라미터로 넘어오는 function ( texture ) { 텍스쳐를

'매핑'이라는 기술로 감싸주는 일을 하는데, 보통은 정육면체의 경우 각각의 면에 1:1로 텍스쳐를 입혀줍니다.

그런데 만일 소스를 아래와 같이 구성하면,

loader.load(
	'이미지URL', 
	function ( texture ) {
		var mesh = new THREE.Mesh(
			new THREE.BoxGeometry(3, 3, 3), 
			new THREE.MeshStandardMaterial({map: texture})
		);
		scene.add(mesh);
	}
);

mesh 라고 이름지은 변수는 아무 때나 사용할 수가 없습니다.

왜냐하면 자바스크립트는 함수 내부에서 선언된 변수는 지역변수라고 생각해서

소멸되기 때문입니다.

함수 밖에서 미리 전역변수로 공변수를 선언해놓고 초기화하는 방법으로 이 mesh 변수를 사용할 수 있습니다.

소스는 아래와 같지요. 이미지 주소는 크레이가 준비한 것이지만 여러분의 사진을 넣으셔도 뭐 무방합니다 :)

var mesh;
loader.load(
	'http://dreamplan7.cafe24.com/SL/img/%EC%B9%B4%EB%93%9C_%EC%97%B0%EB%82%A0%EB%A6%AC%EA%B8%B0.jpg', 
	function ( texture ) {
		mesh = new THREE.Mesh(
			new THREE.BoxGeometry(3, 3, 3), 
			new THREE.MeshStandardMaterial({map: texture})
		);
		scene.add(mesh);
	}
);

이 mesh 는 이제 언제든지 좌표값을 조정해 위치를 조정할 수도 있고,

특정 방향을 바라보도록 회전할 수도 있습니다.

그리고 중요한 것 중의 하나는 카메라의 위치와 각도입니다.

사물이 기본적으로 (0, 0, 0) 위치에 생겨나니까 그대로 두려면

카메라는 물체보다 약간 뒤로 이동해야 물체가 보입니다.

camera.position.z = 7;

그리고 카메라를 약간 위로 이동해야 물체가 입체적으로 보이는데,

camera.position.y = 5;

아래방향으로 내려다 보도록 하는게 구도 잡기 좋습니다.

아래 공식은 35도 아래로 내려다 보게 하는 코드이지요

camera.rotation.x = -35 * ( Math.PI / 180 );

기왕이면 오른쪽에서 왼쪽을 바라보는 구도까지 진행해볼까요.

카메라를 오른쪽으로 이동하고 왼쪽으로 35도 각도 돌려보는 공식입니다.

camera.position.x = 5;
camera.rotation.y = 35 * ( Math.PI / 180 );

재미있지 않나요 ? :)

오늘의 설명은 이것으로 그치겠습니다.

아울러 추가 기능이 들어간 소스 버전을 공개해드릴텐데요.

역시 해설은 나중에 진행하도록 하겠습니다 :)

간단한 사용 조작법은 그냥 마우스로 박스를 클릭하면

박스가 반응해서 회전하는 것이지요.

대신 허공을 클릭하면 아무 반응이 없습니다 :)

 

<html>
	<head>
		<title>3차원 캔바스 예제 1</title>
		<style>
			body { margin: 0; }
			canvas { width: 100%; height: 100% }
		</style>
	</head>
	<body>
		<script src="https://threejs.org/build/three.min.js"></script>
		<script>

			// ==========================
			// 초기화 부분 시작 ( 이 부분은 문서에서 한번만 수행되면 됩니다 )
			// ==========================
			// 3차원 세계
			var scene = new THREE.Scene();

			// 카메라 ( 카메라 수직 시야 각도, 가로세로 종횡비율, 시야거리 시작지점, 시야거리 끝지점
			var camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 0.1, 1000 );

			// 렌더러 정의 및 크기 지정, 문서에 추가하기
			var renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } );
			renderer.setSize( window.innerWidth, window.innerHeight );
			
			document.body.appendChild( renderer.domElement );

			// 빛을 생성해서
			var light1 = new THREE.PointLight( 0xffffff, 1, 100 );
			// 위치를 적당한 지점에 놓고
			light1.position.set( 5, 5, 5 );
			// 장면에 추가합니다.
			scene.add( light1 );

			// 빛을 또한 생성해서
			var light2 = new THREE.PointLight( 0xffFFFF, 1, 100 );
			// 위치를 적당한 지점에 놓고
			light2.position.set( 7, -5, 6 );
			// 장면에 추가합니다.
			scene.add( light2 );

			// 빛을 또한 생성해서
			var light3 = new THREE.PointLight( 0xffffff, 1, 100 );
			// 위치를 적당한 지점에 놓고
			light3.position.set( -7, 3, 3 );
			// 장면에 추가합니다.
			scene.add( light3 );

			
			var loader = new THREE.TextureLoader();

			var mesh;
			loader.load(
				'http://dreamplan7.cafe24.com/SL/img/%EC%B9%B4%EB%93%9C_%EC%97%B0%EB%82%A0%EB%A6%AC%EA%B8%B0.jpg', 
				function ( texture ) {
					mesh = new THREE.Mesh(
						new THREE.BoxGeometry(3, 3, 3), 
						new THREE.MeshStandardMaterial({map: texture})
					);
					mesh.name='Box1';
					scene.add(mesh);
				}
			);

			// 카메라의 Z좌표를 물체에서 7 정도 떨어진 지점에 위치합니다.
			camera.position.z = 7;

			camera.position.y = 5;			
			camera.rotation.x = -35 * ( Math.PI / 180 );

			camera.position.x = 5;
			camera.rotation.y = 35 * ( Math.PI / 180 );

			// ==========================
			// 초기화 부분 끝
			// ========================== 

			var framesPerSecond=60;
			var speed=0;

			// 에니메이션 효과를 자동으로 주기 위한 보조 기능입니다.
			var animate = function () {
				// 프레임 처리
				setTimeout(function() {
					 requestAnimationFrame(animate); 
				}, 1000 / framesPerSecond);

				if(speed>0){
					mesh.rotation.y +=speed;
					speed-=0.001;
				}

				// 랜더링을 수행합니다.
				renderer.render( scene, camera );
			};

			// animate()함수를 최초에 한번은 수행해주어야 합니다.
			animate();

			function onDocumentMouseDown(event)
			{
				event.preventDefault();
				var mouse = new THREE.Vector2(
					( event.clientX / window.innerWidth ) * 2 - 1,
					- ( event.clientY / window.innerHeight ) * 2 + 1
				);
				var raycaster = new THREE.Raycaster();
				raycaster.setFromCamera( mouse, camera );
				var intersects = raycaster.intersectObjects( scene.children );
				if(intersects.length>0)
				{
					if(intersects[0].object.name=='Box1')
					{
						speed+=0.05;
					}
				}
			}
			
			document.addEventListener('mousedown', onDocumentMouseDown, false);
						
		</script>
	</body>
</html>

본 예제는, 아래 url 에서 직접 살펴 보실 수 있습니다.

 

http://dreamplan7.cafe24.com/canvas/three003.php

 

다음강좌 보러가기 / https://itadventure.tistory.com/51

 

 

반응형