Three.jsによるWebGLで物体を回転させる
簡単に WebGL を実装できる Three.js について、これまでいくつか紹介させていただきましたが(1, 2, 3)、
これまでの方法の中では、表示した物体をいろんな方向から見る場合に、カメラの方を回転させていたのですが、
今回は、物体の方を回転させる方法を調べてやってみました。
カメラを回転させる場合、光源も一緒に回転しますが、
物体の方を回転させる場合、光源は固定されたままなのが違う点です。
特に今回は、複数の物体をまとめて回転させたかったので、その方法についてご紹介いたします。
↓作ってみた物
DEMO
ポイント
- meshPhongMaterial() を使うことで、陰を詳細に描画
- Shape() とExtrudeGeometry で path から板を生成
- Group() で複数の物体をグループ化して、まとめて扱えるようにする
- マウス(タッチ)の移動距離を物体の回転にあてる
説明
1. meshPhongMaterial()を使うことで、陰を詳細に描画
これまでは、material の生成に、MeshBasicMaterial を使ってきましたが、今回は陰を描写するため、MeshPhongMaterial を使いました。
1 2 3 | material = new THREE.MeshPhongMaterial( { map: texture, bumpMap:texture }); |
2. Shape()とExtrudeGeometryでpathから板を生成
Three.js では Canvas の beginPath() のように、movoTo、lineTo、bezierCurveToを使ってパスから図形を生成することができます。
さらに、ExtrudeGeometry により、押出による板状の物体を生成することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //パスから星を生成 shape = new THREE.Shape(); shape.moveTo(36.411, 1.597); shape.lineTo(28.698, 29.499); shape.lineTo(0.989, 40.889); shape.lineTo(26, 61.724); shape.lineTo(25.729, 93.401); shape.lineTo(47.527, 68.715); shape.lineTo(84.021, 86.127); shape.lineTo(72.282, 53.786); shape.lineTo(90.936, 21.44); shape.lineTo(58.487, 30.354); shapeGeometry = new THREE.ExtrudeGeometry(shape, { steps: 2,//厚みの分割数 amount: 1, //厚さ bevelEnabled: true, //角を丸める bevelThickness: 1, //角の厚み bevelSize: 1, //角の大きさ bevelSegments: 1 角の分割数 }); |
3. Group() で複数の物体をグループ化して、まとめて扱えるようにする
複数の物体は、グループ化することで、まとめて操作することができます。
1 2 3 4 5 6 7 | sphere = new THREE.Mesh( new THREE.IcosahedronGeometry( 20, 3 ), material ); shapeMesh = new THREE.Mesh(shapeGeometry, material2); //球体と星をグループ化 shapes = new THREE.Group(); shapes.add(sphere); shapes.add(shapeMesh); scene.add(shapes); |
4. マウス(タッチ)の移動距離を物体の回転にあてる
マウスやタッチのイベントを基に、回転の量を設定して、
物体の回転にあてます。
イベントの操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | //イベントの振り分け var EVENT = {}; if ('ontouchstart' in window) { EVENT.TOUCH_START = 'touchstart'; EVENT.TOUCH_MOVE = 'touchmove'; EVENT.TOUCH_END = 'touchend'; } else { EVENT.TOUCH_START = 'mousedown'; EVENT.TOUCH_MOVE = 'mousemove'; EVENT.TOUCH_END = 'mouseup'; } … document.addEventListener( EVENT.TOUCH_START, onDocumentMouseDown, false ); document.addEventListener( 'mousewheel', onDocumentMouseWheel, false ); document.addEventListener( 'MozMousePixelScroll', onDocumentMouseWheel, false); window.addEventListener( 'resize', onWindowResized, false ); onWindowResized( null ); } function onDocumentMouseDown( event ) { event.preventDefault(); if(event.clientX) { onMouseDownMouseX = event.clientX; onMouseDownMouseY = event.clientY; } else if(event.touches) { onMouseDownMouseX = event.touches[0].clientX onMouseDownMouseY = event.touches[0].clientY; } else { onMouseDownMouseX = event.changedTouches[0].clientX onMouseDownMouseY = event.changedTouches[0].clientY } onMouseDownLon = lon; onMouseDownLat = lat; document.addEventListener( EVENT.TOUCH_MOVE, onDocumentMouseMove, false ); document.addEventListener( EVENT.TOUCH_END, onDocumentMouseUp, false ); } function onDocumentMouseMove( event ) { event.preventDefault(); if(event.clientX) { var touchClientX = event.clientX; var touchClientY = event.clientY; } else if(event.touches) { var touchClientX = event.touches[0].clientX var touchClientY = event.touches[0].clientY; } else { var touchClientX = event.changedTouches[0].clientX var touchClientY = event.changedTouches[0].clientY } lon = ( touchClientX - onMouseDownMouseX ) * 0.01 + onMouseDownLon; lat = ( touchClientY - onMouseDownMouseY ) * 0.01 + onMouseDownLat; } … function animate() { requestAnimationFrame( animate ); render(); } function render() { //カメラの位置を固定 camera.position.x = 0; camera.position.y = 25; camera.position.z = 50; camera.lookAt( scene.position ); //物体を回転させる shapes.rotation.y = lon; shapes.rotation.x = lat; renderer.render( scene, camera ); } |
全コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | "use strict"; //イベントの振り分け var EVENT = {}; if ('ontouchstart' in window) { EVENT.TOUCH_START = 'touchstart'; EVENT.TOUCH_MOVE = 'touchmove'; EVENT.TOUCH_END = 'touchend'; } else { EVENT.TOUCH_START = 'mousedown'; EVENT.TOUCH_MOVE = 'mousemove'; EVENT.TOUCH_END = 'mouseup'; } (function () { var camera, scene, renderer; var sphere, material; var path, shape, shapeGeometry, shapeMesh, material2, shapes; var count = 0; var light, light2; var fov = 60, onMouseDownMouseX = 0, onMouseDownMouseY = 0, lon = 0, onMouseDownLon = 0, lat = 0, onMouseDownLat = 0; var textureLoader = new THREE.TextureLoader(); var wrapperElm = document.getElementById("canvas-frame"); textureLoader.load( 'img/ptn.png', function ( texture ) { texture.mapping = THREE.UVMapping; init( texture ); animate(); } ); function init( texture ) { //テクスチャの繰り返し設定 texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(6,4) //カメラ・シーン・レンダラーの設定 camera = new THREE.PerspectiveCamera( fov, wrapperElm.clientWidth / wrapperElm.clientHeight, 1, 1000 ); scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( wrapperElm.clientWidth, wrapperElm.clientHeight ); renderer.setClearColor(0x000000, 0); renderer.shadowMap.enabled = true; wrapperElm.appendChild( renderer.domElement ); //球体の生成 material = new THREE.MeshPhongMaterial( { map: texture, bumpMap:texture } ); sphere = new THREE.Mesh( new THREE.IcosahedronGeometry( 20, 3 ), material ); sphere.castShadow = true; sphere.receiveShadow = true; //パスから星を生成 shape = new THREE.Shape(); shape.moveTo(36.411, 1.597); shape.lineTo(28.698, 29.499); shape.lineTo(0.989, 40.889); shape.lineTo(26, 61.724); shape.lineTo(25.729, 93.401); shape.lineTo(47.527, 68.715); shape.lineTo(84.021, 86.127); shape.lineTo(72.282, 53.786); shape.lineTo(90.936, 21.44); shape.lineTo(58.487, 30.354); shapeGeometry = new THREE.ExtrudeGeometry(shape, { steps: 2, amount: 1, bevelEnabled: true, bevelThickness: 1, bevelSize: 1, bevelSegments: 1 }); material2 = new THREE.MeshPhongMaterial({color: 0xF8C058}); shapeMesh = new THREE.Mesh(shapeGeometry, material2); shapeMesh.position.set(0, 15, 12); shapeMesh.scale.set(0.1, 0.1, 0.1); shapeMesh.rotation.set(0, 0.5, 0); shapeMesh.castShadow = true; shapeMesh.receiveShadow = true; //球体と星をグループ化 shapes = new THREE.Group(); shapes.add(sphere); shapes.add(shapeMesh); scene.add(shapes); //ライトの設定 light = new THREE.DirectionalLight(0xcccccc, 1); light.position.set(0.577, 0.577, 0.577); light.castShadow = true; scene.add(light); light2 = new THREE.AmbientLight(0x333333); scene.add(light2); //操作の設定 document.addEventListener( EVENT.TOUCH_START, onDocumentMouseDown, false ); document.addEventListener( 'mousewheel', onDocumentMouseWheel, false ); document.addEventListener( 'MozMousePixelScroll', onDocumentMouseWheel, false); window.addEventListener( 'resize', onWindowResized, false ); onWindowResized( null ); } function onWindowResized( event ) { renderer.setSize( wrapperElm.clientWidth, wrapperElm.clientHeight ); camera.projectionMatrix.makePerspective( fov, wrapperElm.clientWidth / wrapperElm.clientHeight, 1, 1100 ); } function onDocumentMouseDown( event ) { event.preventDefault(); if(event.clientX) { onMouseDownMouseX = event.clientX; onMouseDownMouseY = event.clientY; } else if(event.touches) { onMouseDownMouseX = event.touches[0].clientX onMouseDownMouseY = event.touches[0].clientY; } else { onMouseDownMouseX = event.changedTouches[0].clientX onMouseDownMouseY = event.changedTouches[0].clientY } onMouseDownLon = lon; onMouseDownLat = lat; document.addEventListener( EVENT.TOUCH_MOVE, onDocumentMouseMove, false ); document.addEventListener( EVENT.TOUCH_END, onDocumentMouseUp, false ); } function onDocumentMouseMove( event ) { event.preventDefault(); if(event.clientX) { var touchClientX = event.clientX; var touchClientY = event.clientY; } else if(event.touches) { var touchClientX = event.touches[0].clientX var touchClientY = event.touches[0].clientY; } else { var touchClientX = event.changedTouches[0].clientX var touchClientY = event.changedTouches[0].clientY } lon = ( touchClientX - onMouseDownMouseX ) * 0.01 + onMouseDownLon; lat = ( touchClientY - onMouseDownMouseY ) * 0.01 + onMouseDownLat; } function onDocumentMouseUp( event ) { document.removeEventListener( EVENT.TOUCH_MOVE, onDocumentMouseMove, false ); document.removeEventListener( EVENT.TOUCH_END, onDocumentMouseUp, false ); } function onDocumentMouseWheel( event ) { // WebKit if ( event.wheelDeltaY ) { fov -= event.wheelDeltaY * 0.05; // Opera / Explorer 9 } else if ( event.wheelDelta ) { fov -= event.wheelDelta * 0.05; // Firefox } else if ( event.detail ) { fov += event.detail * 1.0; } camera.projectionMatrix.makePerspective( fov, wrapperElm.clientWidth / wrapperElm.clientHeight, 1, 1100 ); } function animate() { requestAnimationFrame( animate ); render(); } function render() { //カメラの位置を固定 camera.position.x = 0; camera.position.y = 25; camera.position.z = 50; camera.lookAt( scene.position ); //物体を回転させる shapes.rotation.y = lon; shapes.rotation.x = lat; renderer.render( scene, camera ); } })(); |
参考サイト
Three.jsの基本⑤ – Three.jsを使って、作ってみた
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE