2016/07/06
Three.jsでBoneをあつかう
3Dでアニメーションを実装する場合に、
オブジェクトに骨組み(Bone)を予め設定しておいて、
その骨組みを動かすことで、オブジェクトをアニメーションさせる、
ボーンアニメーションという仕組みがあります。
Three.jsには、このボーンアニメーションの仕組も準備されています。
↓作ってみたもの(Three.js公式のDEMOを基にしています)
DEMO
方法
ボーンの設定は、おおよそ以下の様な流れで行います。
- オブジェクトを設置する
- ボーン(関節)を設置する
- ボーンからスケルトン(ボーンを合わせたもの)を作成する
- オブジェクトとスケルトンをbindする
記事の最後にコードの全文を載せています。こちらは、Three.jsの公式DEMOを基に、一部書き換えたものです。
ポイントは、以下の箇所です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function createBones ( sizing ) { bones = []; var prevBone = new THREE.Bone(); bones.push( prevBone ); prevBone.position.y = - sizing.halfHeight; for ( var i = 0; i < sizing.segmentCount; i ++ ) { var bone = new THREE.Bone(); bone.position.y = sizing.segmentHeight; bones.push( bone ); prevBone.add( bone ); prevBone = bone; } return bones; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //オブジェクトをシーンに追加 var mesh = new THREE.SkinnedMesh( geometry, material ); //スケルトンをシーンに追加 var skeleton = new THREE.Skeleton( bones ); //ボーンの1つ目をシーンに追加 mesh.add( bones[ 0 ] ); //オブジェクトにスケルトンをbind mesh.bind( skeleton ); //ボーンを可視化 skeletonHelper = new THREE.SkeletonHelper( mesh ); skeletonHelper.material.linewidth = 2; scene.add( skeletonHelper ); |
THREE.Bone()で骨格の一部を作成し、
それを基にスケルトン(ボーン全体)を作成します。
更にそれを.bind()によって、オブジェクトに設置します。
あとは、
1 | mesh.skeleton.bones[n].rotation.z = 1 |
と言った形式で、ボーンを変形・移動させることで、オブジェクトを変形・移動させることができるようになります。
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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | "use strict"; (function () { var gui, scene, camera, renderer, orbit, ambientLight, lights, mesh, bones, skeletonHelper; var state = { animateBones : false }; function initScene () { var wrapperElm = document.getElementById("canvas-frame"); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( 80, wrapperElm.clientWidth / wrapperElm.clientHeight, 0.1, 200 ); camera.position.z = 50; camera.position.y = 10; renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( wrapperElm.clientWidth, wrapperElm.clientHeight ); wrapperElm.appendChild( renderer.domElement ); //環境光の設置 ambientLight = new THREE.AmbientLight( 0x000000 ); scene.add( ambientLight ); //点光源の設置 lights = []; lights[ 0 ] = new THREE.PointLight( 0xffffff, 1, 0 ); lights[ 1 ] = new THREE.PointLight( 0xffffff, 1, 0 ); lights[ 2 ] = new THREE.PointLight( 0xffffff, 1, 0 ); lights[ 0 ].position.set( 0, 200, 0 ); lights[ 1 ].position.set( 100, 200, 100 ); lights[ 2 ].position.set( - 100, - 200, - 100 ); scene.add( lights[ 0 ] ); scene.add( lights[ 1 ] ); scene.add( lights[ 2 ] ); //windowのリサイズに対応 window.addEventListener( 'resize', function () { camera.aspect = wrapperElm.clientWidth / wrapperElm.clientHeight; camera.updateProjectionMatrix(); renderer.setSize( wrapperElm.clientWidth, wrapperElm.clientHeight ); }, false ); //要素をクリックしたときに、アニメーションの設定をToggle wrapperElm.addEventListener( 'click', function() { if(state.animateBones) state.animateBones = false; else { state.animateBones = true; } } ); initBones(); } function createGeometry ( sizing ) { var geometry = new THREE.CylinderGeometry( 5, // radiusTop 5, // radiusBottom sizing.height, // height 8, // radiusSegments sizing.segmentCount * 3, // heightSegments true // openEnded ); for ( var i = 0; i < geometry.vertices.length; i ++ ) { var vertex = geometry.vertices[ i ]; var y = ( vertex.y + sizing.halfHeight ); var skinIndex = Math.floor( y / sizing.segmentHeight ); var skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight; geometry.skinIndices.push( new THREE.Vector4( skinIndex, skinIndex + 1, 0, 0 ) ); geometry.skinWeights.push( new THREE.Vector4( 1 - skinWeight, skinWeight, 0, 0 ) ); } return geometry; }; function createBones ( sizing ) { bones = []; var prevBone = new THREE.Bone(); bones.push( prevBone ); prevBone.position.y = - sizing.halfHeight; for ( var i = 0; i < sizing.segmentCount; i ++ ) { var bone = new THREE.Bone(); bone.position.y = sizing.segmentHeight; bones.push( bone ); prevBone.add( bone ); prevBone = bone; } return bones; }; function createMesh ( geometry, bones ) { var material = new THREE.MeshPhongMaterial( { skinning : true, color: 0x156289, emissive: 0x072534, side: THREE.DoubleSide, shading: THREE.FlatShading } ); var mesh = new THREE.SkinnedMesh( geometry, material ); var skeleton = new THREE.Skeleton( bones ); mesh.add( bones[ 0 ] ); mesh.bind( skeleton ); //ボーンを可視化 skeletonHelper = new THREE.SkeletonHelper( mesh ); skeletonHelper.material.linewidth = 2; scene.add( skeletonHelper ); return mesh; }; function initBones () { var segmentHeight = 8; var segmentCount = 4; var height = segmentHeight * segmentCount; var halfHeight = height * 0.5; var sizing = { segmentHeight : segmentHeight, segmentCount : segmentCount, height : height, halfHeight : halfHeight }; var geometry = createGeometry( sizing ); var bones = createBones( sizing ); mesh = createMesh( geometry, bones ); mesh.scale.multiplyScalar( 1.25 ); scene.add( mesh ); }; function render () { requestAnimationFrame( render ); var time = Date.now() * 0.001; var bone = mesh; //Wiggle the bones if ( state.animateBones ) { for ( var i = 0; i < mesh.skeleton.bones.length; i ++ ) { mesh.skeleton.bones[ i ].rotation.z = Math.sin( time ) * 2 / mesh.skeleton.bones.length; } } skeletonHelper.update(); renderer.render( scene, camera ); }; initScene(); mesh.skeleton.bones[2].rotation.z = 1; //boneの3つ目を回転させておく render(); |
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE