2018年12月15日

Three.jsでキューブ環境マッピング

「The British Museum」を見てユーザーインターフェースに使えそうだったので、映り込みが表現できる、キューブ環境マッピングを試してみました。

※ChromeがWebXR Device APIに対応し、Three.jsの仕様が変更になったため内容を修正しました。(2019年12月13日)

キューブ環境マッピング

キューブ環境マッピングには、映り込みを表現する反射マッピングと、ガラス玉のように向こう側が屈折してみえる屈折マッピングがあります。

● テクスチャ画像を用意する

キューブ環境マッピングという名前の通り、立方体の展開図のような6枚のテクスチャ画像を用意する必要があります。普通のパノラマ画像だと、上下が足りないため、どのように制作したらよいかわからなかったのですが、パノラマ画像からキューブ環境マッピング用のテクスチャ画像に変換してくれるサイトがありました。

変換した画像をフォトショップで6枚の画像に分けます。

● テクスチャの設定

6枚のテクスチャ画像をCubeTextureLoaderで読み込み、textureCube.mappingをCubeReflectionMappingにします。CubeRefractionMappingにすると屈折マッピングになります。

//テクスチャ画像の配列
const urls = [
	'img/posx.jpg','img/negx.jpg',
	'img/posy.jpg','img/negy.jpg',
	'img/posz.jpg','img/negz.jpg',
];

//ローダーで画像読み込み
const loader = new THREE.CubeTextureLoader();
const textureCube = loader.load(urls);

//反射マッピングの設定
textureCube.mapping = THREE.CubeReflectionMapping;

const geometry = new THREE.SphereGeometry(1,64,64);
const material = new THREE.MeshPhongMaterial({
	envMap:textureCube,
});
const sphere = new THREE.Mesh(geometry,material);

● script.js

まず、「Three.jsで360°パノラマコンテンツ制作」でやったように、必要なライブラリを読み込みます。

<script src="js/lib/preloadjs.min.js"></script>
<script src="js/lib/TweenMax.min.js"></script>

Three.js関連のライブラリはscript.jsからインポートするので、script.jsはtype="module"をつけて読み込みます。

<script src="js/script.js" type="module"></script>

完成したscript.jsです。「Three.jsでオブジェクトを選択」でやったように影をつけています。

//===============================================================
// Import Library
//===============================================================
import * as THREE from './lib/three_jsm/three.module.js';
import { OrbitControls } from './lib/three_jsm/OrbitControls.js';
import { VRButton } from './lib/three_jsm/VRButton.js';

//===============================================================
// BasicView
//===============================================================
class BasicView{
    constructor(){
        this.init();
    }

    init(){
        //シーン、カメラ、レンダラーを生成
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(90,window.innerWidth/window.innerHeight,0.1,1000);
        this.camera.position.set(0,0,0);
        this.scene.add(this.camera);
        this.renderer = new THREE.WebGLRenderer({antialias:true});
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth,window.innerHeight);
        this.renderer.shadowMap.enabled = true;

        const container = document.querySelector('#canvas_wrapper');
        container.appendChild(this.renderer.domElement);

        document.body.appendChild(VRButton.createButton(this.renderer));

        const _this = this;
        window.addEventListener('resize',function(){
            _this.camera.aspect = window.innerWidth/window.innerHeight;
            _this.camera.updateProjectionMatrix();
            _this.renderer.setSize(window.innerWidth,window.innerHeight);
        },false);
    }
    startRendering(){
        this.renderer.setAnimationLoop(this.startRendering.bind(this));
        this.render();
        this.onTick();
    }
    render(){
        this.renderer.render(this.scene,this.camera);
    }
    onTick(){
    }
}
//===============================================================
// ThreeWorld extend BasicView
//===============================================================
class ThreeWorld extends BasicView{
    constructor(){
        super();
        this.initThreeWorld();
    }
    initThreeWorld(){
        this.checkDevice();
        this.setLoading();
        this.startRendering();
    }

