Three.jsではじめてwebGLコンテンツをつくるときに参考なった公式Example

  • 18
    Like
  • 1
    Comment

残された時間は3週間、初めてのThree.js、なんとか納めるために序盤戦で役に立った
公式のExample(https://threejs.org/examples/) を紹介するコーナです。
最初の3日くらいはひたすらExampleを見てコピペしたり改変したりの作業をしていた。

まずはじめに

Exampleをみる前にまずは基本の基本を抑えるために以下のページを見て
Boxを回転します。
https://threejs.org/docs/index.html#manual/introduction/Creating-a-scene

すると

  • rendererというものをappendChildすればいい。
  • updateはrequestAnimationFrameで行う。
  • rendererをそこで更新する。
  • meshはgeometryとmaterialをつくって生成して、sceneにaddする

などなどの基礎知識が得られた。

animation / keyframes / json

https://threejs.org/examples/#webgl_animation_keyframes_json

プロジェクトでは3dモデルとアニメーションを使うことが決まっていたのでこれから見始めた。
右下をview sourceを押せばソースコード見れる。

特別なところは

真ん中らへんの

new THREE.ObjectLoader().load( 'models/json/pump/pump.json', function ( model ) {
  scene.add( model );
  mixer = new THREE.AnimationMixer( model );
  mixer.clipAction( model.animations[ 0 ] ).play();
  animate();
} );

とanimateの中の

mixer.update( clock.getDelta() );

この辺。

  • ObjectLoaderでpathをしていすればmodelのmeshが得られる
  • AnimationMixerをつくってmodelをいれてclipActionをplayする
  • mixer.updateで更新する
  • 前フレームとのdeltaTimeはTHREE.ClockというのをつくってgetDelta()したら得られるらしい
  • window.onresizeの中をみるとウインドウサイズが変わった時のcanvasのサイズの変え方がわかった

という知見が得られる。特にTHREE.ClockでgetDeltaするのはどこでも使える知識なので
早めに入手できてよかった。
ウインドウサイズまわりもどのサンプルにも書いてあるけどここで覚えた。

loader / fbx

https://threejs.org/examples/#webgl_loader_fbx

3Dモデルは用意してもらうかたちだったが一応fbxでも読み込めるように準備しておかなければ
と思い、次はこっちをみた。
基本はさっきのと変わらなかった。

  • FBXLoaderを別ファイルでインポートしないとfbxはloadできない
  • onProgressっていうの使ってloadするとprogress取れる機能があるらしい(使わなかった)

余談だが、FBXLoaderをインポートするのに、exampleのようにscriptタグを書くのではなくて
requireしたくて少しハマった。

これの解決方法はしっかり覚えていないんですが
exports-loader https://github.com/webpack-contrib/exports-loader
(もしかしたらimports-loaderも必要かも...)

をnpm installまたはyarn addして

webpack.configに以下かいて

resolve: {
  alias: {
    'three-extras': path.resolve(__dirname, 'node_modules/three/examples/js/')
  }
}

example以下の使いたいのがあるjsファイル内で
import FBXLoader from 'imports-loader?THREE=three!exports-loader?THREE.FBXLoader!three-extras/loaders/FBXLoader'

冗長だけどとりあえずこれで解決した。(たぶん)

結局fbx使わなかったけれど、この知見は他で役に立った。

loader / json /blender

https://threejs.org/examples/#webgl_loader_json_blender

プロジェクトでは結局fbxをblenderでjsonに変換して使うことにしたが、
何らかの理由で上のObjectLoaderじゃなくてJsonLoaderをつかわないといけなくなったので
これも確認した。

  • このExampleだけで得られる知見は特になかった。

が、このあたりで***Loader.load('hogehoge' ()=>{});とか書くのが嫌になってきて
これ辺りの記事(http://qiita.com/sawa-zen/items/cba55a23411753f1353e)
をみながらモデルやらテクスチャやらをpreloadしてから使っていた。

以下は参考に書いたソースコードの抜粋です

const loader = new THREE.JSONLoader();
const json = PreLoad.getData("modelName"); //preloadjsをラップしたクラス,loadQue.getResult('hoge')のデータが返ってくる
const tex = PreLoad.getData("textureName");

const threeTex = new THREE.Texture(tex);
threeTex.needsUpdate = true;
const model = loader.parse(json);

const material = model.materials[0];
material.map = threeTex;
const mesh = new THREE.Mesh(model.geometry, material);

こんな感じでかけばネストがなくて気持ち少し楽

buffergeometry / instancing / dynamic

https://threejs.org/examples/#webgl_buffergeometry_instancing_dynamic

紙吹雪みたいなのを大量にだしたいことが決まっていたので
ここはシェーダーでgpuパーティクル?かなと思いこれを確認した。

これは優秀なサンプル

こちらの記事(https://tkmh.me/blog/2016/12/05/555/) と合わせて読んでいくと理解が深まった。

  • シェーダーでのパーティクルはinstancedBufferGeometryを使う
  • それに、vertex, index, uv(テクスチャ使うなら)をsetするのが基本(正確にはこれらも必須というわけではないと思う)
  • offestとorientationは独自実装(それぞれvertex shaderで位置と角度を計算するための値)

ちなみに、後々わかったことでPlaneをつかいたいだけならわざわざ手書きでindexやらvertexやら書かなくても

const plane = new THREE.PlaneBufferGeometry(1, 1, 10, 10);
let vertices = plane.attributes.position.clone();
let uv = plane.attributes.uv.clone();
let indices = plane.index.clone();
this.geometry = new THREE.InstancedBufferGeometry();

this.geometry.addAttribute('position', vertices);
this.geometry.addAttribute('uv', uv);
this.geometry.setIndex(indices);

こういう感じで、一回planeのbuffergeometryつくってそれの値をつかって
instancedBufferGeometryにaddなりsetなりすれば簡単で短い。

このサンプル、理解がするまでに何度も見直したりパラメーター変えたりのスタディをした。

postprocessing / background

https://threejs.org/examples/#webgl_postprocessing_backgrounds

独自にポストエフェクトを書くことが予想できたのでとりあえずこれ。

  • three.jsでのポストエフェクトのかけ方
  • EffectComposerというものを使って、パスを分けて?レンダリングすること

基本的なpostprocessingはthree/examples/js/postporcessing 以下にあるので
このサンプルで使用されているもの以外にも使えばいいと思います。

また、シェーダーでのポストエフェクトを独自で作成したくなると踏んだので
そのあたりもここで一度調査した。

結局、sampleを解体するのがいいかなと思って、CopyShaderという
ただ画面をコピーしているだけのシンプルそうなものがあったのでそれベースで
オリジナルでシェーダー書けるようにしたのがこちら。

PPTest.js
import postVert from '../../shader/postVert.glsl'
import postFrag from '../../shader/postFrag.glsl'
import * as THREE from 'three'

const PPTest = function ( map, opacity ) {
  THREE.Pass.call( this );

  this.map = map;
  this.opacity = ( opacity !== undefined ) ? opacity : 1.0;

  this.uniforms = {
    "tDiffuse": { value: null },
    "opacity":  { value: 1.0 }
  }

  this.material = new THREE.ShaderMaterial( {

    uniforms: this.uniforms,
    vertexShader: postVert,
    fragmentShader: postFrag,
    depthTest: false,
    depthWrite: false

  } );

  this.needsSwap = false;

  this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
  this.scene  = new THREE.Scene();

  this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null );
  this.quad.frustumCulled = false; // Avoid getting clipped
  this.scene.add( this.quad );

};

PPTest.prototype = Object.assign( Object.create( THREE.Pass.prototype ), {

  constructor: THREE.PPTest,

  render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {

    var oldAutoClear = renderer.autoClear;
    renderer.autoClear = false;

    this.quad.material = this.material;

    this.uniforms[ "opacity" ].value = this.opacity;
    this.uniforms[ "tDiffuse" ].value = this.map;
    this.material.transparent = ( this.opacity < 1.0 );

    renderer.render( this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear );

    renderer.autoClear = oldAutoClear;
  }

} );

export default PPTest;

要らなさそうな部分は削って、materialのvertexshader, fragmentshaderを
自分で書いたものを入れるようにしてExportした。Exportできるの重要
jsを書き始めたの最近なのでPrototypeとかよく知らない仕様...
今改めるとコンストラクタのopacityとか不要そうだし削っても良かった気がする。

ちなみに、ここでshaderファイルをwebpackでimportするのどうしようってなって
いくつかloaderを試して、以下を使った。
https://www.npmjs.com/package/shader-loader

以下画面のRを1.0にするだけのを書いてテストしたコード

postVert.glsl
varying vec2 vUv;

void main() {

  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

}
postFrag.glsl
uniform float opacity;
uniform sampler2D tDiffuse;
varying vec2 vUv;

void main() {
  vec4 texel = texture2D( tDiffuse, vUv );
  texel.r = 1.0;
  gl_FragColor = opacity * texel;
}

intaractive / cubes

https://threejs.org/examples/#webgl_interactive_cubes

マウスインタラクションを加えたかったのでとりあえずこれを見た。

  • rayを飛ばしてマウスの座標を得る

そして、結局この記事がとてもシンプルで良かった。
http://qiita.com/edo_m18/items/5aff5c5e4f421ddd97dc

マウスインタラクションについては以上です。

おわりに

長くて、中途半端な記事を読んでいただいてありがとうございます。
間違っていること、ご質問等あればよろしくお願いします。

上記のExampleを3,4日眺めたり触ったりした段階でかなり
three.jsの環境での制作環境になれることができたので
初めての時に、時間がなくても一度要件を分解して
たくさんあるExampleから似ているものを一通りさらっていくのおすすめです。