Loading...

トカチニッチ

気づきにくい十勝を発信する。トカチニッチ

Three.js勉強中 パノラマビューアを作って遊ぶ

荒草

荒草

1108 VIEW
投稿記事
注目タグ

ずっとやりたかったThree.jsを試す。すごく簡単で楽しい。
VR推しな会社に勤めていることもあり「360度パノラマビューア」の作成を目標に据える。
世にあるツールを使えばより簡単であるが基礎を学んで損はないはず。

今回作ったもの

ここまで勉強時間10時間くらいと凄く簡単だった。
ごめんなさい、実際はその3倍くらい。

Three.jsについての解説はたくさんあるので省略…したいのだが一言だけ。
「three.js-master.zip」のダウンロードを試みるも非常に重く何度も失敗。出鼻をくじかれGitHubから必要なものだけ揃えた。

目次
  1. 可変サイズcanvasでhtmlからサイズ指定
  2. ウインドウリサイズ処理の小技
  3. OrbitControls/座標ゼロの罠
  4. DeviceOrientationControlsとドラッグ操作
  5. (挫折)OrbitControlsとDeviceOrientationControlsの同時利用
  6. パノラマビューア
  7. ラスボスIE11のテクスチャエラー

Test001 可変サイズcanvasでhtmlからサイズ指定

アラフォーのjQueryゆとり世代なので、イベント系はそちらで。
できればWEBページに埋め込む汎用ツールが目標なので、必要なパラメータはhtml側から指定できるのが望ましい。
特にcanvasサイズ。使うたびにjsファイルを編集するのは面倒。そんなのはページをレイアウトする人(うちの会社では社長)にcssでやらせとけばいいのだ。

というわけで↓こんな感じのhtmファイル(test001.htm)にしてみる。

test001.htm

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Three.js Test</title>

<!-- css -->
<style>
body {
    margin:0px;
    padding:0px;
}
#canvas_box {
    margin:0px;
    padding:0px;
    width:100%;
    height:100%;
    overflow:hidden;
    background-color:#ffc;
}
#canvas_main {
    margin:0px;
    padding:0px;
    width:100%;
    height:100%;
}
</style>

<!-- js -->
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/three/three.min.js"></script>

<script src="js/test001.js"></script>
<script>
jQuery(function(){
    $(window).on("load",function(){
        $("#canvas_main").panoramaViewer({
            camera : {
                fov : 60,
            }
        });
    });
});
</script>

</head>
<body>
<div id="canvas_box">
  <canvas id="canvas_main"></canvas>
</div>
</body>
</html>

