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

3차원 웹, 소스의 체계적 정리, 22번째 시간

지난번 시간까지는 3차원 웹을 구현하는 방법에 대해서 다뤄보았었는데요.

소스가 단순할 때는 느끼지 못했지만 소스가 점점 길어지다보니까

불편한 점이 하나둘 생겨나게 됩니다.

소스가 길어질수록 스크롤의 압박을 견뎌내야 하는 것도 그렇지만

추가되는 기능을 넣어야 할 때도 한참 찾아야 하는 불편이 있습니다.

하지만 다행스럽게도 이를 체계적으로 정리할 수 있는 방법이 있는데요.

바로 파일을 나누는 겁니다. 용도별로 말이죠.

이번 시간에는 지난 시간까지 다루어보았던 하늘과 바다, 섬, 집 컨텐츠가 포함된 3차원 가상공간을 구현한 소스를 분류하는 부분을 다뤄보도록 하겠습니다.

이를 위해 폴더와 파일 구성을 다음과 같이 꾸며보도록 하겠습니다.

사실 이 파일 구성은 통째로 다운받으실 수 있게 아래 첨부파일로 마련해 놓았으니 그냥 다운받으신 다음 압축을 풀어 웹서버에 올려 주시면 됩니다.

canvas.zip
0.64MB

오늘은 소스를 요약해서 설명드리는 차원으로 진행하도록 하겠습니다.

먼저, three010.html 파일이 웹페이지입니다.

소스가 아주 간결해졌는데요.

자바스크립트를 모두 다른 파일로 빼놓아서 그렇습니다.

<three010.html>

<html>
	<head>
		<title>3차원웹 캔버스</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="js/OrbitControls.js"></script>
		<script src="js/ColladaLoader.js"></script>
		<script src="js/Sky.js"></script>		
		<script src="js/Water.js"></script>
		<!-- 이 위쪽은 three.js 라이브러리 -->
		<script src="js/CrayCommon.js"></script>
		<script src="js/LoadCamera.js"></script>
		<script src="js/LoadLight.js"></script>
		<script src="js/LoadSkySea.js"></script>
		<script src="js/LoadModel.js"></script>
		<script src="js/three010.js"></script>
	</body>
</html>

소스 중에서 three.js 의 부속에 해당하는

OrbitControl.js, ColladaLoader.js, Sky.js, Water.js 파일은 모두 외부서버가 아닌

내부 서버에서 작동할 수 있도록 구성을 변경하였습니다.

<script src="js/OrbitControls.js"></script>
<script src="js/ColladaLoader.js"></script>
<script src="js/Sky.js"></script>		
<script src="js/Water.js"></script>

그리고 이어서 CrayCommon.js 파일을 포함하도록 되어 있는데요.

CrayCommon.js 는 3차원 웹을 구성할 때 공통적으로 수행하는 기능들,

즉 장면 Scene 과 각종 로더를 생성하고 렌더러를 정의하는 부분, 그리고 DAE 파일을 불러오는 함수를 정의하였습니다.

<js/CrayCommon.js>

// 3차원 세계
var scene = new THREE.Scene();

const PI_PER_180 = Math.PI / 180;

var loader = new THREE.TextureLoader();
var loaderMesh = new THREE.ColladaLoader();

function loadDAE(file, obj, callback)
{
	loaderMesh.load(
		file,
		function ( collada ){
			obj = collada.scene;
			var i;
			for(i=0;i<obj.children.length;++i)
				obj.children[i].castShadow=true;
			callback(obj);
	});
}

// 렌더러 정의 및 크기 지정, 문서에 추가하기
var renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFShadowMap;		// <-- 속도가 빠르다
renderer.gammaInput = true;
renderer.gammaOutput = true;

다음으로 이어서 카메라 스크립트를 포함하여 구성합니다.

소스가 아주 소박(?)하지요?

하지만 나중에 점점 기능이 늘어난다고 치면 애시당초 이렇게 파일을 나누는 것이 좋습니다.

