만들어질 페이지를 동영상으로 미리 봐 주세요.

https://youtu.be/ZNrmBoMf0YE

 

결과 페이지는 아래와 같습니다.

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

 

 

 

 

 

지금부터 이걸 만드는 과정을 시작합니다.

 

 

 

 

지난 시간에는 3차원 공간 안에 집과 바닥을 창조해보았지요?

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

근데 바닥이 좀 밋밋하니, 돌바닥으로 바꿔보겠습니다.

회색 바닥을 생성하는 아래 코드를 지우고,

floor = new THREE.Mesh(
	new THREE.BoxGeometry(100, 0.1, 100), 
	new THREE.MeshStandardMaterial({color: 0x808080})
);

이렇게 바꿔볼까요? 참고로 바닥의 크기도 좀 줄었습니다.

floor = new THREE.Mesh(
	new THREE.BoxGeometry(10, 0.1, 10)
);
loader.load(
	'http://dreamplan7.cafe24.com/canvas/img/floor1.jpg', 
	function ( texture ) {
		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;
	}
);

바닥이 벽돌 무늬가 되었습니다.

여기에 적용된 무늬인 벽돌 텍스쳐는 이렇게 생긴 이미지인데, 무료 이미지 사이트 픽사베이에서 다운받아 상하좌우 반복되도록 편집하였습니다.

http://dreamplan7.cafe24.com/canvas/img/floor1.jpg

먼저 이 부분은 표면이 아무것도 정의되지 않은 얇은 상자를 하나 만드는 것입니다.

floor = new THREE.Mesh(
	new THREE.BoxGeometry(10, 0.1, 10)
);

그리고 나서, 이미지 파일을 불러와서 3차원 물체 표면에 텍스쳐로 적용하는 것이지요.

지난번에 정육면체에 무늬 입힌 것과 같은 구조입니다.

loader.load(
	'http://dreamplan7.cafe24.com/canvas/img/floor1.jpg', 
	function ( texture ) {
		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;
	}
);

그런데 새로운 부분이 하나 생겼군요?

바로 repeat 와 RepeatWrapping 이라는 부분입니다.

floor.material.map.repeat.x=3;
floor.material.map.repeat.y=3;
floor.material.map.wrapS=THREE.RepeatWrapping;
floor.material.map.wrapT=THREE.RepeatWrapping;

이 부분은 벽돌텍스쳐가 물체의 표면에 입혀질 때 반복할 횟수를 지정하는 부분입니다.

반복 횟수를 가로, 세로 3번씩 해주었기 때문에, 아래와 같이 촘촘하게 벽돌이 구성되었습니다.

이제 바닥도 좀 뽀대(?) 나게 되었으니 밋밋한 하늘을 꾸며보도록 하겠습니다.

원래는 스카이박스라고 해서 이미지로 하늘을 꾸미는게 있긴 한데,

공모양의 스카이 박스는 360 VR 카메라가 있어야 촬영이라도 할 수 있고,

큐브 모양의 스카이 박스라고 해서 이미지를 6개 준비하는 방식이 있는데

만들어서 적용해본 결과 여간 맞추기 힘들더라구요 :)

그래서 검색해본 결과 더욱 멋진 효과를 발견하여 적용해보았습니다.

실제 하늘과 같은 수준의 그래픽 효과인데요.

일부 구형 PC에서는 혹시 작동안될까 약간 염려스럽기도 합니다.

이를 위해 three.js 에서 제공하는 하늘 셰이더 스크립트가 사용됩니다.

먼저 스크립트를 설정하는 이 부분 소스를 찾으신 다음에,

<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="http://dreamplan7.cafe24.com/canvas/js/Sky.js"></script>

그리고 태양빛을 생성하는 스크립트 아랫 부분에,

light_sun.shadowCameraTop=shadowBlur;
light_sun.shadowCameraBottom=-shadowBlur;
        :

아래 소스를 추가해 줍니다.

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 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;

시험삼아 turbidity 값을 바꿔보니

sky.material.uniforms['turbidity'].value=1;

이렇게 되더군요 :) 뭔가 번짐효과 처럼 느껴집니다.

다음 파라미터 정의 부분에서도 흥미로운 부분은 azimuth 입니다.

var parameters = {

distance: 400,

inclination: 0.1,

azimuth: 0.05

};

태양의 위치가 바뀌거든요. 해가 뜨고 진다고 보면 됩니다.

값을 변경함에 따라 아주 다양한 연출이 좌우됩니다.

