Three.jsで波形アニメーションを作ってみよう!! ビルがニョキニョキ生えてくる?!

IC

「波が広がるようにビルがあらわれるアニメーション」を、ゲームなどで目にしたことはありませんか?

今回はthree.jsとTweenMax(GSAP)を使って、波形アニメーションを作る方法をご紹介します。

インスピレーション

今回はBaran Kahyaogluによる、こちらの作品を参考に制作しました。

コアコンセプト

波のようなアニメーションは、カメラとの距離に基づいて出現するビルのグリッドによって作られています。

遠いところにあるビルを霞ませることにより奥行きを表現している他、視覚的なランダムさを生み出すためにそれぞれのビルのサイズも変更しています。 wave-1

波形アニメーションを作ってみよう

まずはデモ用のマークアップを作成しましょう。

すべてのコードはcanvas要素内で実行されるため、とてもシンプルです。

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="target" content="all">
    <meta http-equiv="cleartype" content="on">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="mobile-web-app-capable" content="yes">
    <title>Buildings Wave</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js">></script>
    <script src="https://Threejs.org/examples/js//loaders/OBJLoader.js" ></script>
    <script src="https://Threejs.org/examples/js/controls/OrbitControls.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
  </head>
  <body>
  </body>
</html>

基本的なCSS

html, body {
  margin: 0;
  padding: 0;
  background-color: #fff;
  color: #fff;
  box-sizing: border-box;
  overflow: hidden;
}

canvas {
  width: 100%;
  height: 100%;
}

3Dの初期設定

メインクラス内にinitという関数を作成します。以降のメソッドはすべてこの中に追加しましょう。

init() {
  this.group = new THREE.Object3D();
  this.gridSize = 40;
  this.buildings = [];
  this.fogConfig = {
    color: '#fff',
    near: 1,
    far: 138
  };
}

3Dシーンの作成

createScene() {
  this.scene = new THREE.Scene();

  this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  this.renderer.setSize(window.innerWidth, window.innerHeight);

  this.renderer.shadowMap.enabled = true;
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  document.body.appendChild(this.renderer.domElement);

  // this is the line that will give us the nice foggy effect on the scene
  this.scene.fog = new THREE.Fog(this.fogConfig.color, this.fogConfig.near, this.fogConfig.far);
}

カメラ

シーンにカメラを追加しましょう。

createCamera() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  this.camera = new THREE.PerspectiveCamera(20, width / height, 1, 1000);

  // set the distance our camera will have from the grid
  // this will give us a nice frontal view with a little perspective
  this.camera.position.set(3, 16, 111);

  this.scene.add(this.camera);
}

地面

次に、地面を作りましょう。

addFloor() {
  const width = 200;
  const height = 200;
  const planeGeometry = new THREE.PlaneGeometry(width, height);

  // all materials can be changed according to your taste and needs
  const planeMaterial = new THREE.MeshStandardMaterial({
    color: '#fff',
    metalness: 0,
    emissive: '#000000',
    roughness: 0,
  });

  const plane = new THREE.Mesh(planeGeometry, planeMaterial);

  planeGeometry.rotateX(- Math.PI / 2);

  plane.position.y = 0;

  this.scene.add(plane);
}

3Dモデル

グリッドを構築する前に、3Dモデルを読み込んでおきます。

wave-2

loadModels(path, onLoadComplete) {
  const loader = new THREE.OBJLoader();

  loader.load(path, onLoadComplete);
}

onLoadModelsComplete(model) {
  // our buildings.obj file contains many models
  // so we have to traverse them to do some initial setup

  this.models = [...model.children].map((model) => {
    // since we don't control how the model was exported
    // we need to scale them down because they are very big

    // scale model down
    const scale = .01;
    model.scale.set(scale, scale, scale);

    // position it under the ground
    model.position.set(0, -14, 0);

    // allow them to emit and receive shadow
    model.receiveShadow = true;
    model.castShadow = true;

    return model;
  });

  // our list of models are now setup
}

環境光源

addAmbientLight() {
  const ambientLight = new THREE.AmbientLight('#fff');

  this.scene.add(ambientLight);
}

グリッドの設定

ここではじめて、ビルをグリッドレイアウトに配置します。

wave-3

createGrid() {
  // define general bounding box of the model
  const boxSize = 3;

  // define the min and max values we want to scale
  const max = .009;
  const min = .001;

  const meshParams = {
    color: '#fff',
    metalness: .58,
    emissive: '#000000',
    roughness: .18,
  };

  // create our material outside the loop so it performs better
  const material = new THREE.MeshPhysicalMaterial(meshParams);

  for (let i = 0; i < this.gridSize; i++) {
    for (let j = 0; j < this.gridSize; j++) {

      // for every iteration we pull out a random model from our models list and clone it
      const building = this.getRandomBuiding().clone();

      building.material = material;

      building.scale.y = Math.random() * (max - min + .01);

      building.position.x = (i * boxSize);
      building.position.z = (j * boxSize);

      // add each model inside a group object so we can move them easily
      this.group.add(building);

      // store a reference inside a list so we can reuse it later on
      this.buildings.push(building);
    }
  }

  this.scene.add(this.group);

  // center our group of models in the scene
  this.group.position.set(-this.gridSize - 10, 1, -this.gridSize - 10);
}

スポットライト

完成度を高くするため、スポットライトも追加します。

wave-4

addSpotLight() {
  const light = { color: '#fff', x: 100, y: 150, z: 100 };
  const spotLight = new THREE.SpotLight(light.color, 1);

  spotLight.position.set(light.x, light.y, light.z);
  spotLight.castShadow = true;

  this.scene.add(spotLight);
}

ポイントライト

さらにポイントライトも追加しましょう。

addPointLight(params) {
  // sample params
  // {
  //   color: '#00ff00',
  //   intensity: 4,
  //   position: {
  //     x: 18,
  //     y: 22,
  //     z: -9,
  //   }
  // };

  const pointLight = new THREE.PointLight(params.color, params.intensity);

  pointLight.position.set(params.position.x, params.position.y, params.position.z);

  this.scene.add(pointLight);
}

ビルを並べる

アニメーションをつける前に、カメラまでのz軸の距離に従ってビルを並べます。

sortBuildingsByDistance() {
  this.buildings.sort((a, b) => {
    if (a.position.z > b.position.z) {
      return 1;
    }

    if (a.position.z < b.position.z) {
      return -1;
    }

    return 0;
  }).reverse();
}

ビルにアニメーションをつける

ビルにアニメーションをつけましょう。リスト内の位置に基づいて、アニメーションの長さを定義します。

showBuildings() {
  this.sortBuildingsByDistance();

  this.buildings.map((building, index) => {
    TweenMax.to(building.position, .3 + (index / 350), { y: 1, ease: Power3.easeOut, delay: index / 350 });
  });
}

完成!

すると、このようなアニメーションが完成します。

ビルがニョキニョキ

今回はBack Studioのモデルを使用しましたが、この記事を参考にぜひオリジナルのモデルで波形アニメーションを楽しんでみてください。

(原文:Ion D. Filho 翻訳:codrops)

 

こちらもおすすめ!▼

SHARE

  • 広告主募集
  • ライター・編集者募集
  • WorkshipSPACE
デザイナー副業案件
Workship