Three.jsこと始め

HP制作・コーディングなどのお手伝いをいたします

Three.jsは、JavaScriptを使った3Dグラフィックスライブラリです
WebGLを使ってブラウザ上で3Dグラフィックスを実現することができます
Three.jsは専門家向けのライブラリであり、高度な3Dグラフィックスアプリケーションの作成を目的としています

目次
  1. セットアップ
  2. 描画してみる
  3. ジオメトリ
  4. カメラ
  5. マテリアルとライト
    1. ライト
    2. マテリアル

セットアップ

npmからインストール

npm install three
import * as THREE from 'three';
const scene = new THREE.Scene();

//または
import { Scene } from 'three';
const scene = new Scene();

CDNを使用する
モジュール版が推奨されている(モジュール未使用版もある)
importmapを使えばNode.jsと同じようにパッケージ名を書くだけにできます
*importmapはまだすべてのブラウザでサポートされているわけではないため、es-module-shims.js(ポリフィル)が必要

<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.121.1/build/three.module.js"
    }
  }
</script>
<script type="module">
  import * as THREE from 'three';
  const scene = new THREE.Scene();

</script>

OrbitControlsをCDNから利用する場合
*OrbitControlsを使うとカメラの動きをマウスでコントロールできます

<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.121.1/build/three.module.js",
      "three/addons/": "https://unpkg.com/three@0.121.1/examples/jsm/"
    }
  }
</script>

<script type="module">
 import * as THREE from 'three';
 import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
</script>

OrbitControlsはnpmではインストールは不要、個別にインポートする必要があります

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls( camera, renderer.domElement );

ブラウザーがWebGLのサポートを確認して、サポートされていない場合はユーザーにメッセージを表示

if ( WebGL.isWebGLAvailable() ) {

	// 

} else {
	alert("WebGLにブラウザーまたはマシンが対応していない可能性があります。");
  return;
}

描画してみる

scene(シーン)・renderer(レンダラー)・camera(カメラ)のセットアップ
*rendererはcameraに基づいてsceneがどのように見えるかを計算します

//シーン
const scene = new THREE.Scene();
//レンダラー
/*antialias:輪郭がギザギザになることを抑える処理
有効にすると計算量が増え動作が重くなる場合があります*/
const renderer = new THREE.WebGLRenderer({antialias: true});
document.body.appendChild( renderer.domElement ); //ページに追加する
/* HTMLに配置したcanvas要素を指定する場合
const renderer = new THREE.WebGLRenderer({
 canvas: document.querySelector('#myCanvas')
});
*/
/*デバイスのピクセル比を設定
renderer.setPixelRatio(window.devicePixelRatio);
デバイスのピクセル比を考慮してキャンバスのサイズを変更
renderer.setSize( window.innerWidth, window.innerHeight );
*/
//カメラ
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.z = 5;

備考

シーン(THREE.Scene)について
THREE.Sceneは全てのオブジェクトを保持します
*Three.jsでsceneに追加できるオブジェクト(THREE.Sceneも含む)は全てTHREE.Object3Dを継承しています

THREE.Object3Dはthree.js のほとんどのオブジェクトの基本クラスでオブジェクトを操作するためのプロパティとメソッドがある
*例えばPerspectiveCameraオブジェクトは自分のプロパティとメソッド以外にもObject3DとCameraのプロパティとメソッドが使える

Object3Dクラスの主なプロパティやメソッド

  • position:オブジェクトの位置を示すVector3オブジェクトです
  • rotation:オブジェクトの回転
  • scale:オブジェクトの拡大縮小
  • children:子オブジェクトの配列
  • add(child):子オブジェクトを追加
  • remove(child):子オブジェクトを削除
  • traverse(callback):すべての子オブジェクトを再帰的にトラバースし、コールバック関数を呼び出します
  • clone(recursive):オブジェクトを複製

*ちなみに数字は相対的な値なので比率が重要

