のぐそんブログ

暗いおじさんがシコシコ書くブログです。

THREE.DataTextureメモ

THREE.DataTexture

THREE.DataTextureを利用することで、配列データからテクスチャを作ることができます。 GPGPUなどでテクスチャをデータの格納場所として使う場合に使えそうなので、少し調べてみたいと思います。

基本的な使い方

new THREE.DataTexture( 
    data, //ArrayBufferもしくは配列
    width, 
    height, 
    format, //THREE.LuminanceFormatとか、THREE.RGBFormatとか
    type,//THREE.UnsignedByteTypeとかTHREE.FloatTypeとか
    mapping, 
    wrapS,
    wrapT,
    magFilter,
    minFilter,
    anisotropy
)

一部抜粋ですが、実際に使う場合はこんな感じ。

var w = 32;

var l = Math.pow(w, 2);
var data = new Uint8Array(l);

for (var i = 0; i < l; i++) {
  data[i] = Math.random() * 255;
}

var dataTex = new THREE.DataTexture(data, w, w, THREE.LuminanceFormat, THREE.UnsignedByteType);

dataTex.magFilter = THREE.NearestFilter;
dataTex.needsUpdate = true;//必ず必要

var geometry = new THREE.PlaneBufferGeometry(1, 1);
var material = new THREE.MeshBasicMaterial({
  map: dataTex,
  side: THREE.DoubleSide
});
var mesh = new THREE.Mesh(geometry, material);

RGBデータを入れてみる

rgbそれぞれ値を設定してみます。 ポイントは、データ配列の長さを3倍にしているところです。

var w = 32;

var l = Math.pow(w, 2);
//幅は同じだが、配列の数を3倍にする
var data = new Uint8Array(l * 3);

for (var i = 0; i < l; i++) {

  let stride = i * 3;
  //RGBそれぞれに設定する
  data[stride] = Math.random() * 256;//R
  data[stride + 1] = Math.random() * 256;//G
  data[stride + 2] = Math.random() * 256;//B
}

var dataTex = new THREE.DataTexture(data, w, w, THREE.RGBFormat, THREE.UnsignedByteType);

dataTex.magFilter = THREE.NearestFilter;
dataTex.needsUpdate = true;

var geometry = new THREE.PlaneBufferGeometry(1, 1);
var material = new THREE.MeshBasicMaterial({
  map: dataTex,
  side: THREE.DoubleSide
});
var mesh = new THREE.Mesh(geometry, material);

1テクセルにRGBの値が入ったテクスチャができあがりました。

RGBAデータを入れてみる

アルファをつかする場合はデータの配列を四倍します。

var w = 32;

var l = Math.pow(w, 2);
var data = new Uint8Array(l * 4);

for (var i = 0; i < l; i++) {

  let stride = i * 4;

  data[stride] = Math.random() * 256;
  data[stride + 1] = Math.random() * 256;
  data[stride + 2] = Math.random() * 256;
  data[stride + 3] = Math.random() * 256;
}

var dataTex = new THREE.DataTexture(data, w, w, THREE.RGBAFormat, THREE.UnsignedByteType);

dataTex.magFilter = THREE.NearestFilter;
dataTex.needsUpdate = true;

var geometry = new THREE.PlaneBufferGeometry(1, 1);
var material = new THREE.MeshBasicMaterial({
  map: dataTex,
  side: THREE.DoubleSide
});
var mesh = new THREE.Mesh(geometry, material);

GPGPUで使う場合

GPGPUのデータ格納場所として使う場合は、Uint8Arrayだと8バイトの整数(符号なし)しか使えないので、Float32Arrayを利用したほうがよさそうです。

こんな感じです。

var w = 32;
var l = Math.pow(w, 2);
var dataTex = new Float32Array(l * 4);
var dataTex = new THREE.DataTexture( data, w, h, THREE.RGBAFormat, THREE.FloatType );

ただし、画像として利用する場合は符号などは必要ないので8バイトで十分です。 あくまで、データ受け渡し用のテクスチャになります。

webpack-dev-serverを使ってみる

webpack-dev-server

開発用サーバを立てることができるモジュールです。

詳しくは公式のドキュメントを見てみてください。

手順

モジュールをインストール。

npm install --save-dev webpack-dev-server

package.jsonにscriptを追加。

"scripts": {
      "start": "webpack-dev-server"
}

サーバーを起動する。

npm start

http://localhost:8080でアクセスできるようになる。

サーバーの設定をする

サーバーの設定は、package.jsonにも指定できるが、webpack.config.jsに記載すると見やすい気がする。

サーバーに設定できるオプションで、私がよく使うのは下記。

オプション 説明
contentBase サーバの起点ディレクトリを指定。未指定の場合はカレントディレクトリが起点になる。
port 未指定の場合は8080が初期値になる。
open サーバー起動時にブラウザを開く。true or falseで指定。
inline ライブリロードを行うための設定。true or falseで指定。未指定の場合はtrue
hot Hot Module Replacement(HMR)を有効にします。true or falseで指定。未指定の場合はtrue。

※hotについては、webpack.config.jsに指定するとエラーとなるのでpackage.jsonにwebpack-dev-server --hotとして渡す必要があるようです。

webpack.config.jsに指定する場合
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    port: 3000,
    open:true
  },
