본문 바로가기
코딩과 알고리즘

3차원 웹, 그림같은 집을 지어요, 캔버스와 함께하는 자바스크립트, 19번째 시간

 

 

미리보기

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

 

3차원 캔바스 예제 1

 

dreamplan7.cafe24.com

지난 강좌까지는 단순한 상자를 가지고 계속 작업을 했습니다만,

오늘부터는 블렌더에서 직접 모델링한 미니 장난감 집을 띄워보는 시간을 갖도록 하겠습니다 :)

집모양을 블렌더에서 만드는 부분은 사실 스크립트의 강좌내용을 벗어나는 부분이라서

집을 만드는 부분은 여기서는 다루지 않겠지만, 추후 블렌더 강좌에서 한번 다뤄보도록 하겠습니다.

지난 블렌더 강좌는 아래 링크에서 주루룩 보실수 있습니다.

1부 30강까지 다 마스터하신 분이라면 이런 집은 그냥 만드실 수도 있을듯 합니다.

https://blog.naver.com/PostList.nhn?blogId=ephraimdrlee&from=postList&categoryNo=21

집의 모형 파일은 크레이가 블렌더를 이용해서 만들었으니 여러분은 그냥 그 모델을 사용하시면 되는데요.

블렌더를 하시는 분을 위해서 모델의 구조를 간략히 소개해드리면,

모든 오브젝트는 합쳐져 있지 않습니다. 모두가 제 각각 분리가 된 형태로

6개의 오브젝트로 각각 나눠져 있는데요.

원기둥 3개는 그냥 밝은 회색을, 나머지 집은 하나의 빨간 천 텍스쳐를 UV 매핑하여 적용하였습니다.

캔버스에서 사용하기 위해서는 집 오브젝트 구성하는 6개를 모두 선택한 다음에,

파일 - 내보내기 - 콜라다(Collada) 파일로 내보내주시면 되겠습니다.

그리고 이 파일과 텍스쳐(이미지) 파일을 서버에 올려주시면 되겠습니다.

노파심에 말씀드리지만, 이 부분은 자바스크립트와는 관련이 없으니

갑자기 새로운 모르는 내용이 나왔다고 당황하지 마세요 :)

자, 이제 지난번까지 작업해왔던 상자를 모두 과감히 지우겠습니다.

바닥은 빼고요 ㅎㅎ

아래 소스 부분을 삭제해 주시기 바랍니다.

// 큐브1
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);
	}
);

// 큐브2
var mesh2;
loader.load(
	'http://dreamplan7.cafe24.com/SL/img/%EC%B9%B4%EB%93%9C_%EA%B1%B4%EB%B0%98%ED%9A%A8%EA%B3%BC.jpg', 
	function ( texture ) {
		mesh2 = new THREE.Mesh(
			new THREE.BoxGeometry(3, 3, 3), 
			new THREE.MeshStandardMaterial({map: texture})
		);
		scene.add(mesh2);
		mesh2.position.set(5, 0, 0);
	}
);

// 큐브3
var mesh3;
loader.load(
	'http://dreamplan7.cafe24.com/SL/img/%EC%B9%B4%EB%93%9C_%EB%AC%B4%ED%95%9C%EC%BB%A4%ED%94%BC.jpg', 
	function ( texture ) {
		mesh3 = new THREE.Mesh(
			new THREE.BoxGeometry(3, 3, 3), 
			new THREE.MeshStandardMaterial({map: texture})
		);
		scene.add(mesh3);
		mesh3.position.set(-5, 0, 0);
	}
);

그리고 그 부분에 3차원 모델 파일을 불러오는 부분을 추가해볼까요?

var model;
var loaderMesh = new THREE.ColladaLoader();
loaderMesh.load(
	'http://dreamplan7.cafe24.com/canvas/img/home.dae',
	function ( collada ){						
		model = collada.scene;			
		scene.add( model );
});

3차원 모델파일을 불러오는 기능을 가진 것을 THREE.ColladaLoader(); 라고 합니다.

콜라다 로더라고 하는데요.

이 객체를 선언한 다음

var loaderMesh = new THREE.ColladaLoader();

load 명령을 이용해서 3차원 모델파일을 불러오는 것이지요.

loaderMesh.load(

'img/home.dae',

function ( collada ){

:

});

그리고 3차원 모델 파일을 지정하는 것은 function 안쪽에서 처리하도록 합니다.

파라미터 collada 가 주어지는데 불러온 3차원 모델은 collada 가 아니라 collada.scene 입니다.

model 변수에 이 값을 넣어주면 앞으로 애니메이션 등을 제어할 수가 있습니다.

model = collada.scene;

scene.add( model );

이 패턴에 3차원 모델 파일을 불러오는 부분인데요.

