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のoutput
のpath
の設定と、devServer
のcontentBase
の設定を同じにする必要があるようです。
ただし、同じにした場合に少し問題があります。 出力先のフォルダにindex.htmlを入れることになるので、ファイル構成上変な感じがします(私だけかもしれませんが。。。)
そこで、output
のpublicPath
を設定することで、path
とcontentBase
が異なってもライブリロードを行うことができます。 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.0とdisableHostCheck: trueを追加します。
devServer: { contentBase: path.resolve(__dirname, 'dist'), port: 3000, open:true, host 0.0.0.0, disableHostCheck: true },
まとめ
メモリ上にファイルが保存されるのがよくわからず、ハマりました。
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を使わなくてもシェーダーを定義することができるので、ライトをつかえるなど私のような初心者にはメリットが多そうでした。