azimuth: 0.0

azimuth: 0.02

azimuth: 0.1

azimuth: 0.25 - 그림자 위치를 보면 완전 정오입니다. 하늘이 푸르르네요.

그 이하 나머지 소스는 아직 크레이도 100% 이해 못했기 때문에 넘어가도록 하겠습니다.

이제 바다를 만들어 보도록 할까요?

바다를 만들기 위해서 먼저 하늘처럼 js 파일을 포함시켜야 합니다.

아까 추가한 하늘 스크립트 불러오는 아랫 부분에,

<script src="http://dreamplan7.cafe24.com/canvas/js/Sky.js"></script>
         :

바다 스크립트를 추가해보겠습니다.

<script src="http://dreamplan7.cafe24.com/canvas/js/Water.js"></script>

그리고 바다를 만들어보겠습니다.

아까처럼 태양광을 생성하는 아랫부분에,

light_sun.shadowCameraTop=shadowBlur;
light_sun.shadowCameraBottom=-shadowBlur; 
          :

다음과 같은 스크립트를 넣습니다.

// Water
var waterGeometry = new THREE.PlaneBufferGeometry( 100000, 100000 );

water = new THREE.Water(
	waterGeometry,
	{
		textureWidth: 512,
		textureHeight: 512,
		waterNormals: new THREE.TextureLoader().load( 'http://dreamplan7.cafe24.com/canvas/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;
scene.add( water );

멋진 물이 생겨났습니다만, 물이 미동하지 않습니다.,

처음 영상에서는 분명 물이 움직였는데 말이죠.

물을 움직이려면 애니메이션에서 물 텍스쳐의 time 값을 지속적으로 변경시켜주어야 합니다. 아래와 같은 소스 아랫 부분에,

var animate = function () {
	// 프레임 처리
	setTimeout(function() {
		 requestAnimationFrame(animate); 
	}, 1000 / framesPerSecond);
           :

아래 소스를 추가해 주세요.

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

물이 막 흘러갑니다. 멋지지요?

이 외에도 몇가지 더 손을 본 부분이 있지만 사소한 부분이라 간단히 정리하겠습니다.

전체 소스 따로 게제하니 그냥 참고만 해 주세요.

우선 자그마한 큐브섬의 느낌을 주기 위해, 큐브 크기를 작게 줄이고,

물위에 떠 있도록 위치를 바닥과 집의 위치를 조정하였습니다.

var floor;
floor = new THREE.Mesh(
	new THREE.BoxGeometry(10, 10, 10)
);
          :
floor.position.set(0, -3, 0);

그리고 카메라가 물 아래쪽으로 가면 물 속에서 보는 느낌이 안 들기 때문에

카메라의 회전 반경을 제한하였지요.

// 카메라가 회전하는
var controls = new THREE.OrbitControls (camera, renderer.domElement);
controls.enablePan = false;
controls.minPolarAngle = Math.PI / -2;
controls.maxPolarAngle = Math.PI / 2.1;

 

 

 

 

 

반영된 전체 소스는 아래와 같습니다.

여기까지 읽어 주셔서 감사합니다 :)

<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 src="http://dreamplan7.cafe24.com/canvas/js/Sky.js"></script>		
		<script src="http://dreamplan7.cafe24.com/canvas/js/Water.js"></script>		

		<script>

			// ==========================
			// 초기화 부분 시작 ( 이 부분은 문서에서 한번만 수행되면 됩니다 )
			// ==========================

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

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

			// 렌더러 정의 및 크기 지정, 문서에 추가하기
			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.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,3,0);
					scene.add( model );

      });

			// 바닥
			var floor;
			floor = new THREE.Mesh(
				new THREE.BoxGeometry(10, 10, 10)
			);
			loader.load(
					'http://dreamplan7.cafe24.com/canvas/img/floor1.jpg', 
					function ( texture ) {
						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;
					}
			);
			scene.add(floor);

			floor.position.set(0, -3, 0);
			floor.receiveShadow=true;

			// 카메라의 위치 조정
			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;

			// 전체 조명을 추가합니다.
			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;

			// Water
			var waterGeometry = new THREE.PlaneBufferGeometry( 100000, 100000 );

			water = new THREE.Water(
				waterGeometry,
				{
					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;
			scene.add( water );

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

			cubeCamera.update( renderer, sky );

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

			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();		
		</script>
	</body>
</html>

 

 

 

 

 

출처 : https://itadventure.tistory.com/56

 

+ Recent posts