AMCI Develop Note #01 背景と地面の色の変更方法

![01_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/7d3cd6ad-ec54-dd02-a822-b97181c58f3f.jpeg)

# はじめに

本記事ではAMCIの演出の一つである、背景と地面の切り替え方法をまとめていきます。
コンテンツ間で背景や地面を変更することで、コンテンツが切り替わった事をより分かりやすく伝えることが出来ます。

こちらは「まちの活動量→CO2排出削減量」を選択した際と、「SDGｓ活動の集積」を選択した際の比較です。
CO2排出削減量では、削減量に応じて葉のパーティクル量が変更されるので、その雰囲気に合うような明るめな背景を設定しています。
逆にSDGｓ活動の集積では、大丸有エリアの各ビル棒グラフに見立てたビジュアライズを行っているので、ビル群が目立つように背景は夜をイメージした暗めな色味を設定しています。

↓明るい背景と地面を設定
![01_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/fa55a49a-ea2a-f219-20a8-63ea03f79456.jpeg)

↓暗めの背景と地面を設定
![01_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/39694983-ed28-8a87-7f74-bcf51701c252.jpeg)


# 背景の色を変える

それでは実際にどのように背景色を変更しているかを説明していきます。

## 実装方法



AMCIのサイトでは、下図のようなレイヤー構造でDOMやWebGLの表示を制御しています。
PLATEAUの3Dモデルやパーティクルは中央の`WebGLのレイヤー`で表示を行っています。
そして背景は一番下の`HTMLの背景レイヤー`で表示をしています。

![01_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/28fe8d68-3b32-50f4-db10-24cd892db249.jpeg)

分かりやすいように実際のサイトで色分けしたものも用意しました。

![01_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/2229daf9-7904-3617-bddb-d987bb3e2ed8.jpeg)

コンテンツの切り替え処理が発生すると、下図のような処理が行われ、WebGL側のコンテンツ切り替えと背景の変更が行われます。

![01_06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/e2188d4b-95cd-e95d-8795-4d5bb86eb164.jpeg)

## 実装例

前述の処理をAMCIサイトではどのように実装しているかを説明します。
※今回の説明に関係の無いコードは省略しているので、挙動については当記事下部のサンプルをご覧ください。



```html:index.html
<div id="canvas_container" class="canvas_container home">
  <canvas ref="canvas" class="canvas"></canvas>
</div>
```

HTML側の記述は上記のように、`<canvas>`タグを`<div>`要素で囲っています。


```scss:scss
.canvas_container {
  &.home{
    background: linear-gradient(to bottom, #95fffa 10%, #ff5c8e 50%);
  }
  &.co2{
    background: linear-gradient(to bottom, #fa86fb 10%, #ecff82 30%, #43ff9c 50%);
  }
  &.walk{
    background: linear-gradient(to bottom, #00ff76 10%, #ecff82 30%, #ff9196 50%);
  }
  &.point{
    background: linear-gradient(to bottom, #00487e 10%, #00dbfc 30%, #a8feff 50%);
  }
  &.sdgs{
    background: linear-gradient(to bottom, #000 10%, #00487e 50%);
  }
}
```

そして、CSS側は前述の`<div>`に対して背景色をグラデーションで指定しています。
この`home, co2, walk, point, sdgs`などのクラスをJavaScriptで切り替えることで背景の切り替えを実装しています。

```javascript:JavaScript
changeBackground(className){
  const element = document.getElementById('canvas_container')
  element.classList.remove('home', 'co2', 'walk', 'point', 'sdgs')
  element.classList.add(className)
}
```


# 地面の色を変える

次は地面の色の変更方法を説明します。

## 実装方法

AMCIサイトでの地面は`PlaneGeometry`を用いてメッシュとして作成しています。
色の変更はマテリアルのシェーダー変数の値を直接変更し、色を変化させています。

:::note info
地表データについて
PLATEAUで配布しているデータ内にDEM(Digital Elevation Model)という地表データも存在し、こちらを利用するとよりリアルな描画が行えますが、今回は処理負荷軽減のため使用していません。
:::

## 実装例

`PlaneGeometry`を用いた地面用メッシュの作成部分は以下の通りです。

```javascript:地面メッシュの作成
this.material = new GroundMaterial({}) // THREE.ShaderMaterialを継承したクラスを適用
this.geometry = new THREE.PlaneGeometry(80, 80) // プレーンジオメトリを作成
this.object = new THREE.Mesh(this.geometry, this.material) // マテリアルとジオメトリからメッシュを作成
this.object.rotation.x = (-90 * Math.PI) / 180 // X軸で-90度回転させて面を上向きに
Core.scene.add(this.object) // シーンに追加
```

### コード内のクラス・変数について

|変数・クラス名|説明|
|:---:|:---:|
|this.material|マテリアル用のプライベート変数|
|this.geometry|ジオメトリ用のプライベート変数|
|this.object|メッシュ用のプライベート変数|
|Core|シーンなどの管理クラス|

こちらは地面メッシュに適用しているマテリアルクラスです。
`THREE.ShaderMaterial`を継承しているため、`Shader`を使用して見栄えの制御が行なえます。


