Intro to WebGL with Three.js

WebGL

JavaScript API for rendering interactive 2D and 3D graphics inside an HTML < canvas > element

지원되는 브라우저 크롬, 파이어폭스, IE11+, 오페라, 사파리, iOS8+, 안드로이드크롬

three.js

threejs.org 3D Javascript Library Renderers: WebGL, < canvas >, < svg >, CSS3D/DOM, and more Scenes, Cameras, Geometry, 3D Model Loaders, Lights, Materials, Shaders, Particles, Animation, Math Utilities

<!DOCTYPE  html>
<html  lang="en">
<head>
	<meta  charset="UTF-8">
	<meta  name="viewport"  content="width=device-width, initial-scale=1.0">
	<meta  http-equiv="X-UA-Compatible"  content="ie=edge">
	<title>Document</title>
	<style>
		html,body{margin:0;padding:0;overflow:hidden;}
	</style>
</head>
<body>
	<script  src="https://threejs.org/build/three.min.js"></script>
	<script>
		var  scene = new  THREE.Scene();
		var  aspect = window.innerWidth / window.innerHeight;
		var  camera = new  THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
		var  renderer = new  THREE.WebGLRenderer();
		renderer.setSize(window.innerWidth, window.innerHeight);
		document.body.appendChild(renderer.domElement);


		var  geometry = new  THREE.BoxGeometry(1,1,1);
		var  material = new  THREE.MeshNormalMaterial();
		var  cube = new  THREE.Mesh(geometry, material);
		scene.add(cube);
		camera.position.z = 5;


		var  render = function(){
			requestAnimationFrame(render);
			cube.rotation.x += 0.1;
			cube.rotation.y += 0.1;
			renderer.render(scene, camera);
		}
		render();
	</script>
</body>
</html>

Scene Graph

enter image description here

Object 3D

var  group = new  THREE.Group();
scene.add(group);  


group.add(mesh1);
group.add(mesh2);  


mesh2.visible = false;
group.remove(mesh2);  


group.children;//mesh1
group.parent;//scene

Object3D Transforms

mesh.position.x = 0;
mesh.position.x = -100;
mesh.scale.set(2,2,2);
mesh.rotation.y = Math.PI / 4;
mesh.rotation.y = Math.PI * 5 / 4

Unit Circle

enter image description here

mesh.rotation.y = THREE.Math.degToRad(45);

enter image description here

mesh.position.x = Math.cos(time);
mesh.position.y = Math.sin(time);

Camera

frustum-3d

cam = new  THREE.PerspectiveCamera(fov, aspect, near, far);
cam.fov = 15;
cam.fov = 60;
// 참고 : http://davidscottlyons.com/threejs-intro/#slide-33
cam.far = 1000;
cam.far = 3000;
// 참고 : http://davidscottlyons.com/threejs-intro/#slide-35


cam = new  THREE.OrthographicCamera(left, right, top, bottom, near, far);

Camera Controls

OrbitControls

Orbit controls을 사용하면 카메라가 대상 주위를 공전 할 수 있습니다.

<script src="path/to/OrbitControls.js"></script>

controls = new THREE.OrbitControls( camera );

function render() {
  requestAnimationFrame( render );
  controls.update();
  renderer.render( scene, camera );
}
controls.enablePan = false;
controls.enableZoom = false;
controls.enableRotate = false;

controls.minDistance
controls.maxDistance

controls.minPolarAngle
controls.maxPolarAngle
<!DOCTYPE  html>
<html  lang="en">
<head>
    <meta  charset="UTF-8">
    <meta  name="viewport"  content="width=device-width, initial-scale=1.0">
    <meta  http-equiv="X-UA-Compatible"  content="ie=edge">
    <title>Document</title>
    <style>
        html,body{margin:0;padding:0;overflow:hidden;}
    </style>
