【WebGL】Three.jsでガラスのような表現
Three.jsではCubeCameraという環境マッピング(対象オブジェクトに背景を反映させるためのマッピング)用のテクスチャ画像を生成するためのカメラが用意されていますが、
こちらを活用することで、様々なリアルな表現が可能になります。
今回はその中で、ガラスのような表現に挑戦してみました。
↓作ってみたもの
DEMO
仕組み・方法
仕組み
ガラスでは、向こう側の景色が屈折して映り、表面は反対側の景色が反射して映っています。
Three.jsのcubeCameraではこの表現に必要な屈折と反射の両方が用意されているため、
マテリアルにそれを反映することで、ガラスのような表現が可能となります。
方法
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 | //通常のカメラを設置 camera = new THREE.PerspectiveCamera( fov, wrapperElm.clientWidth / wrapperElm.clientHeight, 1, 1000 ); //シーンを設置 scene = new THREE.Scene(); //背景を準備 var mesh = new THREE.Mesh( new THREE.SphereGeometry( 500, 32, 16 ), new THREE.MeshBasicMaterial( { map: texture } ) ); mesh.scale.x = -1; scene.add( mesh ); //レンダラーを設置 renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( wrapperElm.clientWidth, wrapperElm.clientHeight ); /*-- 球体の映り込みの準備 --*/ //cubeCameraで環境マッピングを作成 cubeCamera = new THREE.CubeCamera( 1, 1000, 256 ); cubeCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; scene.add( cubeCamera ); cubeCamera2 = new THREE.CubeCamera( 1, 1000, 256 ); cubeCamera2.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; scene.add( cubeCamera2 ); //1つ目のcubeCameraには屈折マッピングを適用 cubeCamera.renderTarget.mapping = THREE.CubeRefractionMapping; material = new THREE.MeshBasicMaterial( { color: 0xf0f0ff, //色 envMap: cubeCamera.renderTarget, //屈折マッピングにしたcubeCameraで作成した環境マッピングを適用 refractionRatio: 0.75, //屈折率 } ); //2つ目は通常のマッピング(反射マッピング) material2 = new THREE.MeshBasicMaterial( { color: 0xcccccc, envMap: cubeCamera2.renderTarget.texture, //反射マッピングのcubeCameraで作成した環境マッピングを適用 reflectivity: 1, //反射率 opacity: 0.3, //不透明度で反射具合を調整 transparent: true //透明を有効に } ); //準備された映り込みを元に球体を生成 sphere = new THREE.Mesh( new THREE.IcosahedronGeometry( 20, 3 ), material ); sphere2 = new THREE.Mesh( new THREE.IcosahedronGeometry( 20, 3 ), material2 ); scene.add( sphere ); scene.add( sphere2 ); //実装 wrapperElm.appendChild( renderer.domElement ); |
屈折を反映させた球体と、反射を反映させた球体を重ねて表示することで、
ガラスのような球体を表現しました。
cubeCameraはdefaultの状態では反射マッピングとなっているため、
1つ目の屈折の球体に反映させるcubeCameraには、以下の指定で、屈折マッピングを適用します。
1 | cubeCamera.renderTarget.mapping = THREE.CubeRefractionMapping; |
屈折率は0.75くらいがガラス球っぽくなります。
以下が、DEMOの全コードです。
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 | "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 cube, sphere, sphere2, torus, material, material2; var count = 0, cubeCamera, cubeCamera2; var fov = 60, isUserInteracting = false, onMouseDownMouseX = 0, onMouseDownMouseY = 0, lon = 0, onMouseDownLon = 0, lat = 0, onMouseDownLat = 0, phi = 0, theta = 0; var textureLoader = new THREE.TextureLoader(); var wrapperElm = document.getElementById("canvas-frame"); textureLoader.load( 'images/Tropical_Beach_4k.jpg', function ( texture ) { texture.mapping = THREE.UVMapping; init( texture ); animate(); } ); function init( texture ) { //通常のカメラを設置 camera = new THREE.PerspectiveCamera( fov, wrapperElm.clientWidth / wrapperElm.clientHeight, 1, 1000 ); //シーンを設置 scene = new THREE.Scene(); //背景を準備 var mesh = new THREE.Mesh( new THREE.SphereGeometry( 500, 32, 16 ), new THREE.MeshBasicMaterial( { map: texture } ) ); mesh.scale.x = -1; scene.add( mesh ); //レンダラーを設置 renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( wrapperElm.clientWidth, wrapperElm.clientHeight ); /*-- 球体の映り込みの準備 --*/ //cubeCameraで環境マッピングを作成 cubeCamera = new THREE.CubeCamera( 1, 1000, 256 ); cubeCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; scene.add( cubeCamera ); cubeCamera2 = new THREE.CubeCamera( 1, 1000, 256 ); cubeCamera2.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; scene.add( cubeCamera2 ); //1つ目のcubeCameraには屈折マッピングを適用 cubeCamera.renderTarget.mapping = THREE.CubeRefractionMapping; material = new THREE.MeshBasicMaterial( { color: 0xf0f0ff, //色 envMap: cubeCamera.renderTarget, //屈折マッピングにしたcubeCameraで作成した環境マッピングを適用 refractionRatio: 0.75, //屈折率 } ); //2つ目は通常のマッピング(反射マッピング) material2 = new THREE.MeshBasicMaterial( { color: 0xcccccc, envMap: cubeCamera2.renderTarget.texture, //反射マッピングのcubeCameraで作成した環境マッピングを適用 reflectivity: 1, //反射率 opacity: 0.3, //不透明度で反射具合を調整 transparent: true //透明を有効に } ); //準備された映り込みを元に球体を生成 sphere = new THREE.Mesh( new THREE.IcosahedronGeometry( 20, 3 ), material ); sphere2 = new THREE.Mesh( new THREE.IcosahedronGeometry( 20, 3 ), material2 ); scene.add( sphere ); scene.add( sphere2 ); //実装 wrapperElm.appendChild( renderer.domElement ); 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.1 + onMouseDownLon; lat = ( touchClientY - onMouseDownMouseY ) * -0.1 + 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() { var time = Date.now(); lon += .15; lat = Math.max( - 85, Math.min( 85, lat ) ); phi = THREE.Math.degToRad( 90 - lat ); theta = THREE.Math.degToRad( lon ); camera.position.x = 100 * Math.sin( phi ) * Math.cos( theta ); camera.position.y = 100 * Math.cos( phi ); camera.position.z = 100 * Math.sin( phi ) * Math.sin( theta ); camera.lookAt( scene.position ); sphere.visible = false; // *cough* sphere2.visible = false; // *cough* // pingpong material.envMap = cubeCamera.renderTarget; cubeCamera.updateCubeMap( renderer, scene ); material2.envMap = cubeCamera.renderTarget.texture; cubeCamera2.updateCubeMap( renderer, scene ); count ++; sphere.visible = true; // *cough* sphere2.visible = true; // *cough* renderer.render( scene, camera ); } })(); |
参考図書
three.jsによるHTML5 3Dグラフィックス 下―ブラウザで実現するOpenGL(WebGL)の世界 遠藤 理平著 2014/1
参考デモ
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE