LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

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

01_01.jpg

はじめに

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

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

↓明るい背景と地面を設定
01_02.jpg

↓暗めの背景と地面を設定
01_03.jpg

背景の色を変える

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

実装方法

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

01_04.jpg

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

01_05.jpg

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

01_06.jpg

実装例

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

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

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

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
changeBackground(className){
  const element = document.getElementById('canvas_container')
  element.classList.remove('home', 'co2', 'walk', 'point', 'sdgs')
  element.classList.add(className)
}

地面の色を変える

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

実装方法

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

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

実装例

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

地面メッシュの作成
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を使用して見栄えの制御が行なえます。

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
  }
}

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 );
}
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.vertGround.fragを読み込み、マテリアルを作成しています。
色を変更する場合はメッシュに適用したマテリアルのUniform変数(シェーダーとJSを繋ぐ変数)に対して、THREE.Color の形式で値を設定します。

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

GroundMaterial.js(デフォルト色)
this.material.uniforms.diffuse.value = new THREE.Color(0xdddddd)
GroundMaterial.js(暗めの色)
this.material.uniforms.diffuse.value = new THREE.Color(0x111111)

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

uniform vec3 diffuse;

01_03.jpg

サンプルデータ

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0