</head>
<body>
    <script  src="https://threejs.org/build/three.min.js"></script>
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/141228/OrbitControls.js"></script>
    <script>
        var scene = new  THREE.Scene();
        var aspect = window.innerWidth / window.innerHeight;
        var camera = new  THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
        
        var renderer = new  THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);     

        //Show Axis
        var axes = new THREE.AxisHelper(10);
        scene.add(axes);
        var  geometry = new  THREE.BoxGeometry(1,1,1);
        var  material = new  THREE.MeshNormalMaterial();
        var  cube = new  THREE.Mesh(geometry, material);
        scene.add(cube);
        camera.position.z = 5;

        var controls = new THREE.OrbitControls( camera, renderer.domElement );
        controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled,컨트롤에 무게감을주는 데 사용할 수있는 댐핑 (관성)을 활성화하려면 true로 설정하십시오. 기본값은 false입니다.
        controls.dampingFactor = 0.05;
        controls.screenSpacePanning = false;//패닝시 카메라의 위치가 어떻게 변환되는지 정의합니다. 참이면 카메라가 화면 공간에서 이동합니다. 그렇지 않으면 카메라가 카메라의 위쪽 방향과 직교하는 평면으로 이동합니다. 기본값은 false입니다.
        controls.minDistance = 2;
        controls.maxDistance = 100;
        controls.maxPolarAngle = Math.PI / 2;   
        controls.autoRotate = true; //Set to true to automatically rotate around the target.

        var  render = function(){
            requestAnimationFrame(render);
            /* cube.rotation.x += 0.1;
            cube.rotation.y += 0.1; */
            controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
            renderer.render(scene, camera);
        }
        render();
    </script>
</body>
</html>

Geometry

// cube
var geo = new THREE.BoxGeometry( width, height, depth );
// sphere
var geo = new THREE.SphereGeometry( 60, 24, 16 );
// cyliner
var geo = new THREE.CylinderGeometry( ... );
// doughnut
var geo = new THREE.TorusGeometry( ... );

Materials

// MeshBasicMaterial : A material for drawing geometries in a simple shaded (flat or wireframe) way.
var material = new THREE.MeshBasicMaterial({ ... });
// MeshLambertMaterial : A material for non-shiny surfaces, without specular highlights.
var material = new THREE.MeshLambertMaterial({ ... });
// MeshPhongMaterial : A material for shiny surfaces with specular highlights.
var material = new THREE.MeshPhongMaterial({ ... });
// MeshStandardMaterial : A standard physically based material, using Metallic-Roughness workflow.
var material = new THREE.MeshStandardMaterial({ ... });
// MeshToonMaterial : An extension of the MeshPhongMaterial with toon shading.
var material = new THREE.MeshToonMaterial({ ... });
// MeshNormalMaterial : A material that maps the normal vectors to RGB colors.
var material = new THREE.MeshNormalMaterial({ ... });

Material Properties

var  material = new  THREE.MeshNormalMaterial({
	/* 
	Defines whether this material is transparent. This has an effect on rendering as transparent objects need special treatment and are rendered after non-transparent objects.
	When set to true, the extent to which the material is transparent is controlled by setting its opacity property.
	Default is false. 
	*/
	transparent:false,
	/* Float in the range of 0.0 - 1.0 indicating how transparent the material is. A value of 0.0 indicates fully transparent, 1.0 is fully opaque.
	If the material's transparent property is not set to true, the material will remain fully opaque and this value will only affect its color.
	Default is 1.0. */
	opacity:1.0,
	/*Whether to have depth test enabled when rendering this material. Default is true.*/
	depthTest:true,
	/* Whether rendering this material has any effect on the depth buffer. Default is true. */
	depthWrite:true,
	/* Sets the alpha value to be used when running an alpha test. The material will not be renderered if the opacity is lower than this value. Default is 0. */
	alphaTest:0,
	/* Defines whether this material is visible. Default is true. */
	visible:true,
	/* Defines which side of faces will be rendered - front, back or both. Default is THREE.FrontSide. Other options are THREE.BackSide and THREE.DoubleSide. */
	side:THREE.FrontSide,
	/* Define whether the material is rendered with flat shading. Default is false. */
	flatShading:false,
	/* Render geometry as wireframe. Default is false (i.e. render as smooth shaded). */
	wireframe:false,
	/* Controls wireframe thickness. Default is 1. */
	wireframeLinewidth:1,
});

Texture Mapping

<!DOCTYPE  html>
<html  lang="en">
<head>
	<meta  charset="UTF-8">
	<meta  name="viewport"  content="width=device-width, initial-scale=1.0">
	<meta  http-equiv="X-UA-Compatible"  content="ie=edge">
	<title>Document</title>
	<style>
		html, body{overflow: hidden;width: 100%;height: 100%;}
		* { margin: 0; padding: 0; }
		canvas { width: 100%; height: 100% }  
	</style>
