pagetop

BLOG

【js】three.jsを使ってみた

  • HOME

  • BLOG

  • 【js】three.jsを使ってみた

Article

【js】three.jsを使ってみた

【js】three.jsを使ってみた

今回はThree.jsを使って3Dモデルの簡単なコンテンツを作成してみました。
ずいぶん、久しぶりとなってしまいましたが、よろしくお願いします。

 

Three.jsとは

今更ですが、簡単に説明しますと….
Three.jsは、Webブラウザ上で3Dグラフィックスを描画するためのJavaScriptライブラリです。WebGLをより扱いやすくした仕組みになっており、立体的なオブジェクトの表示や回転、光や影の表現、画像テクスチャの貼り付け、カメラ操作などを比較的シンプルなコードで実装できます。Webサイトの演出やインタラクティブなコンテンツ、3Dモデルの表示など、通常のCSSやJavaScriptだけでは難しい表現を実現できるのが大きな特長です。

 

基本設定

まずはThree.jsを設定しましょう。
一番簡単なのは、CDNからThree.jsを直接importする方法なので今回はCDNを利用します。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Test</title>

<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}

#three-canvas {
width: 100%;
height: 100vh;
}

#three-canvas canvas {
display: block;
width: 100%;
height: 100%;
}
</style>
</head>

<body>

<div id="three-canvas"></div>

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

</body>
</html>

J S(function.js)

import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js';

/* --------------------------------
サイズ
-------------------------------- */
let width = window.innerWidth;
let height = window.innerHeight;


/* --------------------------------
シーン
-------------------------------- */
const scene = new THREE.Scene();


/* --------------------------------
カメラ
-------------------------------- */
const camera = new THREE.PerspectiveCamera(
50,
width / height,
0.1,
1000
);

camera.position.z = 5;


/* --------------------------------
レンダラー
-------------------------------- */
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
});

renderer.setSize(width, height);

renderer.setPixelRatio(
Math.min(window.devicePixelRatio, 2)
);


/* --------------------------------
HTMLへcanvas追加
-------------------------------- */
const canvasContainer = document.getElementById('three-canvas');

if (!canvasContainer) {
throw new Error('#three-canvas が見つかりません。');
}

canvasContainer.appendChild(renderer.domElement);


/* --------------------------------
ライト
-------------------------------- */

// 平行光
const dirLight = new THREE.DirectionalLight(
0xffffff,
2
);

dirLight.position.set(1, 1, 3);

scene.add(dirLight);


// 環境光
const ambLight = new THREE.AmbientLight(
0xffffff,
0.5
);

scene.add(ambLight);


/* --------------------------------
オブジェクト
-------------------------------- */
const geometry = new THREE.TorusKnotGeometry(
1,
0.35,
100,
16
);

const material = new THREE.MeshLambertMaterial({
color: 0x55ffff
});

const obj001 = new THREE.Mesh(
geometry,
material
);

scene.add(obj001);


/* --------------------------------
リサイズ
-------------------------------- */
window.addEventListener('resize', () => {

width = window.innerWidth;
height = window.innerHeight;

camera.aspect = width / height;
camera.updateProjectionMatrix();

renderer.setSize(width, height);

renderer.setPixelRatio(
Math.min(window.devicePixelRatio, 2)
);

});


/* --------------------------------
アニメーション
-------------------------------- */
function animate() {

requestAnimationFrame(animate);

obj001.rotation.x += 0.01;
obj001.rotation.y += 0.01;

renderer.render(
scene,
camera
);

}

animate();

ここでまず確認するポイントは 以下3つ。

1:function.js の先頭をCDN読み込みにする

import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js';

2:typeはmoduleにする

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

3:セレクタ名の確認

<div id="three-canvas"></div>

Three.jsは「3Dの舞台セットを作る道具箱」のようなもので

  • 舞台(scene)を用意し
  • カメラ(camera)を設置し
  • 照明(light)を計算し
  • オブジェクト(3Dの物体=cubeやsphereなど)を登場させ
  • アニメーション(動き)を加える

という考え方になります。

ここまでの設定で以下のようになります。

デモはこちら

 

オブジェクトの形を変える

形を変える場合はgeometryを調整します。

const geometry = new THREE.TorusKnotGeometry(
1,
0.35,
100,
16
);

