はじめに
この方法は、たし算やかけ算を使わずにカメラをある程度自由に動かしたいと試みた一つのやり方です。
blender上で線として確認できるので、リハーサルっぽくできます。
今回の道筋を使用して作成したサンプル
サンプル画面左上にボタンがありますので色々と試してみてください。
00.準備
blender v2.78
three.js r88
を使用しています。
blenderにthree.js(json)のエクスポートを使用しますので、あらかじめ導入しておいていただけるとスムーズです。
エクスポートアドオン導入の参考記事です。
Three.jsのJSONLoaderのメモ
細かく書いていく予定ですが、少しblenderが使用できると良いかもしれません。
01.3Dビューと全選択
blenderを起動すると、このような画面が表示されます。
水色で囲われた部分が3Dビューです。
ショートカットはこの中にマウスカーソルを置いて実行してください。
デフォルトのキューブが選択状態なので、「Aキー」を押して選択を解除します。
続いて、もう一度「Aキー」を押して全てを選択します。
「Aキー」は選択解除と全選択をしています。
次は、「Xキー」を押してください。削除するかを聞かれるので削除を押してください。
3Dビュー上に何もなくなりました。
02.3Dビューのカメラ操作
3Dビューの視点操作
こちらに、詳しく書いていただいています。
何度か視点を動かしていただき、慣れてもらえるといいと思います。
デフォルトのキューブを残した状態で試した方がわかりやすいかもしれません。
03.オブジェクトモードと編集モード
水色で囲われた部分がモード変更のボタンです。
何もメッシュ(キューブやパス)がないと、編集モードに変更できないので、パスを追加します。
パスを追加できたら、編集モードに変更します。
そして、テンキーの7(真上からの視点)を押して、ズームアップするとX軸の上に直線の矢印が表示されているはずです。
これをこの向きのまま使ってもよいのですが、使いやすい向きに回転します。
パスが選択された状態でRキー、Zキーの順で入力して90と押すとパスが奥方向に向きます。
Rキーはオブジェクトの回転。ZキーはblenderのZ軸(上下の軸)。90は+90度と動きます。
04.座標軸の違い
ここで、blenderとthree.jsの座標軸の違いの画像。赤がX軸、緑がY軸、青がZ軸です。
手前右上からの視点です。
違いはあるのですが、エクスポート時にデフォルト設定でthree.jsの軸に合わせてくれるので、three.jsの軸で考えていきます。
05.メッシュに変換
話は戻り、先ほど作成したパスは(three.jsの軸で考えるので)手前から奥に進むことになります。
追加したパスには、現時点では動かせるポイントが5つあります。
この直線パスをジグザグにしてみます。2つめと4つめのポイントを動かします。
ポイントを動かすのは、動かしたいポイントのみを選択(右クリック)し、Gキーです。
移動するのをX軸に固定したい場合はGキーの後にXキーを押します。
パスはエクスポートできないので、これをメッシュに変換します。
オブジェクトモードにした後に、Alt+C 変換 カーブからメッシュを押します。
06.エクスポート
これでエクスポートといきたいところなのですが、僕が試した限りでは一つ以上面がないとエクスポートできないようです。
なので、面を一つ貼ります。編集モードにして、3つ頂点を選択しFキーです。
ちなみに先端の頂点から、Eキーで頂点を増やしつつ線をのばしていけます。
移動(Gキー) 押し出し(Eキー) 拡大縮小(Sキー)をする際の軸の固定方法は、目的の動作キーを押した後にX, Y, Zキーのどれかを押すと固定できます。
面が貼れたらオブジェクトモードに戻し、エクスポートに入っていきます。
メッシュを選択した状態で、ファイル エクスポート three.js(json)です。
エクスポートの設定は画像の通りです。
GEOMETRY: 頂点のみチェック
タイプ: ジオメトリ
Index Buffer: なし
それ以降のチェックの入っているところを全て外してください。
すると、次のようなJSONが出力されます。
{
"vertices":[0,8.74228e-08,2,......],
"metadata":{
"type":"Geometry",
"generator":"io_three",
"version":3,
"vertices":48
}
}
このverticesを使用して道筋を作ります。
今回はverticesをpointsにコピペしていますが、もちろん、JSONから読み込んでも大丈夫です。
ソース全体を貼ります。コメントもついていますので、ぜひ試してみてください。
07.コード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
<title>root</title>
<style>
html,body {height: 100%; overflow: hidden;}
body {margin: 0; padding: 0; background: #fff;}
#button {position: absolute; top: 10px; left: 10px;}
</style>
</head>
<body>
<div id="button">
<input type="button" value="全体図" onclick="overall();">
<input type="button" value="レール" onclick="rail();">
<input type="button" value="表示Off" onclick="invisible();">
<input type="button" value="表示On" onclick="visible();">
</div>
<script type="text/javascript" src="three.min.js"></script>
<script type="text/javascript" src="OrbitControls.js"></script>
<script type="text/javascript" src="TweenMax.js"></script>
<script type="text/javascript">
var percentage = 0;
var scene,camera,cameraK,controls,renderer,points,path,geometry,material,tube;
function initScene() {
// シーン
scene = new THREE.Scene();
// パスに沿って移動するカメラ
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.001, 1000 );
// パスを俯瞰するカメラ
cameraK = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.001, 1000 );
cameraK.position.set(-2.5, 2.5, 2.5);
controls = new THREE.OrbitControls(cameraK);
// camera 切り替えのフラグ
cameraActive = false;
// レンダラー
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setPixelRatio(window.devicePixelRatio || 1);
renderer.setClearColor(0xffffff);
document.body.appendChild( renderer.domElement );
// ライト
light = new THREE.AmbientLight(0xffffff);
scene.add(light);
// blender で作成したパス
points = [0,8.74228e-08,2,-0.239253,8.37027e-08,1.91489,-0.4473,7.99825e-08,1.82979,-0.625526,7.62624e-08,1.74468,-0.77532,7.25423e-08,1.65957,-0.898067,6.88222e-08,1.57447,-0.995155,6.51021e-08,1.48936,-1.06797,6.1382e-08,1.40426,-1.1179,5.76618e-08,1.31915,-1.14634,5.39417e-08,1.23404,-1.15466,5.02216e-08,1.14894,-1.14426,4.65015e-08,1.06383,-1.11652,4.27814e-08,0.978724,-1.07283,3.90612e-08,0.893617,-1.01457,3.53411e-08,0.808511,-0.943144,3.1621e-08,0.723404,-0.859925,2.79009e-08,0.638298,-0.766304,2.41808e-08,0.553192,-0.663668,2.04607e-08,0.468085,-0.553403,1.67405e-08,0.382979,-0.436898,1.30204e-08,0.297873,-0.315537,9.3003e-09,0.212766,-0.190709,5.58018e-09,0.12766,-0.0638011,1.86007e-09,0.0425533,0.0638008,-1.86006e-09,-0.0425532,0.190709,-5.58018e-09,-0.12766,0.315537,-9.30029e-09,-0.212766,0.436897,-1.30204e-08,-0.297872,0.553403,-1.67405e-08,-0.382979,0.663668,-2.04606e-08,-0.468085,0.766304,-2.41808e-08,-0.553191,0.859925,-2.79009e-08,-0.638298,0.943144,-3.1621e-08,-0.723404,1.01457,-3.53411e-08,-0.808511,1.07283,-3.90612e-08,-0.893617,1.11652,-4.27814e-08,-0.978723,1.14426,-4.65015e-08,-1.06383,1.15466,-5.02216e-08,-1.14894,1.14634,-5.39417e-08,-1.23404,1.1179,-5.76618e-08,-1.31915,1.06797,-6.13819e-08,-1.40426,0.995155,-6.51021e-08,-1.48936,0.898067,-6.88222e-08,-1.57447,0.77532,-7.25423e-08,-1.65957,0.625527,-7.62624e-08,-1.74468,0.4473,-7.99825e-08,-1.82979,0.239254,-8.37027e-08,-1.91489,7.15256e-07,-8.74228e-08,-2];
// パスを x, y, z に
for (var i = 0; i < points.length; i++) {
var x = points[i*3+0];
var y = points[i*3+1];
var z = points[i*3+2];
points[i] = new THREE.Vector3(x, y, z);
}
// points を3で割って path に入ったときに合うように
points.length = points.length / 3;
// CatmullRomCurve3 に
path = new THREE.CatmullRomCurve3(points);
// パスに沿っているチューブのメッシュ
geometryT = new THREE.TubeGeometry( path, 360, 0.05, 16, false );
materialT = new THREE.MeshBasicMaterial( { color: 0xffd700, side : THREE.BackSide, wireframe: true } );
tube = new THREE.Mesh(geometryT, materialT);
scene.add( tube );
// 動くカメラの位置についてくるキューブ
camera_geo = new THREE.BoxGeometry(0.05, 0.05, 0.05);
camera_mat = new THREE.MeshPhongMaterial( { color: 0xff0000 } );
camera_pos = new THREE.Mesh(camera_geo, camera_mat);
scene.add( camera_pos );
// トーラス
jLoader = new THREE.JSONLoader();
jLoader.load('wa.json', function(geometryK1, materialK1) {
materialK1 = new THREE.MeshNormalMaterial();
meshK1 = new THREE.Mesh(geometryK1, materialK1);
scene.add(meshK1);
});
render();
}
function overall() {
cameraActive = true;
}
function rail() {
cameraActive = false;
}
function invisible() {
tube.visible = false;
}
function visible() {
tube.visible = true;
}
function render() {
percentage += 0.0005; // percentage を増やしていき動かしている
var p1 = path.getPointAt(percentage%1);
var p2 = path.getPointAt((percentage + 0.02)%1);
camera.position.set(p1.x, p1.y, p1.z);
camera_pos.position.set(p1.x, p1.y, p1.z);
camera.lookAt(p2); // camera の少し前を見る。
if (percentage > 1) {
percentage = 0;
}
if(!cameraActive){
renderer.render(scene, camera);
}else{
renderer.render(scene, cameraK);
}
controls.update();
requestAnimationFrame(render);
}
window.onload = initScene;
</script>
</body>
</html>