</head>
<body>
	<script  src="./three.min.js"></script>
	<script src="./OrbitControls.js"></script>
	<script>		
		var camera, scene, renderer, pointLight, controls;
		var earthMesh;

		function init() {
			camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 5000);
			camera.position.set(5, 50, 10)

			scene = new THREE.Scene();
			scene.background = new THREE.Color(0x000000);
			renderer = new THREE.WebGLRenderer({
				antialias: true
			});
			// renderer.setClearColor(0xff0000)
			renderer.setSize(window.innerWidth, window.innerHeight);
			renderer.shadowMap.enabled = true;
			renderer.shadowMap.type = THREE.PCFSoftShadowMap;
			document.body.appendChild(renderer.domElement);

			// Controls
			controls = new THREE.OrbitControls(camera, renderer.domElement);
			controls.target = new THREE.Vector3(0, 50, 0);
			// controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
			controls.update();
			controls.maxDistance = 80;
			controls.minDistance = 7;			
			controls.enablePan = false;
			
			var textureLoader = new THREE.TextureLoader();

			// Earth
			var geometry = new THREE.SphereBufferGeometry(5, 32, 32)
			var material = new THREE.MeshPhongMaterial()
			material.bumpMap = textureLoader.load('./earth1.jpg')
			material.bumpScale = 0.05
			material.map = textureLoader.load('./earth1.jpg')
			material.specularMap = textureLoader.load('./earth1.jpg')
			material.specular = new THREE.Color(0xfdb813)
			material.shininess = 15
			earthMesh = new THREE.Mesh(geometry, material)
			earthMesh.position.x = 0;
			earthMesh.position.y = 50;
			scene.add(earthMesh)

			// Ambient Light
			var ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
			scene.add(ambientLight);

			// Point Light
			pointLight = new THREE.PointLight(0xffffff, 0.8);
			pointLight.position.set(0, 50, 0);
			pointLight.castShadow = true;
			pointLight.shadow.mapSize.width = 1024;
			pointLight.shadow.mapSize.height = 1024;
			scene.add(pointLight);


			window.addEventListener('resize', () => {
				renderer.setSize(window.innerWidth, window.innerHeight)
				camera.aspect = window.innerWidth / window.innerHeight
				camera.updateProjectionMatrix()
			})

			earthMesh.rotation.x = 0
			animate()
		}

		var theta = 0;
		function update() {		
			earthMesh.rotation.y = theta/1200
			theta += 0.2;
			pointLight.position.x = 40 * Math.sin(THREE.Math.degToRad(theta));
			// pointLight.position.y += 1 * Math.sin(THREE.Math.degToRad(theta));
			pointLight.position.z = 40 * Math.cos(THREE.Math.degToRad(theta));
		}

		function animate() {
			renderer.render(scene, camera);
			requestAnimationFrame(animate);
			update();
		}
		
		window.onload = function() {
			init();
		};
	</script>
</body>
</html>

light

// 이 빛은 마치 멀리 떨어져있는 것처럼 작동하며 그 광선은 모두 평행입니다. 이를위한 일반적인 사용 사례는 일광을 시뮬레이션하는 것입니다. 태양은 그 위치가 무한한 것으로 간주 될 수있을만큼 멀리 떨어져 있으며, 그로부터 오는 모든 광선은 평행합니다. 이 빛은 그림자를 드리울 수 있습니다
light = new THREE.DirectionalLight( 0xdddddd, 0.8 );
// 단일 지점에서 방출되는 조명입니다. 이를위한 일반적인 사용 사례는 노출 된 전구에서 방출 된 빛을 복제하는 것입니다. 이 빛은 그림자를 드리울 수 있습니다
light = new THREE.PointLight( 0xb4e7f2, 0.8 );
// 이 빛은 하나의 점에서 한 방향으로, 원뿔을 따라 더 큰 크기로 커지는 원뿔을 따라 방출됩니다. 이 빛은 그림자를 드리울 수 있습니다
light = new THREE.SpotLight( 0xb4e7f2, 0.8 );
// 이 라이트는 전체적으로 장면의 모든 오브젝트를 동일하게 비춥니다.이 라이트는 방향이 없으므로 그림자를 투사하는 데 사용할 수 없습니다.
light = new THREE.AmbientLight( 0x444444 );