MeshLambertMaterial は色や光沢などの「表面」を決めるものなので、形を変える場合は TorusKnotGeometry を別のGeometryに変更します。

たとえば、球体ならこうです。

const geometry = new THREE.SphereGeometry(
1.2,
64,
64
);

立方体なら、

const geometry = new THREE.BoxGeometry(
1.8,
1.8,
1.8
);

ドーナツ形なら、

const geometry = new THREE.TorusGeometry(
1,
0.35,
32,
100
);

正二十面体っぽい形なら、

const geometry = new THREE.IcosahedronGeometry(
1.3,
1
);

さらにThree.jsなら、既成の形だけではなく、頂点そのものを動かして有機的な形に変形させることもできます。たとえば球を少しデコボコした液体・雲・アメーバのような形にすることも可能です。

 

自転する地球を作ってみる

スクロールに合わせて自転する地球を作ってみます。
これはThree.jsに加えてGSAP ScrollTriggerを使用します。

HTML

<!-- ========================================
INTRO
======================================== -->
<section class="intro">
<div class="intro-inner">
<p>SCROLL DOWN</p>
<h1>EARTH</h1>
</div>
</section>


<!-- ========================================
THREE.JS
======================================== -->
<section class="earth-section">

<div class="earth-sticky">

<!-- Three.js Canvas -->
<div id="three-canvas"></div>

<!-- テキスト -->
<div class="earth-content">
<p class="earth-subtitle">
OUR PLANET
</p>

<h2 class="earth-title">
EARTH
</h2>
</div>

</div>

</section>


<!-- ========================================
NEXT
======================================== -->
<section class="next-section">
<div class="next-inner">

<p>NEXT CONTENT</p>

<h2>
次のコンテンツ
</h2>

</div>
</section>
<script src="js/gsap.min.js"></script>
<script src="js/ScrollTrigger.min.js"></script>
<script type="module" src="function.js"></script>

CSS

body {
background: #000;
color: #fff;
font-family:
Arial,
Helvetica,
sans-serif;
}


/* --------------------------------
INTRO
-------------------------------- */

.intro {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #111;
}

.intro-inner {
text-align: center;
}

.intro p {
margin-bottom: 20px;
font-size: 14px;
letter-spacing: 0.2em;
}

.intro h1 {
font-size: clamp(70px, 12vw, 180px);
line-height: 1;
}


/* --------------------------------
EARTH
-------------------------------- */

.earth-section {
position: relative;
width: 100%;
height: 300vh;
background: #000;
}

.earth-sticky {
position: sticky;
top: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}


/* Three.js */

#three-canvas {
position: absolute;
top: 0;
left: 0;

width: 100%;
height: 100%;

z-index: 1;
}

#three-canvas canvas {
display: block;

width: 100%;
height: 100%;
}


/* --------------------------------
テキスト
-------------------------------- */

.earth-content {
position: absolute;
top: 50%;
left: 50%;

transform: translate(-50%, -50%);

z-index: 2;

text-align: center;

pointer-events: none;
}

.earth-subtitle {
margin-bottom: 15px;

font-size: 14px;
letter-spacing: 0.3em;
}

.earth-title {
font-size: clamp(80px, 15vw, 220px);
line-height: 1;

color: rgba(255, 255, 255, 0.8);
}


/* --------------------------------
NEXT
-------------------------------- */

.next-section {
width: 100%;
height: 100vh;

display: flex;
align-items: center;
justify-content: center;

background: #eee;
color: #111;
}

.next-inner {
text-align: center;
}

.next-inner p {
margin-bottom: 15px;

font-size: 13px;
letter-spacing: 0.2em;
}

.next-inner h2 {
font-size: clamp(40px, 7vw, 100px);
}

JS

import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js';


/* --------------------------------
GSAP
-------------------------------- */

gsap.registerPlugin(ScrollTrigger);


/* --------------------------------
DOM
-------------------------------- */

const canvasContainer =
document.getElementById('three-canvas');

if (!canvasContainer) {
throw new Error(
'#three-canvas が見つかりません。'
);
}


/* --------------------------------
サイズ
-------------------------------- */

let width = window.innerWidth;
let height = window.innerHeight;


/* --------------------------------
Scene
-------------------------------- */

const scene =
new THREE.Scene();