<js/LoadCamera.js>

// 카메라 ( 카메라 수직 시야 각도, 가로세로 종횡비율, 시야거리 시작지점, 시야거리 끝지점
var camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 1, 20000 );
	camera.position.set ( 25, 5, 3 );

// 카메라 컨트롤러
var controls = new THREE.OrbitControls (camera, renderer.domElement);
	controls.enablePan = false;
	controls.minPolarAngle = Math.PI / -2;
	controls.maxPolarAngle = Math.PI / 2.1;

다음으로 빛을 관리하는 스크립트로 나누어 지는데요.

전체 조명과 태양광 조명이 이에 해당합니다.

태양의 좌표를 지정하는 소스가 없어졌는데, 사실 그 다음 스크립트인 하늘과 바다에서 태양광의 위치를 다시 계산해서 굳이 필요없기 때문입니다.

만약 하늘과 바다 스크립트를 뺀다면, 태양광의 좌표를 정해주어야 하지요.

<js/LoadLight.js>

// 전체 조명
var light_base = new THREE.AmbientLight( 0xf0f0f0 ); // soft white light
	scene.add( light_base );

// 태양 조명
var light_sun = new THREE.DirectionalLight ( 0x808080, 5.0 );
	var shadowBlur=10;
	light_sun.castShadow=true;
	light_sun.shadow.camera.left=-shadowBlur;
	light_sun.shadow.camera.right=shadowBlur;
	light_sun.shadow.camera.top=shadowBlur;
	light_sun.shadow.camera.bottom=-shadowBlur;
	scene.add( light_sun );

이어서, 하늘과 바다를 관리하는 스크립트입니다.

이 스크립트는 꽤 내용이 깁니다.

하지만 앞으로는 하늘과 바다를 다룰 일이 없다면 이 소스를 열어 볼일은 한동안 없겠지요.

<js/LoadSkySea.js>

// 하늘
var sky = new THREE.Sky();
	sky.material.uniforms['turbidity'].value=10;
	sky.material.uniforms['rayleigh'].value=2;
	sky.material.uniforms['luminance'].value=1;
	sky.material.uniforms['mieCoefficient'].value=0.005;
	sky.material.uniforms['mieDirectionalG'].value=0.8;

	var parameters = {
		distance: 400,
		inclination: 0.1,
		azimuth: 0.05
	};

	var cubeCamera = new THREE.CubeCamera( 0.1, 1, 512 );
	scene.background = cubeCamera.renderTarget;

	var theta = Math.PI * ( parameters.inclination - 0.5 );
	var phi = 2 * Math.PI * ( parameters.azimuth - 0.5 );

	light_sun.position.x = parameters.distance * Math.cos( phi );
	light_sun.position.y = parameters.distance * Math.sin( phi ) * Math.sin( theta );
	light_sun.position.z = parameters.distance * Math.sin( phi ) * Math.cos( theta );

	sky.material.uniforms['sunPosition'].value = light_sun.position.copy( light_sun.position );

	cubeCamera.update( renderer, sky );

// 바다
var water = new THREE.Water(
		new THREE.PlaneBufferGeometry( 100000, 100000 ),
		{
			textureWidth: 512,
			textureHeight: 512,
			waterNormals: new THREE.TextureLoader().load( 'img/waternormals.jpg', function ( texture ) {

				texture.wrapS = texture.wrapT = THREE.RepeatWrapping;

			} ),
			alpha: 1.0,
			sunDirection: light_sun.position.clone().normalize(),
			sunColor: 0xffffff,
			waterColor: 0x001e0f,
			distortionScale: 3.7,
			fog: scene.fog !== undefined
		}
	);

	water.rotation.x = - Math.PI / 2;
	water.material.uniforms['sunDirection'].value.copy( light_sun.position ).normalize();
	scene.add( water );

다음으로는 각종 모델을 불러오는 LoadModel.js 입니다.

모델을 추가할 때는 이 파일만 열어서 추가해주시면 끝입니다.