rendererのメソッドの一部抜粋

  • setSize(width, height , Boolean)
    デフォルトではrender.setSizeはデバイスのピクセル比を考慮してキャンバスのサイズを変更します
    第三引数にfalseに設定するとキャンバスのCSSサイズで変更されなくなります
    備考:そもそもキャンバスのサイズは2種類あります
    • ページに表示されるサイズ(CSSで設定)
      canvas.clientWidth・canvas.clientHeight
    • キャンバス自体のピクセル数(ディスプレイで異なる)
      canvas.width・canvas.height
  • setClearColor ( color, alpha )
    クリアカラーと不透明度を設定
  • shadowMap.enabled = true;
    影の描写(デフォルトはfalse・計算コスト大)
    plane.receiveShadow = true;(影を落とされる物体)
    cube.castShadow = true;(影を落とす物体)

備考
背景色を設定する
scene.background:テクスチャーやキューブマップなどのより複雑な背景を設定できます
renderer.setClearColor:単一の背景色を設定するためのシンプルな方法

scene.background = new THREE.Color( 0xffffff );
renderer.setClearColor( 0xffffff );

立方体はMesh(メッシュ)というオブジェクトを使用して作成します
メッシュを作るには、ジオメトリ(形状)とマテリアル(素材)が必要です
*three.jsで描画するものはシーンに追加する必要があります

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshNormalMaterial();
// new THREE.Mesh(ジオメトリ,マテリアル)
const cube = new THREE.Mesh( geometry, material );
// シーンに追加
scene.add( cube );

シーンのレンダリング
requestAnimationFrameのタイミングにあわせて、シーンをレンダリングを行うように設定

function animate() {
	requestAnimationFrame( animate );
	cube.rotation.x += 0.01;  //cube.rotateX(0.01)でもいい
	cube.rotation.y += 0.01;  //cube.rotateY(0.01)でもいい
//レンダラーのrender関数にシーンとカメラに渡し、シーンをレンダリング
	renderer.render( scene, camera );
	};
animate();

/*またはrenderer.setAnimationLoop(callback)
requestAnimationFrameの代わりに使用できる
nullが渡されるとアニメーションが停止 
renderer.setAnimationLoop(animate)
function animate() {
//XとY回転
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
*/

Canvasのリサイズ処理

備考:renderer.setPixelRatioについて
https://threejs.org/manual/#ja/responsive

 /*ドキュメントによると非推奨
デバイスのピクセル比を設定
renderer.setPixelRatio(window.devicePixelRatio);
デバイスのピクセル比を考慮してキャンバスのサイズを変更
renderer.setSize( window.innerWidth, window.innerHeight );
*/

//キャンバスのサイズ変更時にレンダラーの調整する関数を使用
function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const pixelRatio = window.devicePixelRatio;
  const width = (canvas.clientWidth * pixelRatio) | 0;
  const height = (canvas.clientHeight * pixelRatio) | 0;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}
init();
window.addEventListener('resize', init);
function init() {
  //サイズを取得
  const width = window.innerWidth;
  const height = window.innerHeight;
  //レンダラーの調整
  resizeRendererToDisplaySize(renderer);
  // カメラのアスペクト比
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
}
function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const pixelRatio = window.devicePixelRatio;
  const width = (canvas.clientWidth * pixelRatio) | 0;
  const height = (canvas.clientHeight * pixelRatio) | 0;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

ジオメトリ

ジオメトリは3Dモデルの形状を定義するためのデータ構造です
ジオメトリは「頂点、面、法線、テクスチャ座標」などの情報を持ちます

一般的に、ジオメトリは以下の手順で作成されます

  1. 頂点の作成
    頂点(3次元空間内の位置を表す座標)を定義します
  2. 面の作成
    頂点を結んで面を作成
    面は頂点を結んでできる平面のことで多角形で表現されます
    ジオメトリの面は、通常三角形か四角形です
    *三角形が一般的に使用される理由は、WebGLが三角形を描画することに特化しているためです
  3. 法線の計算
    各面の法線ベクトルを定義
    法線は面の向きを定義するために使用されます
    法線ベクトルは、面の平面に垂直な単位ベクトルです。
  4. テクスチャ座標(UV座標)の作成
    テクスチャマッピングを行う場合はテクスチャ座標を追加する必要があります
    テクスチャ座標は、3Dモデルの表面に貼り付けられる2D画像の位置を定義します。