/* --------------------------------
Camera
-------------------------------- */

const camera =
new THREE.PerspectiveCamera(
45,
width / height,
0.1,
100
);

camera.position.set(
0,
0,
6
);


/* --------------------------------
Renderer
-------------------------------- */

const renderer =
new THREE.WebGLRenderer({
alpha: true,
antialias: true
});

renderer.setSize(
width,
height
);

renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);

renderer.outputColorSpace =
THREE.SRGBColorSpace;


canvasContainer.appendChild(
renderer.domElement
);


/* --------------------------------
Earth Group
-------------------------------- */

const earthGroup =
new THREE.Group();

scene.add(
earthGroup
);


/* --------------------------------
Texture Loader
-------------------------------- */

const textureLoader =
new THREE.TextureLoader();


/* --------------------------------
Earth Texture
-------------------------------- */

/*
HTMLから見た画像パスではなく、
ブラウザからアクセスできるURLを指定します。

例:
/img/earth.jpg
./img/earth.jpg
*/

const earthTexture =
textureLoader.load(
'./img/earth.jpg',

// 読み込み成功
() => {
console.log(
'earth.jpg loaded'
);
},

undefined,

// 読み込み失敗
(error) => {
console.error(
'earth.jpg load error:',
error
);
}
);


/* --------------------------------
Texture Color
-------------------------------- */

earthTexture.colorSpace =
THREE.SRGBColorSpace;


/* --------------------------------
Earth Geometry
-------------------------------- */

const earthGeometry =
new THREE.SphereGeometry(
1.6,
64,
64
);


/* --------------------------------
Earth Material
-------------------------------- */

const earthMaterial =
new THREE.MeshStandardMaterial({

map: earthTexture,

roughness: 0.7,

metalness: 0
});


/* --------------------------------
Earth Mesh
-------------------------------- */

const earth =
new THREE.Mesh(
earthGeometry,
earthMaterial
);

earthGroup.add(
earth
);


/* --------------------------------
地軸
-------------------------------- */

earthGroup.rotation.z =
THREE.MathUtils.degToRad(
23.4
);


/* --------------------------------
Atmosphere
-------------------------------- */

const atmosphereGeometry =
new THREE.SphereGeometry(
1.64,
64,
64
);

const atmosphereMaterial =
new THREE.MeshBasicMaterial({

color: 0x66aaff,

transparent: true,

opacity: 0.06,

side: THREE.BackSide
});

const atmosphere =
new THREE.Mesh(
atmosphereGeometry,
atmosphereMaterial
);

earthGroup.add(
atmosphere
);


/* --------------------------------
Light
-------------------------------- */

const directionalLight =
new THREE.DirectionalLight(
0xffffff,
3
);

directionalLight.position.set(
4,
2,
5
);

scene.add(
directionalLight
);


const ambientLight =
new THREE.AmbientLight(
0xffffff,
0.8
);

scene.add(
ambientLight
);


/* --------------------------------
ScrollTrigger
スクロールで地球を2回転
-------------------------------- */

gsap.to(
earth.rotation,
{
y: Math.PI * 4,

ease: 'none',

scrollTrigger: {

trigger: '.earth-section',

start: 'top top',

end: 'bottom bottom',

scrub: 1,

invalidateOnRefresh: true

}
}
);


/* --------------------------------
スクロールで少し拡大
-------------------------------- */

gsap.fromTo(
earthGroup.scale,
{
x: 0.8,
y: 0.8,
z: 0.8
},
{
x: 1.15,
y: 1.15,
z: 1.15,

ease: 'none',

scrollTrigger: {

trigger: '.earth-section',

start: 'top top',

end: 'bottom bottom',

scrub: 1
}
}
);


/* --------------------------------
Render
-------------------------------- */

function render() {

requestAnimationFrame(
render
);

renderer.render(
scene,
camera
);

}

render();


/* --------------------------------
Resize
-------------------------------- */

function resize() {

width =
window.innerWidth;

height =
window.innerHeight;


camera.aspect =
width / height;

camera.updateProjectionMatrix();


renderer.setSize(
width,
height
);

renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);


ScrollTrigger.refresh();

}


window.addEventListener(
'resize',
resize
);