canvasは100%*100%に固定して、背景色とサイズ指定は親要素「#canvas_box」へのcssのみ。後述するウインドウのサイズ変更に対応するためだ。
(デモでは#canvas_boxをウインドウサイズに合わせた)

<div id="canvas_box">
  <canvas id="canvas_main"></canvas>
</div>

そしてcanvas要素「canvas_main」に対してjsを呼ぶ。

$("#canvas_main").panoramaViewer({
    camera : {
        fov : 60,
    }
});

カメラの設定をhtmlソースからパラメータで渡せるようにした。
js側ではパラメータのデフォルト値を用意しておく。

test001.js

;(function($){

    //panoramaViewer
    jQuery.fn.panoramaViewer=function(prmUser){

        //////////////////////////
        // Var
        //////////////////////////

        //パラメータデフォルト値
        const prmDef={
            camera : {
                fov  : 45,
                near : 1,
                far  : 2000
            }
        };
        const prm=$.extend(prmDef,prmUser);

        //canvas
        const myCanvas=$(this);
        var cv=new Canvas();
        function Canvas(){
            this._id  =myCanvas.attr("id");
            this._box =myCanvas.closest("#canvas_box");
            this._w   =this._box.width();
            this._h   =this._box.height();
        }
        myCanvas.attr({
            'width'  : cv._w,
            'height' : cv._h
        });

        //three.js
        var renderer;
        var scene;
        var group;
        var camera;


        //////////////////////////
        // Canvas
        //////////////////////////

        setCanvas();
        function setCanvas(){

            renderer=new THREE.WebGLRenderer({
                canvas    : document.querySelector('#'+cv._id),
                antialias : true,
                alpha     : true
            });

            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(cv._w,cv._h);

            scene=new THREE.Scene();
            group=new THREE.Group();

            camera=new THREE.PerspectiveCamera(prm.camera.fov,cv._w/cv._h,prm.camera.near,prm.camera.far);
            camera.position.set(0,100,100);

            //makeGeometry
            group.add(makeGeometry());

            scene.add(group);
            runAnimate();

            function runAnimate(){
                renderer.render(scene,camera);
                requestAnimationFrame(runAnimate);
            }
        }

    };

    //テスト3Dを配置
    function makeGeometry(){
        var group=new THREE.Group();

        group.add(new THREE.GridHelper(1000,100));

        var testGeometry1=new THREE.BoxGeometry(150,150,120);
        var testMaterial1=new THREE.MeshNormalMaterial();
        var testMesh1=new THREE.Mesh(testGeometry1,testMaterial1);
        testMesh1.position.set(100,300,-500);
        group.add(testMesh1);

        var testGeometry2=new THREE.SphereGeometry(70,6,4);
        var testMaterial2=new THREE.MeshNormalMaterial();
        var testMesh2=new THREE.Mesh(testGeometry2,testMaterial2);
        testMesh2.position.set(-120,100,-400);
        group.add(testMesh2);

        return group;
    }

})(jQuery);

ほぼ基本形と変わりないが、以下の部分でcanvas要素のID、親要素、親要素の幅/高さを保存して利用している。

const myCanvas=$(this);
var cv=new Canvas();
function Canvas(){
    this._id  =myCanvas.attr("id");
    this._box =myCanvas.closest("#canvas_box");
    this._w   =this._box.width();
    this._h   =this._box.height();
}

この形をベースにパノラマビューアを作る。まずウインドウのリサイズ時にcanvasのサイズも変更できるようにする。

Test002 ウインドウリサイズ処理の小技

jsの中に以下のコードを追加。
「小技」と記したのは、ウインドウリサイズ中に繰り返し処理しないようタイマーを使用しているから、ただそれだけ。(単に昔使ったヤツ)

var timerResize=false;
$(window).on("resize",function(){
    if(timerResize!==false){
        clearTimeout(timerResize);
    }
    timerResize=setTimeout(function(){
        resizeCanvas();
    },500);
});

function resizeCanvas(){
    cv._w=cv._box.width();
    cv._h=cv._box.height();
    myCanvas.attr({
        'width'  : cv._w,
        'height' : cv._h
    });

    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(cv._w,cv._h);
    camera.aspect=cv._w/cv._h;
    camera.updateProjectionMatrix();
}

デモページを開いてウインドウサイズを動かしてみる。リサイズが止んで0.5秒後にcanvasサイズが変更されるのですこぶる処理が軽い(はず)。

リサイズしてもcanvasサイズは変わらないので、resizeCanvas()では親要素である「#canvas_box」のサイズをもう一度取得して反映している。

Test003 OrbitControls/座標ゼロの罠

「three js カメラ ドラッグ」で検索すると「OrbitControls」ばかり。例に漏れず「OrbitControls」を使う。
そしてパノラマビューアにしたいのでカメラの座標を0,0,0に変更。これが大失敗。

OrbitControlsだと座標0,0,0のカメラは動かなくなるのだ。orbitの意味がわかっていればこんなかっこ悪いミスしない。当たり前だ、座標ゼロを見るように言われたカメラが座標ゼロに置かれたら動かないのだ。

三時間くらいハマった…情けない。

困ったときは一歩引いてみることが肝心だと学ぶ。

camera.translateZ(0.01);
//↑とか→camera.position.set(0,0,0.01);

ほかOrbitControlsの設定をhtmlから操作できるようにパラメータを増やす。
わざわざ項目立てて書くことでもないかもしれない…ただ私のように困った人がいたら(いたら!)救いたいと思い書いた次第。

Test004 DeviceOrientationControlsとドラッグ操作

せっかくなのでスマホの場合はジャイロセンサーでの操作を目指して「DeviceOrientationControls」を使う。
ジャイロセンサーがあれば「DeviceOrientationControls」、なければ「OrbitControls」と切り分けるだけなら簡単であるが、スマホでも方向をドラッグで操作したい。
じゃあスマホなら基本「DeviceOrientationControls」でドラッグ操作があった場合だけ「OrbitControls」のカメラに切り替えれば良いじゃん?と試行錯誤して挫折したので諦めて「3D要素」を回転させることにする…。

  • OrbitControlsのときはカメラが回転
  • DeviceOrientationControlsのときは周りが回転

なんとも気持ちが悪い…本当はカメラ回転のみで統一したかった。
諦めた経緯は次項で。

Test005 (挫折)OrbitControlsとDeviceOrientationControlsの同時利用

なんとかカメラ側の回転だけで統一できないかと試行錯誤するも早々に諦める。
PCなどの場合は「OrbitControls」のみで変わらず。

ジャイロセンサーを持つスマートデバイスでアクセスしたとき、通常は「DeviceOrientationControls」のカメラ。
ドラッグ操作があった場合だけ「OrbitControls」のカメラに切り替えるという荒業を試す。

myCanvas.on(_move,function(){
    if(istap){
        camera_do.translateZ(0.01);
        camera_ob.position.x=camera_do.position.x;
        camera_ob.position.y=camera_do.position.y;
        camera_ob.position.z=camera_do.position.z;
        isdo=false;
    }
    istap=false;
});

↑はドラッグ開始時の処理。
ドラッグ操作があった瞬間に、DeviceOrientationControlsカメラの位置を座標ゼロから一歩後ろに下げてそのポジションをOrbitControlsカメラに渡しrunAnimateの中でカメラを切り替える。
普通角度を合わせたいところだが座標0,0,0を見るOrbitControlsカメラはポジションの方が重要の様子。ここまでは成功。

ここから難しい…
そしてドラッグ操作が終わった際、OrbitControlsカメラからDeviceOrientationControlsカメラに戻したいのだが今度は角度をコピーしたいのに、デバイスのジャイロセンサーが強すぎて角度設定が効かないのだ。画面のドラッグ操作を離した瞬間にジャイロセンサーの角度に戻ってしまう。

↓どうしてもうまくいかない、ドラッグ終了時の処理。

myCanvas.on(_end,function(e){
    camera_do.rotation.x=camera_ob.rotation.x;
    camera_do.rotation.y=camera_ob.rotation.y;
    camera_do.rotation.z=camera_ob.rotation.z;
    camera_do.position.set(0,0,0);
    isdo=true;
    istap=false;
});

もしくはOrbitControlsカメラは角度が取得できないのだろうか…と試す前に諦め、スマートデバイスの場合は周囲のオブジェクトを動かす方法(前項)にした。なんか良い方法あったら教えてください。

Test006 パノラマビューア

ここから「Detector.js」も入れてみました。

いよいよ本題。
htmlからパラメータでパノラマ画像パスと初期角度(Y軸)を渡せるようにする。

$("#canvas_main").panoramaViewer({
    src       : "./src/texture.jpg",
    rotationY : -90,
    camera    : {
        fov : 60,
    }
});

X軸も動かせるつもりで作っていたが、その後のドラッグ操作やジャイロセンサーの動きがもの凄く気持ち悪い。乗り物酔いする私の独断で回転はY軸のみとする。
※カメラの方回転すれば良さそうだが、OrbitControlsとDeviceOrientationControlsのカメラがあり、もう疲れたのでやめる。

要素を角度で回転させるのは「three js rotation degrees」とかで探した方法で適当に。

var gmBase;
var mtrBase;
if(prm.src==""){
    gmBase=new THREE.SphereBufferGeometry(1000,60,40);
    gmBase.scale(-1,1,1);
    mtrBase=new THREE.MeshNormalMaterial();
}else{
    gmBase=new THREE.SphereBufferGeometry(1000,60,40);
    gmBase.scale(-1,1,1);
    var texture=new THREE.TextureLoader().load(prm.src);
    mtrBase=new THREE.MeshBasicMaterial({
        map : texture
    });
}

球体要素を作り指定されたテクスチャ画像を貼る。ものすごく簡単で驚く。

しかしInternetExplorer11はエラーで動かない。

Test007 ラスボスIE11のテクスチャエラー

まず最初にやったこと…は、IE11のサポート期間終了日を調べることである。近々存在が抹消されるなら、そのブラウザでエラーになっても問題ないのだ。

Windows10×IE11 2025年10月まで。

「光陰矢の如し」IE11のことは忘れよう、とも考えたが頑張る。IEのためだけに何かするのはこれで何度目だろうか。

「three js texture example」で出てくるサイトをIE11でチェック、動いているコードでテストして検討→難しい→最初に戻る
この繰り返し、ずっとやってた。ずっっとやってた…。

この日もIE11でthree.js / examplesを眺めてた。
動いてそうなのはこれとかこれ

KTX形式のファイルってなんだろう、JPEGと変換できるか、とか調べても列車の写真しか出てこない。
DDSファイルならオンラインでJPGから変換できそう、「DDSLoader.js」使うのは簡単だしテクスチャも貼れる(反転してたけど)。ファイル容量が重くても「まあこれでいいか」と妥協しかけるが、やはり釈然としない。

このへんはJPEGファイルでもテクスチャ貼れているのだ。ソースの解析、自らのソースと違う部分を探すがこの歳になるとツラい。

texture.minFilter=texture.magFilter=THREE.LinearFilter;
texture.mapping=THREE.UVMapping;

気になった↑の2行を追加してみると、なんと!!うまく表示されたではないか。
※Test006と比べると真下のつなぎ目もキレイ。

ここ数日の悩みがたった2行で解決されるなんて…はっきり言って達成感より虚無感の方が大きい。無知は罪だ。
このコードの意味も推測できるが今は調べる気力もない。
今後の課題は動画やLoaderか。撮りためている十勝の絶景パノラマ写真を使ったコンテンツづくりの第一歩だ。

自分で調べても見つからないキーワードばかり並べて記事にしたがいかがだったろうか。
トカチニッチは他の方の記事も面白いので仕事に戻る前にでも気晴らしにどうぞ。


※2018.05.10追記 続編:ローディング付けました。


この記事が気に入ったら

※投稿内容に関係のないコメントや、不適切な表現に該当すると判断したコメントは、投稿者に断りなく削除する場合があります。

スマホでのドラッグ操作の動きを逆にするとYoutubeと同じ感覚になってウレシイ!

ウホ?
通訳の人よんでほしいウホ
森に帰りたいウホホ

  • コメント
  • ランキング
  • オススメ