package.json指定する場合
"scripts": {
  "start": "webpack-dev-server client.js --inline --hot --colors --content-base dist/ --port=3000"
}

live reloadを利用する

ライブリロードを行う場合は、webpack.config.jsのoutputpathの設定と、devServercontentBaseの設定を同じにする必要があるようです。

ただし、同じにした場合に少し問題があります。 出力先のフォルダにindex.htmlを入れることになるので、ファイル構成上変な感じがします(私だけかもしれませんが。。。)

そこで、outputpublicPathを設定することで、pathcontentBaseが異なってもライブリロードを行うことができます。 publicPathはwebpack-dev-serverを利用する際は、指定したほうが良さそうです。

const path = require('path');

module.exports = {
  //ビルドするファイル
  entry: path.resolve(__dirname, './src/main.js'), 
  //ビルドしたファイルの出力先
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js',
    publicPath: '/assets'
  },
  //開発サーバーの設定
  devServer: {
    contentBase: path.resolve(__dirname, './src'),
    inline: true,
    open: true
  },
  devtool: 'source-map'
};

webpack-dev-serverでのビルド

webpack-dev-serverでも、webpack.config.js設定にあわせてファイルのビルドが行われます。 ただしこのビルドは、実際のnpm run buildとは異なり、ファイルはメモリ上に保存されます。

例)webpack.config.jsの設定が上記の場合はこんな感じ

.
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.html
│   ├── main.js
│   └── assets //メモリ上に存在する
│        └── dist.js //メモリ上に存在する
└── webpack.config.js

その他

webpack-dev-serverはipはそのままの設定ではプライベートIPでアクセスすることができません。

プライベートIPでアクセスする為、host:0.0.0.0disableHostCheck: trueを追加します。

  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    port: 3000,
    open:true,
    host 0.0.0.0,
    disableHostCheck: true
  },

まとめ

メモリ上にファイルが保存されるのがよくわからず、ハマりました。

2018年の目標

新年を迎えて、遅くなりましたが2017年の振り返りと、2018年の目標を立てたいと思います。 あまり目標を文字に起こしたことがなかったので、今年はブログに書いておくことにします。

続きを読む

THREE.jsのEffectComposerでオリジナルのポストプロセスをやってみる

前回「THREE.jsでオリジナルのポストプロセスをやってみる」と題しましてポストプロセスについての記事を書きました。

よくよく調べると、THREE.jsにはポストプロセスを行う仕組み自体が用意されており、それを利用すればオリジナルのポストプロセスも簡単に書くことができました。

やってみる

オリジナルのポストプロセスを行うには下記のファイルが必要です。

JS

ShaderPassにShaderMaterialと同じように、unfiforms、vertexShader、fragmentShaderを含んだオブジェクトを追加します。 uniformsには、通常のシーンの描画結果を入れるtDiffuseというテクスチャ型のオブジェクトが必ず必要です。

以下の例では、ポストプロセスで通常のシーンのUV座標をずらすエフェクトをかけています。

var renderer,
    scene,
    camera,
    myCanvas = document.getElementById('myCanvas');

//RENDERER
renderer = new THREE.WebGLRenderer({
    canvas: myCanvas,
    antialias: true
});
renderer.setClearColor(0xffffff);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

//CAMERA
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 1;

//SCENE
scene = new THREE.Scene();

//LIGHTS
var light = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(light);

var material = new THREE.MeshLambertMaterial();
var geometry = new THREE.SphereGeometry(0.1, 20, 20);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);


//ポストプロセスの設定
//WebGLRendererをラップするためにEffectComposerに渡す
//最終的にはEffectComposerのインスタンスを描画する
var composer = new THREE.EffectComposer(renderer);
//現在のシーンを設定
var renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);
//カスタムシェーダー
var myEffect = {
    uniforms: {
        "tDiffuse": {
            value: null,
            type: 't'
        },
        "amount": {
            value: 1.0,
           type:'f'
        }
    },
    vertexShader: [
        "varying vec2 vUv;",
        "void main() {",
        "vUv = uv;",
        "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
        "}"
    ].join("\n"),
    fragmentShader: [
        "uniform float amount;",
        "uniform sampler2D tDiffuse;",
        "varying vec2 vUv;",
        "void main() {",
        "vec4 color = texture2D( tDiffuse, vUv );",
        "gl_FragColor = vec4( color.rgb , color.a );",
        "gl_FragColor.r = texture2D( tDiffuse, vUv + vec2(0.1,0.0)).r;",
        "gl_FragColor.g = texture2D( tDiffuse, vUv - vec2(0.1,0.0)).g;",
        "}"
    ].join("\n")
}

//エフェクト結果をスクリーンに描画する
var customPass = new THREE.ShaderPass(myEffect);
customPass.renderToScreen = true;
composer.addPass(customPass);

render();


function render() {

    composer.render();

    requestAnimationFrame(render);
}

まとめ

THREE.EffectComposerを使うと、オフスクリーンレンダリング明示的にやらなくてよかったり、画面全体を覆うポリゴンを作る必要がなかったりとポストプロセスを行う上で手間がかからない印象でした。 また、ShaderMaterialを使わなくてもシェーダーを定義することができるので、ライトをつかえるなど私のような初心者にはメリットが多そうでした。