ジオメトリは要素を直接定義することができる他、事前に用意されているジオメトリを使用することもできます。

Three.jsに用意されているさまざまなジオメトリ(形状)の抜粋

  • 箱:BoxGeometry(width, height,奥行き)
  • 球体:SphereGeometry(半径,widthの分割数, heightの分割数 )
  • 20面体:IcosahedronGeometry(半径)
  • 12面体:DodecahedronGeometry(半径)
  • 8面体:OctahedronGeometry(半径)
  • 4面体:TetrahedronGeometry(半径)
  • 円錐:ConeGeometry( 半径,高さ,Radialの分割数 )
  • 円柱:CylinderGeometry(radiusTop,radiusBottom,高さ,Radialの分割数)
  • ドーナツ:TorusGeometry(半径、チューブ半径、Radialの分割数、tubularの分割数 )
  • 平面:PlaneGeometry( width, height )
  • 平らな円:CircleGeometry(半径,分割数 )

ShapeGeometryExtrudeGeometryで星型のオブジェクトを作る
*ExtrudeGeometryは2Dシェイプを3Dジオメトリに押し出します

const geometry = createStarGeometry();
function createStarGeometry(innerRadius = 0.4, outerRadius = 0.8, points = 5) {
  const shape = new THREE.Shape();
  const PI2 = Math.PI * 2;
  const inc = PI2 / (points * 2);
  shape.moveTo(outerRadius, 0);
  let inner = true;
  for (let i = inc; i < PI2; i += inc) {
    const radius = inner ? innerRadius : outerRadius;
    shape.lineTo(Math.cos(i) * radius, Math.sin(i) * radius);
    inner = !inner;
  }
  const extrudeSettings = {
    steps: 1,
    depth: 1,
    bevelEnabled: false,
  };
  return new THREE.ExtrudeGeometry(shape, extrudeSettings);
}

余談:度からラジアンへの変換

const deg = 45
const rad = deg * (Math.PI / 180 )

カメラ

  • PerspectiveCamera : 遠くにある立方体ほど小さく描画される
    現実世界のような奥行きのあるシーン
  • OrthographicCamera : 立方体が同じサイズで描画される
    ゲームでよくある擬似3Dのようなシーン

*プロパティの変更を有効にするにはupdateProjectionMatrixを呼び出す必要がある

PerspectiveCameraについて
参照:https://threejs.org/manual/#ja/fundamentals
4つの引数で錐台(先端が切り取られたピラミッドのような3D形状)を定義します
*錐台の外側は何も描画しません

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

CameraHelper(カメラの錐台を描画)
オブジェクトがカメラの内にあるか等の確認ができます

const cameraHelper = new THREE.CameraHelper(camera);
scene.add(cameraHelper);
  • fov
    視野角(垂直方向)
    *パースペクティブカメラはラジアン単位ではなく角度
    *遠近感を強調したい場合は視野角を大きくする
  • aspect
    Canvasの表示アスペクト
    *カメラのアスペクトをCanvasの表示サイズのアスペクトに設定することでウィンドウの大きさに関係なく形状のアスペクト比が維持できる
  • nearとfar
    レンダリングされるカメラのスペースを表します
    *範囲の前またはその範囲の後は描画されません

錐台の高さ:視野によって決まります
錐台の幅:高さにaspectを掛ける

*視野はアスペクトの影響を受けます
幅は高さより広いので視野もその分広くなります

OrthographicCameraについて

const camera = new THREE.OrthographicCamera( left, right, top, bottom, near, far );
/*
camera = new THREE.OrthographicCamera(
 window.innerWidth / -16, 
 window.innerWidth / 16, 
 window.innerHeight / 16, 
 window.innerHeight / -16, 
 -200, 
 500
);
*/

カメラの配置と視点