    //開発用にPCで動作確認できるように設定
    checkDevice(){
        const ua = window.navigator.userAgent.toLowerCase();
        let _iOS,_Android,_Tablet,_Pc,_vrDisplay;

        _iOS = /ipad|iphone|ipod/.test(ua);
        _Android = /android/.test(ua);
        _Tablet = /ipad|nexus (7|9)|xoom|sch-i800|playbook|tablet|kindle/i.test(ua);
        _Pc = /windows|mac/.test(ua);

        if(_Pc || _iOS || _Tablet){
            //OrbitControlsを初期化
            const orbitControls = new OrbitControls(this.camera,this.renderer.domElement);
            orbitControls.target.set(
                this.camera.position.x + 0.01,
                this.camera.position.y + 1,
                this.camera.position.z
            );
        }else{
            //VRを許可
            this.renderer.vr.enabled = true;
        }
        document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false});
    }

    //ローディング画面
    setLoading(){
        const _this = this;
        TweenMax.to(".loader",0.1,{opacity:1});

        const manifest = [
            {id:'ground',src:'img/ground.png'}
        ];
        const loadQueue = new createjs.LoadQueue();

        loadQueue.on('progress',function(e){
            const progress = e.progress;
        });

        //ローディング画面を消去
        loadQueue.on('complete',function(){
            const image = loadQueue.getResult('ground');
            _this.texture = new THREE.Texture(image);
            _this.texture.needsUpdate = true;

            TweenMax.to("#loader_wrapper" , 1 , {
                opacity:0,
                onComplete: function(){
                    document.getElementById("loader_wrapper").style.display ="none";
                }
            });

            //ローディング後、実行
            _this.initObject();
            _this.initLight();
        });

        loadQueue.loadManifest(manifest);
    }

    initObject(){
        //床を生成
        this.texture.repeat.set(50, 50);
        this.texture.wrapS = this.texture.wrapT = THREE.RepeatWrapping;
        this.texture.magFilter = THREE.NearestFilter;
        let geometry = new THREE.PlaneGeometry(250,250);
        let material = new THREE.MeshStandardMaterial({map:this.texture,roughness:0.0,metalness:0.6});

        const floor = new THREE.Mesh(geometry,material);
        floor.rotation.x = -Math.PI / 2;
        floor.receiveShadow = true;
        this.scene.add(floor);

        //テクスチャ画像の配列
        const urls = [
            'img/texture/posx.jpg','img/texture/negx.jpg',
            'img/texture/posy.jpg','img/texture/negy.jpg',
            'img/texture/posz.jpg','img/texture/negz.jpg',
            ];

        //ローダーで画像読み込み
        const loader = new THREE.CubeTextureLoader();
        const textureCube = loader.load(urls);

        //反射マッピングの設定
        textureCube.mapping = THREE.CubeReflectionMapping;

        geometry = new THREE.SphereGeometry(1.25,64,64);
        material = new THREE.MeshPhongMaterial({
            envMap:textureCube,
        });

        //球体を生成
        const sphere = new THREE.Mesh(geometry,material);
        sphere.position.x = 5;
        sphere.position.y = 2;
        sphere.castShadow = true;

        this.scene.add(sphere);
    }

    //ライトを生成
    initLight(){
        const ambientLight = new THREE.AmbientLight(0xFFFFFF);
        this.scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1, 0);
        directionalLight.position.set(0, 100, 0);
        directionalLight.castShadow = true;
        directionalLight.shadow.camera.top = 20;
        directionalLight.shadow.camera.bottom = -20;
        directionalLight.shadow.camera.right = 20;
        directionalLight.shadow.camera.left = -20;
        directionalLight.shadow.mapSize.set(4096,4096);
        this.scene.add(directionalLight);
    }

    onTick(){
    }
}
//===============================================================
// Window load
//===============================================================
window.addEventListener("load", function () {
   const threeWorld = new ThreeWorld();
});

完成したデモになります。VRヘッドセットでアクセスすると、ルーブル美術館が映り込んだ球体が表示されます。PCで確認する場合、画面を上下にドラッグすると球体が表示されます。

  • このエントリーをはてなブックマークに追加

関連記事

前の記事へ

Three.jsでオブジェクトを選択

次の記事へ

Three.jsで360度パノラマギャラリー制作