今回は Three.jsで地球風の球体を表示し、GSAP ScrollTriggerのscrubでスクロール量に完全連動して自転させる構成にしています。SphereGeometryで球体を作り、ScrollTriggerでrotation.yをアニメーションさせる形です。Three.js公式にもSphereGeometryが用意されており、ScrollTriggerのscrubはスクロール位置とアニメーション進行を同期させる用途です。

今回はまず仕組みが分かりやすいように、100vhの地球表示エリアを固定しながら、スクロールすると地球が2回転するデモにしています。

デモはこちら

 

自転する地球の応用版を作ってみる

今度は、スクロールで回転しながら布がひらりと解けるように、地球から1枚の世界地図にする変化するアニメーションです。
HTMLは変わりません。
CSSは以下の部分を変更します。

CSS

.earth-section {
position: relative;
width: 100%;
height: 500vh;
background: #000;
}

#three-canvas {
position: sticky;
top: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}

JS

import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js';


/* --------------------------------
GSAP
-------------------------------- */

gsap.registerPlugin(ScrollTrigger);


/* --------------------------------
DOM
-------------------------------- */

const canvasContainer =
document.getElementById('three-canvas');

if (!canvasContainer) {
throw new Error(
'#three-canvas が見つかりません。'
);
}


/* --------------------------------
サイズ
-------------------------------- */

let width =
window.innerWidth;

let height =
window.innerHeight;


/* --------------------------------
Scene
-------------------------------- */

const scene =
new THREE.Scene();


/* --------------------------------
Camera
-------------------------------- */

const camera =
new THREE.PerspectiveCamera(
45,
width / height,
0.1,
100
);

camera.position.set(
0,
0,
6
);


/* --------------------------------
Renderer
-------------------------------- */

const renderer =
new THREE.WebGLRenderer({
alpha: true,
antialias: true
});

renderer.setSize(
width,
height
);

renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);

renderer.outputColorSpace =
THREE.SRGBColorSpace;

canvasContainer.appendChild(
renderer.domElement
);


/* --------------------------------
Texture Loader
-------------------------------- */

const textureLoader =
new THREE.TextureLoader();


/* --------------------------------
Earth Texture
-------------------------------- */

const earthTexture =
textureLoader.load(
'./img/earth.jpg',

() => {

console.log(
'earth.jpg loaded'
);

},

undefined,

(error) => {

console.error(
'earth.jpg load error:',
error
);

}
);

earthTexture.colorSpace =
THREE.SRGBColorSpace;


/* --------------------------------
地球設定
-------------------------------- */

const SEGMENTS_X =
128;

const SEGMENTS_Y =
64;

const EARTH_RADIUS =
1.6;


/* --------------------------------
最終世界地図サイズ
-------------------------------- */

const MAP_WIDTH =
5.2;

const MAP_HEIGHT =
2.6;


/* --------------------------------
Geometry
-------------------------------- */

const geometry =
new THREE.PlaneGeometry(
1,
1,
SEGMENTS_X,
SEGMENTS_Y
);


/* --------------------------------
Geometry Attributes
-------------------------------- */

const positionAttribute =
geometry.attributes.position;

const uvAttribute =
geometry.attributes.uv;


/* --------------------------------
球体座標
-------------------------------- */

const spherePositions =
new Float32Array(
positionAttribute.count * 3
);


/* --------------------------------
平面座標
-------------------------------- */

const planePositions =
new Float32Array(
positionAttribute.count * 3
);


/* --------------------------------
球体と平面の座標を作成
-------------------------------- */

for (
let i = 0;
i < positionAttribute.count;
i++
) {

const u =
uvAttribute.getX(i);

const v =
uvAttribute.getY(i);


/* ----------------------------
球体
---------------------------- */

const longitude =
(u - 0.5) *
Math.PI *
2;

const latitude =
(v - 0.5) *
Math.PI;


const x =
EARTH_RADIUS *
Math.cos(latitude) *
Math.sin(longitude);

const y =
EARTH_RADIUS *
Math.sin(latitude);

const z =
EARTH_RADIUS *
Math.cos(latitude) *
Math.cos(longitude);


spherePositions[
i * 3
] = x;

spherePositions[
i * 3 + 1
] = y;

spherePositions[
i * 3 + 2
] = z;


/* ----------------------------
世界地図
---------------------------- */

const px =
(u - 0.5) *
MAP_WIDTH;

const py =
(v - 0.5) *
MAP_HEIGHT;


planePositions[
i * 3
] = px;

planePositions[
i * 3 + 1
] = py;

planePositions[
i * 3 + 2
] = 0;

}