앞으로 쭉 자주 사용될테니 굳이 외우지 않으셔도 됩니다 :)

음 불러오긴 했는데 뭔가 좀 이상하지요?

집이 옆으로 뉘인 것도 그렇고, 우선 표면에 아무것도 입혀지지 않았습니다.

집이 옆으로 뉘인 것은 블렌더와 캔버스의 좌표 체계가 다르기 때문입니다.

블렌더에서는 Z축방향이 하늘방향이지만, 캔버스에서는 Y축방향이 하늘방향이기 때문이지요.

그냥 간단히 각도를 90도 회전해주면 해결됩니다.

X축만 90도 회전하면 되긴 하지만 Z축도 회전시켜서 집이 왼쪽을 바라보게 합시다.

금방 작업한 바로 아랫 부분에 넣어 주세요.

model.rotation.x= -90 * ( Math.PI / 180 ); 
model.rotation.z= -90 * ( Math.PI / 180 ); 

이제 집의 무늬가 입혀지지 않은 원인이 뭔지 살펴볼텐데요.

한가지 결론을 먼저 말씀드리면, 블렌더에서 내보내기한 모델에는 UV맵이라는 정보는 존재하지만 이미지파일명이나 색상정보는 들어있지 않습니다.

그러니까 결론은 하나하나 텍스쳐(그림파일)이나 색상을 지정해줘야 한다는 것이지요.

텍스쳐는 그림파일처럼 바로 적용할 수가 없어서 상자처럼 텍스쳐 로더를 이용해야 합니다.

역시 금방 추가한 바로 아랫 부분에 넣어주시면 됩니다.

loader.load(
	'http://dreamplan7.cafe24.com/canvas/img/checkPattern.jpg', 
	function ( texture ) {
		model.children[0].children[0].material = new THREE.MeshStandardMaterial({map: texture});
		model.children[1].children[0].material = new THREE.MeshStandardMaterial({map: texture});
		model.children[2].children[0].material = new THREE.MeshStandardMaterial({map: texture});
		model.children[3].children[0].material = new THREE.MeshStandardMaterial({map: texture});
		model.children[4].children[0].material = new THREE.MeshStandardMaterial({map: texture});
		model.children[5].children[0].material = new THREE.MeshStandardMaterial({map: texture});
	}
);

참고로 http://dreamplan7.cafe24.com/canvas/img/checkPattern.jpg 파일은 이렇게 생긴 그림 파일입니다

텍스쳐 로더가 아래 명령을 이용해서 위 이미지를 텍스쳐로 받아옵니다.

loader.load(

'http://dreamplan7.cafe24.com/canvas/img/checkPattern.jpg',

:

}

그러면 그 텍스쳐를 6개 오브젝트의 표면에 각각 지정해 주면 되는 것이지요.

주의할 점은 model.children[순번].children[0] 과 같은 형태로 써주셔야 합니다.

model.children[순번] 은 사실 형태가 없는 케이스같은 것이고,

model.children[순번].children[0] 이 실제 3차원 모델의 한 조각입니다.

model.children[0].children[0].material = new THREE.MeshStandardMaterial({map: texture});

:

model.children[0].children[5].material = new THREE.MeshStandardMaterial({map: texture});

그러면 아래와 같은 집이 보일 겁니다.

"근데 둥그런 기둥은 원래 하얀색이었지 않나요?"

예리하십니다 :)

사실 둥그런 기둥은 텍스쳐를 입히면 안되고 색상 코드를 입혀야 합니다.

몇번째인지 판단하는 기능을 만드는건 사실 복잡합니다.

그래서 그냥 0, 1, 2번이 흰기둥이라는 사실을 알려드리겠습니다.

앞의 코드에서 0, 1, 2 번을 이렇게 고쳐 주세요.

model.children[0].children[0].material = new THREE.MeshStandardMaterial({color: 0xf0f0f0});
model.children[1].children[0].material = new THREE.MeshStandardMaterial({color: 0xf0f0f0});
model.children[2].children[0].material = new THREE.MeshStandardMaterial({color: 0xf0f0f0});

아주 예쁘게 나왔습니다만, 아직 완성된 것은 아닙니다.

그림자가 없거든요 ㅎㅎ

그림자를 매길 때에도 children 을 2단계 들어가서 적용해주셔야 합니다.

model.children[0].children[0].castShadow=true;
model.children[1].children[0].castShadow=true;
model.children[2].children[0].castShadow=true;
model.children[3].children[0].castShadow=true;
model.children[4].children[0].castShadow=true;
model.children[5].children[0].castShadow=true;

약간 마우스로 클릭해서 돌려보셔야 그림자가 보이실 겁니다.

그림자가 생기긴 했는데 건물이 약간 하늘로 떠서 그림자도 떨어져 보이는군요.

건물을 약간 아래쪽으로 내려보겠습니다.

model.position.set(0,-0.52,0);

자 이제 완성이 되었습니다.

하늘이 검정색이라 좀 마음에 안 드는군요.

하늘을 우선 하늘색으로 바꿔보겠습니다.

아래와 같은 부분을 찾으셔서

var scene = new THREE.Scene();
       :

그 아랫 부분에 다음 코드를 넣어 주세요.

scene.background = new THREE.Color( 0x50bcdf );

좀 밋밋하긴 하지만 하늘이 하늘색이 되었습니다 :)

이 것으로 완성되었습니다.

하나만 더 수정해보겠습니다.

크레이가 어제 알아낸건데,

아래와 같은 부분을

renderer.shadowMap.type = THREE.PCFSoftShadowMap;

이렇게 바꿔주세요.

renderer.shadowMap.type = THREE.PCFShadowMap;

만일 속도가 느려서 버벅거렸다면 엄청나게 빨라질겁니다.

아마도 그래픽 카드가 부드러운 그림자를 지원하지 않으면 꽤 느려지는 듯 합니다.

여기까지 전체 소스 공개합니다 :)

읽어주시느라 수고하셨습니다.

<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 src="http://fenixrepo.fao.org/cdn/js/threejs/4.4/OrbitControls.js"></script>
		<script src="https://cdn.rawgit.com/mrdoob/three.js/r69/examples/js/loaders/ColladaLoader.js"></script>		
		<script>

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

			// 카메라 ( 카메라 수직 시야 각도, 가로세로 종횡비율, 시야거리 시작지점, 시야거리 끝지점
			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 );
			renderer.shadowMapEnabled = true;
			//renderer.shadowMap.type = THREE.PCFSoftShadowMap;
			renderer.shadowMap.type = THREE.PCFShadowMap;		// <-- 속도가 빠르다
			renderer.gammaInput = true;
			renderer.gammaOutput = true;

			var model;

			var loader = new THREE.TextureLoader();
			var loaderMesh = new THREE.ColladaLoader();
			loaderMesh.load(
				'http://dreamplan7.cafe24.com/canvas/img/home.dae',
				function ( collada ){						
					model = collada.scene;
					loader.load(
						'http://dreamplan7.cafe24.com/canvas/img/checkPattern.jpg', 
						function ( texture ) {

							model.children[0].children[0].material = new THREE.MeshStandardMaterial({color: 0xf0f0f0});
							model.children[1].children[0].material = new THREE.MeshStandardMaterial({color: 0xf0f0f0});
							model.children[2].children[0].material = new THREE.MeshStandardMaterial({color: 0xf0f0f0});
							model.children[3].children[0].material = new THREE.MeshStandardMaterial({map: texture});
							model.children[4].children[0].material = new THREE.MeshStandardMaterial({map: texture});
							model.children[5].children[0].material = new THREE.MeshStandardMaterial({map: texture});
						
							model.children[0].children[0].castShadow=true;
							model.children[1].children[0].castShadow=true;
							model.children[2].children[0].castShadow=true;
							model.children[3].children[0].castShadow=true;
							model.children[4].children[0].castShadow=true;
							model.children[5].children[0].castShadow=true;
						}
					);

					model.rotation.x= -90 * ( Math.PI / 180 ); 
					model.rotation.z= -90 * ( Math.PI / 180 ); 
					model.position.set(0,-0.52,0);
					scene.add( model );

      });

			// 바닥
			var floor;
			floor = new THREE.Mesh(
				new THREE.BoxGeometry(100, 0.1, 100), 
				new THREE.MeshStandardMaterial({color: 0x808080})
			);
			scene.add(floor);
			floor.position.set(0, -1.5, 0);
			floor.receiveShadow=true;

			// 카메라의 위치 조정
			camera.position.set ( 5, 5, 7 );
			camera.lookAt(0, 0, 0);
			// camera.rotation.set ( -35 * ( Math.PI / 180 ), 35 * ( Math.PI / 180 ), 0 );
	
			// 카메라가 회전하는
			var controls = new THREE.OrbitControls (camera, renderer.domElement);
			controls.update();

			// 전체 조명을 추가합니다.
			var light_base = new THREE.AmbientLight( 0xf0f0f0 ); // soft white light
			scene.add( light_base );

			var light_sun = new THREE.DirectionalLight ( 0x808080, 5.0 );
			light_sun.position.set( 200, 200, 300 );
			scene.add( light_sun );
			shadowBlur=10;
			light_sun.castShadow=true;
			light_sun.shadowCameraLeft=-shadowBlur;
			light_sun.shadowCameraRight=shadowBlur;
			light_sun.shadowCameraTop=shadowBlur;
			light_sun.shadowCameraBottom=-shadowBlur;

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

			var framesPerSecond=30;

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

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

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