デフォルトではカメラの位置(0,0,0)で視点も中心(0,0,0)を向いている
Yを上にして奥を見ているのでカメラを原点から少し後ろ(Z軸)に動かして物体が見えるようにする

  • カメラの位置
    camera.position.set (0,0,3);
    *3単位後方にカメラを移動
  • カメラの視点を変更
    camera.lookAt(new THREE.Vector3(x, y, z))
    *lookAt関数は引数に渡した位置を「見る」ようにカメラを向けます
  • カメラにとってどの方向が「上」かを伝える
    デフォルトはY軸の正が上で設定は不要だが、上から見下ろす場合Z軸の正が上だとカメラに伝える必要がる
    camera.up.set(0, 0, 1);

カメラの動きを自動的に制御する THREE.OrbitControlsクラスがあります
マウス操作でカメラを制御できます
マウスホイール:ズーム
左ボタンでドラッグ:回転
右ボタンでドラッグ:パン(向きを左右に振る)
.update()はカメラの位置を手動で変更した後に呼び出す必要がある

// カメラを作成
const camera = new THREE.PerspectiveCamera(/*省略*/);
// カメラの初期座標を設定
camera.position.set(0, 0, 5);
const controls = new OrbitControls( camera, renderer.domElement );
/*原点から5単位上にターゲットを設定する場合の例
controls.target.set(0, 5, 0);
*/
/*滑らかなカメラコントローラーを制御する場合
controls.enableDamping = true;
controls.dampingFactor = 0.2;
アニメーションループ内でupdate()を呼び出す必要がある
controls.update();
*/

座標を表示する
X 軸(赤)・ Y 軸(緑)・Z 軸(青)

// 引数は線の長さ(デフォルトは 1)
const axes = new THREE.AxisHelper(25);
scene.add(axes);

平面にグリッドを表示する

//sizeグリッドのサイズ/デフォルトは10 
//Divisionsグリッド全体の分割数/デフォルトは10
const gridHelper = new THREE.GridHelper( size, divisions );
scene.add(gridHelper);

マテリアルとライト

「ライトが必要なマテリアル」と「ライト不要のマテリアル」があります

色は16進数(例:0xFF0000)または
CSS文字列(例:'#F00''red''rgb(255,0,0)''hsl(0,100%,50%)')で設定できます

備考

ColorはRGB(Red、Green、Blue)値に基づく色を表現するためのクラスです
*値は0〜1

const color = new THREE.Color( r, g, b );

ColorクラスではRGB値の他にHSL(Hue色相、Saturation彩度、Lightness明度)値でも色を表現きます
ColorオブジェクトをHSL値で作成する

const color = new THREE.Color();
color.setHSL( h, s, l );

ライト

シーンに追加する
*ターゲットを設定していない場合のデフォルトはターゲットの位置は(0,0,0)

const light = new THREE.AmbientLight(0xFFFFFF, 1.0);
scene.add(light);

//ターゲットの設定(座標)
const light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
/*light.position.set(0, 10, 0);
scene.add(light);*/
light.target.position.set(-5, 0, 0);
scene.add(light.target);

//ターゲットの設定(オブジェクト)
const light = new THREE.SpotLight(0xFFFFFF, 1.0);
scene.add(ターゲットオブジェクト);
light.target = ターゲットオブジェクト;

Three.jsにはさまざまなライトが用意されています

  • AmbientLight(色, 強さ)
    全体に均等に光を当てます
    影は出来ない
    他のライトと組み合わせて暗くなりすぎないように色味を加えたりするために使用
  • DirectionalLight(色, 強さ)
    特定の方向(デフォルトは0,0,0)に放射される光で光線はすべて平行になります
    影はオブジェクトの位置に影響されない
    イメージは太陽
  • PointLight(色, 強さ, 距離, 光の減衰率)
    単一点からあらゆる方向から放射される光源(周辺を明るくする)
    影はできない
    イメージは裸電球
    距離が0の場合は無限に輝きます
  • SpotLight(色, 強さ, 距離, 照射角, ボケ具合, 減衰率)
    単一の点から一方向に放出される
    距離に沿ってライトが暗くなる
    照射角:光分散の最大角度(上限 Math.PI/2)
    ボケ具合:0~1でデフォルトは0
    イメージは懐中電灯(光が円錐の内側だけを照らし、円錐はターゲットに向かって開きます)
  • HemisphereLight(空の色, 地の色, 強さ)
    上からの光の色と下からの光の色を分けられる
    下からの光は反射光として屋外での光の見え方に近い
    影は出来ない
  • RectAreaLight(色, 強さ, ライトの幅・ライトの高さ)
    一定の方向を照らす
    ひかる板のイメージ