/* --------------------------------
初期状態
-------------------------------- */

for (
let i = 0;
i < positionAttribute.count;
i++
) {

positionAttribute.setXYZ(

i,

spherePositions[
i * 3
],

spherePositions[
i * 3 + 1
],

spherePositions[
i * 3 + 2
]

);

}

positionAttribute.needsUpdate =
true;

geometry.computeVertexNormals();


/* --------------------------------
Material
-------------------------------- */

const material =
new THREE.MeshStandardMaterial({

map:
earthTexture,

roughness:
0.75,

metalness:
0,

side:
THREE.DoubleSide

});


/* --------------------------------
Earth
-------------------------------- */

const earth =
new THREE.Mesh(
geometry,
material
);

scene.add(
earth
);


/* --------------------------------
地球初期角度
-------------------------------- */

earth.rotation.x =
THREE.MathUtils.degToRad(
-5
);

earth.rotation.z =
THREE.MathUtils.degToRad(
23.4
);


/* --------------------------------
Light
-------------------------------- */

const directionalLight =
new THREE.DirectionalLight(
0xffffff,
3
);

directionalLight.position.set(
4,
2,
5
);

scene.add(
directionalLight
);


const ambientLight =
new THREE.AmbientLight(
0xffffff,
0.9
);

scene.add(
ambientLight
);


/* --------------------------------
Animation State
-------------------------------- */

const animationState = {

/*
0 = 球体
1 = 平面
*/

morph:
0,


/*
シルク波
*/

wave:
0

};


/* --------------------------------
画面いっぱいになるScaleを取得
-------------------------------- */

function getFullscreenScale() {

/*
カメラからオブジェクトまでの距離
*/

const distance =
camera.position.z -
earth.position.z;


/*
画面に見えている高さ
*/

const visibleHeight =
2 *
Math.tan(
THREE.MathUtils.degToRad(
camera.fov / 2
)
) *
distance;


/*
画面に見えている横幅
*/

const visibleWidth =
visibleHeight *
camera.aspect;


/*
地図を画面いっぱいにする倍率

coverと同じ考え方
*/

const scaleX =
visibleWidth /
MAP_WIDTH;

const scaleY =
visibleHeight /
MAP_HEIGHT;


return Math.max(
scaleX,
scaleY
);

}


/* --------------------------------
Geometry変形
-------------------------------- */

function updateGeometry() {

const morph =
animationState.morph;

const wave =
animationState.wave;


for (
let i = 0;
i < positionAttribute.count;
i++
) {

const index =
i * 3;


/* ----------------------------
球体座標
---------------------------- */

const sx =
spherePositions[
index
];

const sy =
spherePositions[
index + 1
];

const sz =
spherePositions[
index + 2
];


/* ----------------------------
平面座標
---------------------------- */

const px =
planePositions[
index
];

const py =
planePositions[
index + 1
];

const pz =
planePositions[
index + 2
];


/* ----------------------------
Morph
---------------------------- */

let x =
THREE.MathUtils.lerp(
sx,
px,
morph
);

let y =
THREE.MathUtils.lerp(
sy,
py,
morph
);

let z =
THREE.MathUtils.lerp(
sz,
pz,
morph
);


/* ----------------------------
シルク強度

開始・終了時は0
中間だけ強くする
---------------------------- */

const silkAmount =
Math.sin(
morph *
Math.PI
) *
wave;


const uvX =
uvAttribute.getX(i);

const uvY =
uvAttribute.getY(i);


/* ----------------------------
大きな横波
---------------------------- */

const wave1 =
Math.sin(
uvX *
Math.PI *
6 +
morph *
Math.PI *
2
);


/* ----------------------------
細かい縦波
---------------------------- */

const wave2 =
Math.sin(
uvY *
Math.PI *
5 -
morph *
Math.PI *
3
);


/* ----------------------------
Z方向
---------------------------- */

z +=
wave1 *
0.30 *
silkAmount;

z +=
wave2 *
0.13 *
silkAmount;


/* ----------------------------
Y方向
---------------------------- */

y +=
wave1 *
0.08 *
silkAmount;


/* ----------------------------
X方向も少し揺らす
---------------------------- */

x +=
wave2 *
0.05 *
silkAmount;


positionAttribute.setXYZ(
i,
x,
y,
z
);

}


positionAttribute.needsUpdate =
true;

geometry.computeVertexNormals();

}