Model Loader

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    html, body {margin: 0; height: 100%;}
    </style>
</head>
<body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
    <script src="https://threejs.org/examples/js/loaders/GLTFLoader.js"></script>
    <script>
        var camera, scene, renderer, controls;
        var geometry, material, mesh;
        var mixer, clips;

        var clock = new THREE.Clock();
        
        var modelUrl = 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf';

        init();
        animate();
        
        function init() {        
            camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 1000 );
            camera.position.y = 1.5;
            camera.position.z = 1.25;
        
            controls = new THREE.OrbitControls( camera );
            controls.update();
        
            scene = new THREE.Scene();
        
            geometry = new THREE.BoxGeometry( 1, 1, 1 );
            material = new THREE.MeshNormalMaterial();
        
            mesh = new THREE.Mesh( geometry, material );
            // scene.add( mesh );
        
            light = new THREE.HemisphereLight( 0xbbbbff, 0x444422 );
            light.position.set( 0, 1, 0 );
            scene.add( light );
        
            var light = new THREE.AmbientLight( 0x404040 ); // soft white light
            scene.add( light );
        
            var light = new THREE.PointLight( 0xffffff, 1, 100 );
            light.position.set( 2, 2, 2 );
            scene.add( light );
        
            renderer = new THREE.WebGLRenderer({
                antialias: true
            });
            
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize( window.innerWidth, window.innerHeight );
            document.body.appendChild( renderer.domElement );        
        
            // model
            var loader = new THREE.GLTFLoader();
            loader.load( modelUrl, 
                        function ( gltf ) {    
                            mixer = new THREE.AnimationMixer( gltf.scene );
                            clips = gltf.animations;                
                            scene.add( gltf.scene );            
                        }, 
                        undefined, 
                        function ( e ) {
                            console.error( e );
                        });
        }
        
        function animate() {        
            requestAnimationFrame( animate );        
            mesh.rotation.x += 0.01;
            mesh.rotation.y += 0.02;            
            var delta = clock.getDelta();
            if(mixer) {
                mixer.update( delta );
            }
            renderer.render( scene, camera );
        }
    </script>
</body>
</html>

Interaction

Raycaster

This class is designed to assist with raycasting.
Raycasting is used for mouse picking (working out what objects in the 3d space the mouse is over) amongst other things.

// normalized device coordinates
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

raycaster = new THREE.Raycaster();

raycaster.setFromCamera( mouse, camera );

var intersects = raycaster.intersectObjects( scene.children );

INTERSECTED = intersects[ 0 ].object;

example1 지구본 클릭시 회전

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        *{margin:0;padding:0;}
        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.setClearColor(0xEEEEEE);
            renderer.setSize( window.innerWidth, window.innerHeight );          
            document.body.appendChild( renderer.domElement );
            
            //Show Axis
            var axes = new THREE.AxisHelper(10);
            scene.add(axes);
            // 빛을 생성해서
            var light1 = new THREE.PointLight( 0xffffff, 1, 100 );//PointLight( color : Integer, intensity : Float, distance : Number, decay : Float )
            // 위치를 적당한 지점에 놓고
            light1.position.set( 5, 5, 5 );
            // 장면에 추가합니다.
            scene.add( light1 );
            
            var loader = new THREE.TextureLoader();

            var mesh;
            loader.load(
                'https://picsum.photos/id/85/300/300', 
                function ( texture ) {
                    mesh = new THREE.Mesh(
                        new THREE.SphereGeometry( 2, 32, 32 ), 
                        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 raycaster = new THREE.Raycaster();
            var mouse = new THREE.Vector2();
            // 에니메이션 효과를 자동으로 주기 위한 보조 기능입니다.
            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();
                mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
                mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
                
                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>

p5js

p5js 란

웹페이지에 자유롭게
그림을 그리고,
그림을 움직이고,
사용자와 상호작용하도록
돕는 javascript canvas 기반
무료 오픈소스 라이브러리입니다.

출처
Intro to WebGL
Watch Presentation Video
Slides on GitHub
Discover Three.js
생활코딩 p5.js