<js/LoadModel.js>

// 집 모델
var home_mesh;
	loadDAE(
		'http://dreamplan7.cafe24.com/canvas/img/homeK.dae',
		home_mesh,
		function(obj){
			scene.add( obj );
			obj.rotation.set(-90 * PI_PER_180, 0, -90 * PI_PER_180); 
			obj.position.set(0,3,0);
		}
	);

// 섬 모델
var land_mesh;
	loadDAE(
		'http://dreamplan7.cafe24.com/canvas/dae/island3.dae',
		land_mesh,
		function(obj){
			scene.add( obj );
			obj.rotation.set(-90 * PI_PER_180, 0, 0); 
			obj.position.set(0,-50,50);
			obj.scale.set(300,300,300);
		}
	);

// 바닥
var floor;
	loader.load(
		'http://dreamplan7.cafe24.com/canvas/img/floor1.jpg', 
		function ( texture ) {
			floor = new THREE.Mesh(
				new THREE.BoxGeometry(10, 10, 10)
			);
			floor.material = new THREE.MeshStandardMaterial({map: texture});
			floor.material.map.repeat.x=3;
			floor.material.map.repeat.y=3;
			floor.material.map.wrapS=THREE.RepeatWrapping;
			floor.material.map.wrapT=THREE.RepeatWrapping;
			floor.position.set(0, -3, 0);
			floor.receiveShadow=true;
			scene.add(floor);
		}
	);

마지막으로 three010.js 입니다.

아직은 소스량이 거의 없는데요.

앞으로 이 부분에 가장 많은 소스가 실릴 예정입니다

결국에는 이 곳의 소스를 또 다시 주제별로 분리할 예정입니다.

<js/three010.js>

var framesPerSecond=30;

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

	water.material.uniforms[ 'time' ].value += 1.0 / 60.0;

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

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

사실 이번에는 딱히 큰 기능적 추가 이슈는 없습니다.

다만 소스를 체계적으로 관리하도록 분리하는 방법을 잠깐 요약해보았는데요.

three010.html 파일 내용을 다시 한번 살펴보면 마치 각 스크립트 기능들을

도서관에서 책을 종류별로 분리하듯 나눈 이 구조가 훨씬 깔끔해보이시는 않으시는지요 ? :)

<html>
	<head>
		<title>3차원웹 캔버스</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="js/OrbitControls.js"></script>
		<script src="js/ColladaLoader.js"></script>
		<script src="js/Sky.js"></script>		
		<script src="js/Water.js"></script>
		<!-- 이 위쪽은 three.js 라이브러리 -->
		<script src="js/CrayCommon.js"></script>
		<script src="js/LoadCamera.js"></script>
		<script src="js/LoadLight.js"></script>
		<script src="js/LoadSkySea.js"></script>
		<script src="js/LoadModel.js"></script>
		<script src="js/three010.js"></script>
	</body>
</html>

그냥 지나치셨을 수도 있으니 전체 파일이 포함된 첨부 파일은 본문 중간에 있다는 점 알려드립니다 :)

이 방식이 적용된 예제는 아래 페이지에 있습니다. 지난번과 결과물은 똑같지만 소스 구성이 잘 분리된 소스라는 점에서 수정이 용이한 이점이 있습니다.

http://dreamplan7.cafe24.com/canvas2/three010.html

 

3차원웹 캔버스

 

dreamplan7.cafe24.com

여기까지 읽어주시느라 수고하셨습니다~

 

다음강좌 고고싱! / https://itadventure.tistory.com/61

 

3차원 웹, 해가 뜨고 해가 지고, 23번째 시간

https://youtu.be/Zyi10Fgoqh0 3차원 웹 가상세계에서도 시계는 돌아갑니다. 똑딱똑딱- 마치 디즈니 애니메이션 겨울왕국에서 엘사와 함께 할 수 없어 시계만 마냥 바라보며 소리내는 안나처럼 말입니다 :) ​ 과..

itadventure.tistory.com