ライトのヘルパー
例:SpotLightHelper

const spotLight = new THREE.SpotLight(0xffffff);
const lightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLight);
scene.add(lightHelper);
// ループ内でヘルパーを更新する場合
lightHelper.update();

//DirectionalLightのヘルパー
//const dLightHelper = new THREE.DirectionalLightHelper(light, 5);

マテリアル

const material = new THREE.MeshBasicMaterial({ color: 0xFF0000});

//または
const material = new THREE.MeshPhongMaterial();
material.color.set(0x00FFFF); 

//その他のプロパティの一例
//flatShading:立方体のときの面がそのまま残るような見た目
material.flatShading=true
//side:面のどちら側をレンダリングするか THREE.BackSideand(裏面) 
material.side = THREE.DoubleSide(両側)

ライト不要(ライトに反応しない)のマテリアル

  • MeshNormalMaterial
    RGBで可視化するマテリアル
  • MeshBasicMaterial
    陰がつかないので均一な塗りつぶした状態にる

ライトが必要なマテリアル

  • MeshLambertMaterial(光沢感がない)
    頂点でのみ光を計算します
  • MeshToonMaterial(2トーンの外観ができる)
  • MeshPhongMaterial(光沢感のある)
    全てのピクセルで光を計算
    鏡面反射(specular)もサポートは
    *鏡面反射は光源からの光が光沢のある表面に反射して見える光源の鏡像
    *shininess設定は特定のハイライトの輝きを決めます
    デフォルトは30
  • MeshStandardMaterial(光の反射や散乱など現実の物理現象を再現)
    2つの設定がある(roughnessとmetalness)
    roughness(粗さ)はshininessの反対で 0(光沢)〜1(マッド)
    metalness(金属的) 0(非金属)〜1(金属)

マテリアルの描画は多くのGPUパワーが必要なので必要ない場合は、単純な素材を使用します

  1. MeshBasicMaterial
  2. MeshLambertMaterial
  3. MeshPhongMaterial
  4. MeshStandardMaterial

動的に影を作る方法(光源の位置によって影の位置を変更)
*計算量が高く精度低い

  1. レンダラーで影を有効にする
  2. ライトの影を有効にする
  3. 影を落としたいMeshオブジェクトを設定
  4. 影を受けるMeshオブジェクトを設定

*3Dのモデリングソフトなどで焼きこみ(影となる暗い部分)方法もある

//レンダラーで影を有効にする
renderer.shadowMap.enabled = true;
//ライトの影を有効にする
.castShadow = true;
//影を落としたいMeshオブジェクトを設定
.receiveShadow = true;
//影を受けるMeshオブジェクトを設定
.castShadow = true;

DirectionalLightの影の場合
範囲が小さい場合、shadowプロパティの設定が必要
*カメラのヘルパーにlight.shadow.cameraを渡すことで影を調べることができる

light.shadow.camera.right = 100;
light.shadow.camera.left = -100;
light.shadow.camera.top = -100;
light.shadow.camera.bottom = 100;
//カメラのヘルパーにlight.shadow.cameraを渡す
const helper = new THREE.CameraHelper( light.shadow.camera );
scene.add( helper );

備考

カメラからの距離によって特定の色にフェードアウトする
THREE.SceneオブジェクトのFog(FogExp2)でカメラから遠いオブジェクトほど暗く(薄く)表示できます

//far(color)からnearにcolorがフェード
scene.fog = new THREE.Fog(color, near, far);
//距離に応じて指数関数的に大きくなる
scene.fog = new THREE.FogExp2(color, density);