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

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

https://youtu.be/Zyi10Fgoqh0

3차원 웹 가상세계에서도 시계는 돌아갑니다.

똑딱똑딱-

마치 디즈니 애니메이션 겨울왕국에서 엘사와 함께 할 수 없어 시계만 마냥 바라보며 소리내는 안나처럼 말입니다 :)

과거에는 시간을 측정하는 도구로서 해시계가 있었지요.

비록 비오는 날이면 무용지물이긴 하지만, 그래도 햇볓의 그림자로 시간을 알아낼 수 있었던 것은 조상들의 지혜가 아닌가 생각됩니다.

모두가 태앙이 지구 주위를 회전한다는 천동설을 주장할 때에

갈릴레오 갈릴레이는 지구가 회전한다는 지동설을 주장하였지요.

협박에 못 이겨서 자신의 주장을 굽히면서도 '그대로 지구는 돈다'라고 되뇌이던 갈릴레오 갈릴레이의 주장은 지금에 와서야 그 사실이 더욱 확고해졌습니다.

하지만, HTML5 3차원 웹 캔버스의 세계에서는 약간 다른 원리가 작동합니다.

바로 천동설이지요 :) 3차원 웹 공간에서 바다와 대륙을 중심으로 태양이 도는 것입니다.

실제 이 지구의 원리와는 어쩌면 다른 세계이고,

갈릴레오 갈릴레이가 들으면 화를 낼 법하기도 한데요.

그렇게 될 수 밖에 없는 이유는 바로 지동설의 세계를 만드는 것은 어렵기 때문입니다. :)

그냥 XYZ축으로 구성된 3차원 공간에서 XZ 축으로 휘어지지 않은 평면이 쫙 뻗어있고

태양이 세계를 빙글빙글 회전하는 것이 더욱 구현하기가 쉽기 때문이지요 :)

이번 시간에는 태양이 캔버스의 세계를 빙글빙글 돌면서 새벽부터 자정까지, 자정부터 다시 다음날 새벽까지 시간이 무한 반복되는 세계를 표현해보겠습니다.

맨 앞에서 보셨던 영상처럼 말이죠 :)

우선 지난 번에 소스를 나누었기 때문에 일부 소스만 수정해주시면 됩니다.

먼저 모델을 불러들이는 부분에 약간의 문제가 있었으며 수정하였습니다.

바로 자바스크립트의 특성 때문인데요.

관련 정의는 아래와 같습니다.

< 자바스크립트 call by value, call by reference 특성 >

자바스크립트에서는 
함수 안에서 변수자체에 값을 개입하면,
함수가 종료될 경우 변수를 사용할 수 없는 특성이 있습니다.

대신 함수 안에서 변수의 속성에 값을 할당하면
함수가 종료되어도 변수의 속성을 사용할 수 있는데요.

이를 위해서 변수를 함수 밖에서 먼저 Object() 형으로 선언하여야 합니다.

변경된 소스는 아래와 같은데요.

세세한 원리까지 설명드리자면, 너무 깊이 들어가니까 차후에 기회가 되면 다뤄보도록 하겠습니다.

< js/LoadModel.js >

// 집 모델
var home_mesh = new Object();
	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 = new Object();
	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);
		}
	);

<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)
{
	if(obj == undefined)obj=new Object();
	loaderMesh.load(
		file,
		function ( collada ){
			obj.obj = collada.scene;
			var i;
			for(i=0;i<obj.obj.children.length;++i)
				obj.obj.children[i].castShadow=true;
			callback(obj.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;

그리고 금번 강좌에 해당하는 새로운 버전의 HTML 파일인데

변경된 부분은 <script src="js/three011.js"></script> 밖에는 없습니다.

<three011.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/three011.js"></script>
	</body>
</html>

그리고 변경된 three011.js 파일의 내용은 아래와 같은데요.

var framesPerSecond=30;

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

	if(home_mesh.obj != undefined)
	{
		home_mesh.obj.rotation.set(-90 * PI_PER_180, 0, -45 * PI_PER_180); 
	}

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

	parameters.azimuth+=0.001;

	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 );
	water.material.uniforms['sunDirection'].value.copy( light_sun.position ).normalize();

	cubeCamera.update( renderer, sky );

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

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

우선 집 모형의 로딩이 완료될 경우 45도로 틀어서 보여주도록 하였습니다.

큰 의미는 없고요. 그림자가 더욱 확연하게 돋보이도록 하기 위함입니다.

if(home_mesh.obj != undefined)
{
	home_mesh.obj.rotation.set(-90 * PI_PER_180, 0, -45 * PI_PER_180); 
}

그리고 태양의 좌표를 조정하는 부분입니다.

사실 외국 소스를 가져온 거라서, 100% 이해가 되지는 않지만,

여러모로 이것 저것 시험해본 결과를 반영한 것이지요.

	parameters.azimuth+=0.001;

	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 );
	water.material.uniforms['sunDirection'].value.copy( light_sun.position ).normalize();

	cubeCamera.update( renderer, sky );

먼저 parameters.azimuth 는 태양의 위치를 의미합니다.

여기서는 0이 새벽 6시이고, 0.5가 오후 6시입니다.

0.75면 밤 12시이고, 0.75부터 1까지는 한 밤중이지요

그 값을 계속 0.001 씩 추가 갱신하면서,

태양의 위치값을 삼각함수를 이용해서 산출해주고,

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 );
	water.material.uniforms['sunDirection'].value.copy( light_sun.position ).normalize();

	cubeCamera.update( renderer, sky );

그래서 시간이 점점 흘러갈수록 태양의 위치와 그림자의 위치도 바뀌는 것이지요.

실제 반영 예제는 아래 URL에서 확인해보실 수 있습니다.

http://dreamplan7.cafe24.com/canvas2/three011.html\

 

3차원웹 캔버스

 

dreamplan7.cafe24.com

여기까지 읽어주셔서 감사드립니다.

수고하셨습니다 :)

 

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

 

3차원 웹 캔버스, 원터치 버튼 이동! - 24번째 시간

https://youtu.be/cnARSpNaAV0 웹의 장점은 빠른 찾기 기능입니다. 지금도 언제든지 크레이의 블로그의 메뉴에서 찾고 싶은 메뉴를 클릭하면 언제든지 해당 메뉴 위치로 이동하지요. ​ 3차원 웹에서도 특정지점으..

itadventure.tistory.com