/* --------------------------------
GSAP Timeline
-------------------------------- */

const earthTimeline =
gsap.timeline({

scrollTrigger: {

trigger:
'.earth-section',

start:
'top top',

end:
'bottom bottom',

scrub:
1,

invalidateOnRefresh:
true

}

});


/* --------------------------------
01
1回転目
-------------------------------- */

earthTimeline.to(
earth.rotation,
{

y:
Math.PI *
2,

duration:
1,

ease:
'none'

}
);


/* --------------------------------
02
2回転目
-------------------------------- */

earthTimeline.to(
earth.rotation,
{

y:
Math.PI *
4,

duration:
1,

ease:
'none'

}
);


/* --------------------------------
03
地軸を水平へ
-------------------------------- */

earthTimeline.to(
earth.rotation,
{

x:
0,

z:
0,

duration:
0.3,

ease:
'power2.inOut'

}
);


/* --------------------------------
04
シルク開始
-------------------------------- */

earthTimeline.to(
animationState,
{

wave:
1,

duration:
0.25,

ease:
'power2.out'

}
);


/* --------------------------------
05
球体
↓
世界地図

同時に少し拡大
-------------------------------- */

earthTimeline.to(
animationState,
{

morph:
1,

duration:
1.5,

ease:
'power2.inOut',

onUpdate:
updateGeometry

}
);


/* --------------------------------
06
シルク波を消す
-------------------------------- */

earthTimeline.to(
animationState,
{

wave:
0,

duration:
0.5,

ease:
'power2.out',

onUpdate:
updateGeometry

}
);


/* --------------------------------
07
世界地図を画面いっぱいへ
-------------------------------- */

earthTimeline.to(
earth.scale,
{

x: () =>
getFullscreenScale(),

y: () =>
getFullscreenScale(),

z: () =>
getFullscreenScale(),

duration:
1,

ease:
'power2.inOut'

}
);


/* --------------------------------
Render
-------------------------------- */

function render() {

requestAnimationFrame(
render
);

renderer.render(
scene,
camera
);

}

render();


/* --------------------------------
Resize
-------------------------------- */

function resize() {

width =
window.innerWidth;

height =
window.innerHeight;


/* ----------------------------
Camera
---------------------------- */

camera.aspect =
width / height;

camera.updateProjectionMatrix();


/* ----------------------------
Renderer
---------------------------- */

renderer.setSize(
width,
height
);

renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);


/* ----------------------------
ScrollTrigger
---------------------------- */

ScrollTrigger.refresh();

}


/* --------------------------------
Resize Event
-------------------------------- */

window.addEventListener(
'resize',
resize
);

全体の流れとしては
地球が2回転 → 地軸が水平 → シルク状に展開 → 世界地図になる → そのまま画面全体を覆う
という感じです。

重要なのは、今回は「地球のMeshを消して、別の世界地図画像をフェードイン」しているわけではない」ことです。
img/earth.jpg が最初から最後まで同じMeshに貼られていて、球体の頂点位置から、平面の頂点位置へ直接変形しています。BufferGeometryは頂点位置やUVを保持できるため、このような頂点単位の変形ができます。
なので、かなり「地球そのものがほどけて地図になる」感じになります。

デモはこちら

 

まとめ

Three.jsは、Webブラウザ上で3D表現を手軽に実装できるJavaScriptライブラリです。基本的には「シーン」「カメラ」「レンダラー」を用意し、その中に3Dオブジェクトやライト、テクスチャなどを配置して制作します。さらにGSAPやScrollTriggerと組み合わせれば、スクロールに連動した回転・移動・変形など、Webサイトならではのインタラクティブな演出も可能です。3Dモデルやシェーダーを活用すれば表現の幅はさらに広がります。Webサイトに「見る」だけでなく「体験する」要素が求められる中、Three.jsは今後も魅力的なWeb表現をつくる選択肢のひとつになりそうです。

Spread the love