```javascript:GroundMaterial.js
import * as THREE from 'three'
import Core from '../Core'
import vertexShader from './glsl/ground.vert'
import fragmentShader from './glsl/ground.frag'

export default class GroundMaterial extends THREE.ShaderMaterial {
  constructor() {
    const time = Core.time.total

    super({
      side: THREE.DoubleSide,
      lights: true,
      fog: true,
      transparent: true,
      blending: THREE.NormalBlending,
      depthWrite: true,
      uniforms: THREE.UniformsUtils.merge([
        THREE.UniformsLib.common,
        THREE.UniformsLib.specularmap,
        THREE.UniformsLib.envmap,
        THREE.UniformsLib.aomap,
        THREE.UniformsLib.lightmap,
        THREE.UniformsLib.emissivemap,
        THREE.UniformsLib.bumpmap,
        THREE.UniformsLib.normalmap,
        THREE.UniformsLib.displacementmap,
        THREE.UniformsLib.fog,
        THREE.UniformsLib.lights,
        {
          diffuse: { value: new THREE.Color(0xffffff) },
          emissive: { value: new THREE.Color(0x000000) },
          specular: { value: new THREE.Color(0x111111) },
          shininess: { value: 30 },
          opacity: { value: 1.0 },
        },
        {
          time: { value: time },
        },
      ]),
      vertexShader,
      fragmentShader,
    })

    this.type = 'GroundMaterial'
    this.color = new THREE.Color(0x000000)
    this.specular = new THREE.Color(0x111111)
    this.shininess = 30

    //...省略

    this.flatShading = false
  }
}

```


```glsl:ground.vert
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <normal_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>

uniform float time;

varying vec3 vViewPosition;
varying vec2 vUv;

void main(){
  #include <uv_vertex>
  #include <uv2_vertex>
  #include <color_vertex>
  #include <beginnormal_vertex>
  #include <morphnormal_vertex>
  #include <skinbase_vertex>
  #include <skinnormal_vertex>
  #include <defaultnormal_vertex>
  #include <normal_vertex>
  #include <begin_vertex>
  #include <morphtarget_vertex>
  #include <skinning_vertex>
  #include <displacementmap_vertex>
  #include <project_vertex>
  #include <logdepthbuf_vertex>
  #include <clipping_planes_vertex>

  vViewPosition = - mvPosition.xyz;
  vUv = uv - 0.5;

  #include <worldpos_vertex>
  #include <envmap_vertex>
  #include <shadowmap_vertex>
  #include <fog_vertex>

  vec3 nPos = position;
  gl_Position = projectionMatrix * modelViewMatrix * vec4( nPos, 1.0 );
}
```


```glsl:ground.frag
uniform vec3 diffuse;
uniform vec3 emissive;
uniform vec3 specular;
uniform float shininess;
uniform float opacity;

#include <common>
#include <packing>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <emissivemap_pars_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_pars_fragment>
#include <cube_uv_reflection_fragment>
#include <fog_pars_fragment>
#include <bsdfs>
#include <lights_pars_begin>
#include <normal_pars_fragment>
#include <lights_phong_pars_fragment>
#include <shadowmap_pars_fragment>
#include <bumpmap_pars_fragment>
#include <normalmap_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>

uniform float time;

varying vec2 vUv;

void main() {
  #include <clipping_planes_fragment>

  vec4 diffuseColor = vec4( diffuse, opacity );
  ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
  vec3 totalEmissiveRadiance = emissive;

  #include <logdepthbuf_fragment>
  #include <map_fragment>
  #include <color_fragment>
  #include <alphamap_fragment>
  #include <alphatest_fragment>
  #include <specularmap_fragment>
  #include <normal_fragment_begin>
  #include <normal_fragment_maps>
  #include <emissivemap_fragment>
  // accumulation
  #include <lights_phong_fragment>
  #include <lights_fragment_begin>
  #include <lights_fragment_maps>
  #include <lights_fragment_end>
  // modulation
  #include <aomap_fragment>

  vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;

  #include <envmap_fragment>
  #include <output_fragment>
  #include <tonemapping_fragment>
  #include <encodings_fragment>
  #include <fog_fragment>
  #include <premultiplied_alpha_fragment>
  #include <dithering_fragment>

  float alpha = 1.0 - smoothstep(0.3, 0.5, length(vUv.xy));

  gl_FragColor = vec4(outgoingLight, diffuseColor.a * alpha);
}

```


このように、`GroundMaterial`から`Ground.vert`と`Ground.frag`を読み込み、マテリアルを作成しています。
色を変更する場合はメッシュに適用したマテリアルのUniform変数（シェーダーとJSを繋ぐ変数）に対して、`THREE.Color`　の形式で値を設定します。

下記の例では`#dddddd`と`#111111`を設定しています。

```javascript:GroundMaterial.js（デフォルト色）
this.material.uniforms.diffuse.value = new THREE.Color(0xdddddd)
```


```javascript:GroundMaterial.js（暗めの色）
this.material.uniforms.diffuse.value = new THREE.Color(0x111111)
```

こうすることで、`Ground.frag`の下記の`diffuse`値が変更され、表示にもそれが反映されます。

```
uniform vec3 diffuse;
```

![01_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36929/65f5602b-0424-cb8c-cd63-74b276752240.jpeg)


# サンプルデータ

当記事のサンプルデータは、下記のリポジトリにて公開しています。
https://github.com/Project-PLATEAU/AMCI-Sample