3Dオブジェクトの頂点情報をデータとして利用する
TouchDesignerで3Dオブジェクトから頂点座標を抜き出す
3Dオブジェクトから頂点座標を抜き出して、データとして利用する方法のメモです。
①SOPから、好きな3Dオブジェクトを配置。
②オペレーターの右下にある「 + 」ボタンを押す。
③右クリックでコンテキストメニューから「Display Options」を選択
※トラックパッドの場合は2点タッチ
④メニューからAllを選択
⑤SOP toオペレーターをつかって頂点座標を数値化する
頂点座標に3Dオブジェクトを配置する
3Dオブジェクトを利用するためには、「Camera COMP」、Light COMP」、「Geometry COMP」が必要です。
インスタンスを利用する
①Geometoryでインスタンスを利用できるようにする
Geometryを選択して、「instancce」タブを選択して「Instancing」をONにします。 ONにすることで、「Instance CHOP/DAT」を選択できるようになります。
②頂点データをインスンタンスに設定する
③インスタンスの座標を割り当てる
②で設定した頂点データをどの座標として使うかを設定する。
④頂点に設定されているgeometoryの大きさを調整
Box SOPをSIZE「0.01,0.01,0.01」で設定する。
その他
頂点データの詳しい内容は、DATAのSOP toオペレーターで確認することができる。
まとめ
最終的にはこんな感じになります。 基礎的な部分ですがよく使う方法らしいので、覚えておくようにしたいと思います。
GPGPUがまったくわからないのでGPUComputationRenderer.jsをまず調べてみるメモ
GPGPU用のヘルパーライブラリであるGPUComputationRenderer.jsの使い方を調べてみました。
GPGPUが難しいので上手く説明できておらず、よくわからない文章になってしまっています。 あまり参考にならないかもしれません。
こちらの「Three.jsのGPGPUのサンプルが難しすぎるから解体して勉強してみる」のがとてもわかりやすので参考になります。
やること事
なんとなくのイメージこんな感じです。 GPGPUなのでテクスチャをデータ格納用に利用します。 テクスチャは、参照用と、データ格納用を作成して毎レーム入れ替えていきます。 ※複数のフレームバッファを使う場合です。1つだけの場合は「オフスクリーンレンダリング2」はいりません。
STEP 1
GPUComputationRendererのインスタンスを作成する。
引数のwidth, height, renderer
はオフスクリーンレンダリング用のWebGLRenderTargetや、DataTextureを作成する際に利用される。
var gpuRender = new GPUComputationRenderer(width, height, renderer);
GPUComputationRendererのインスタンスを作成すると、はじめに下記のようなオブジェクトが作成される。
var material = new THREE.ShaderMaterial( { uniforms:{ texture: { value: null } }, vertexShader: vs,//中身は↓に記載 fragmentShader: fs//中身は↓に記載 } ); var geometory = new THREE.PlaneBufferGeometry( 2, 2 ); var mesh = new THREE.Mesh(geometory, material );
◎vs
void main() { gl_Position = vec4( position, 1.0 ); }
◎fs
uniform sampler2D texture; void main() { vec2 uv = gl_FragCoord.xy / resolution.xy; gl_FragColor = texture2D( texture, uv ); }
STEP 2
テクスチャを作成する。
var textuer = gpuCompute.createTexture();
GPUComputationRenderer内で以下の処理が実行されテクスチャデータが作成される。
var arr = new Float32Array( width * height * 4 ); var texture = new THREE.DataTexture( arr, width, height, THREE.RGBAFormat, THREE.FloatType ); texture.needsUpdate = true;
STEP 3
matrialやtextuerなどのデータを格納しておくオブジェクトを作成する。
第二引数はフラグメントシェーダを指定します。 第三引数にはテクスチャを指定します。
var velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, textuer );
GPUComputationRenderer内で以下の処理が実行され、設定した内容が保持される。
var material = new THREE.ShaderMaterial( { uniforms:{}, vertexShader: vs,//中身は↓に記載 fragmentShader: fragmentShaderVel } ); var variable = { name: "textureVelocity", initialValueTexture: textuer, material: material, dependencies: null, renderTargets: [], wrapS: null, wrapT: null, minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter }; //作成したデータをインスタンス内に保持する this.variables.push( variable );
◎vs
void main() { gl_Position = vec4( position, 1.0 ); }
STEP 4
velVar、posVarに設定されたフラグメントシェーダーそれぞれからSTEP 2で作成したテクスチャが参照できるようする為の前準備。 シェーダー間でテクスチャを参照できるようになる。
下記の場合は、velVarのフラグメントシェーダから、velVar、posVarのテクスチャを参照することができる。
gpuCompute.setVariableDependencies( velVar, [ velVar, posVar] );
velVarのフラグメントシェーダから、velVarのテクスチャの参照だけで良い場合は、下記のような定義になる。
gpuCompute.setVariableDependencies( velVar, [ velVar] );
STEP 5
オフスクリーンレンダリング用のWebGLRenderTargetを作成する。
「STEP 3」で空だったrenderTargets
の配列にWebGLRenderTargetのインスタンスを追加します。
gpuCompute.init()
GPUComputationRenderer内では、同じWebGLRenderTargetが2つ作成される。
variable.renderTargets[ 0 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter ); variable.renderTargets[ 1 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
また設定しているフラグメントシェーダーの頭に、テクスチャの読みこみ用の定義が追加される。
自分で用意したフラグメントシェーダーにuniform sampler2Dの定義がないのに、何故テクスチャが参照できるのだろうと疑問におもっていたがここでライブラリが自動で挿入してくれていた。
uniform sampler2D textureVelocity; uniform sampler2D texturePosition; //↓↓fragmentShaderVelに定義した処理がはいる↓↓
material(shaderMaterial)にも、uniformの定義が追加される。 この段階ではまだ、テクスチャの値はnull。
uniforms.textureVelocity = { value: null };
STEP 6
最後に毎フレーム毎に下記を実行して、参照用と格納用のテクスチャを切り替えていく。
gpuCompute.compute();
GPUComputationRenderer内では、STEP 5ではまだnullだったテクスチャデータに、 WebGLRenderTargetが挿入される。
サンプル
実際にサンプルを作りました。 このサンプルはオフスクリーンレンダリングは1つだけです。 内部的にはGPUComputationRendererがテクスチャを複製してくれるので2つです。
まとめ
GPUComputationRendererはGPGPUの面倒なところを代わりにやってくれるので、私のような初学者には大変助かるライブラリです。 だたし、中でなにをやっているかを正しく理解しないと、GPGPUとはなんなのかがわからなくなりそうです。
TouchDesignerで使いそうなデータ制御の基礎基礎メモ1
あくまで自分用ですが、使いそうだけど、すぐ忘れてしまいそうな基本的なデータの制御です。
一定の時間でカウントアップする
特定の処理をループしたい場合など、一定時間でカウントアップした値を作成したい場合はLFO CHOPを利用します。
正の整数だけ利用したい場合は、TypeをRampを選択します。
カウントアップはCount CHOPを利用します。 カウントアップの閾値はTrigger Thresholdを利用します。ここでは0.98を設定しています。
閾値を設定するとカウントアップされるようになりますが、どこかの値で0に戻したい場合は、LimitをLoop Min/Maxにします。 そして、Limit Maximumに最大値を設定します。
オーディオデータの制御
オーディをデータはそのままだと、使いづらい場合があります。 Audio spectrum CHOPを利用することで、オーディオデータを正の整数にすることができます。 また、analyze CHOPを利用すると、特定の値を取得することができます。 analyze CHOPのFunctionをMaximumにすることで最大値のみ取得することができます。
ランダムな値を作る
ランダムな値をつくるにはNoise CHOPを利用します。
Noise CHOPはcommonタブの、Time Slice
をONにしないと動きが発生しません。
THREE.jsでグネグネしたメタボールを作成する
こちらを目指して、グネグネしたメタボール(呼び方がメタボールであってるのかわかりません)を作ってみたいと思い挑戦してみました。 結果としては、実現することが出来ませんでした。。。
実際に作成できたのはこちらです。
難しい。
JS
global.THREE = require('three'); const glslify = require('glslify'); const vertexShader = glslify('./src/js/shaders/circle/vertexShader.vert'); const fragmentShader = glslify('./src/js/shaders/circle/fragmentShader.frag'); const clock = new THREE.Clock(); let time = 0.0; let windowWidth = window.innerWidth; let windowHeight = window.innerHeight; let dpr = window.devicePixelRatio; const app = { renderer: new THREE.WebGLRenderer(), scene: new THREE.Scene(), camera: new THREE.PerspectiveCamera(60, windowWidth / windowHeight, 0.1, 1000) }; const body = document.getElementsByTagName('body')[0]; app.renderer.setClearColor(new THREE.Color(0xffffff), 1.0); app.renderer.setPixelRatio(window.devicePixelRatio || 1); // canvasをbodyに追加 body.appendChild(app.renderer.domElement); // canvasをリサイズ app.renderer.setSize(windowWidth, windowHeight); //LIGHTS let light = new THREE.AmbientLight(0xffffff, 1.0); app.scene.add(light); app.camera.position.z = 1.5; // Geometory作成 let geometry = new THREE.IcosahedronGeometry(0.5, 4); // Material作成 let material = new THREE.ShaderMaterial({ uniforms: { 'time': { type: 'f', value: time }, 'resolution': { type: 'v2', value: new THREE.Vector2(windowWidth * dpr, windowHeight * dpr) } }, vertexShader: vertexShader, fragmentShader: fragmentShader, //wireframe:true }); let mesh = new THREE.Mesh(geometry, material); app.scene.add(mesh); render(); function render() { time = clock.getElapsedTime(); mesh.material.uniforms.time.value = time; app.renderer.render(app.scene,app.camera); requestAnimationFrame(render); }
頂点シェーダー
ノイズはglsl-noiseを利用させていただいています。
#pragma glslify: cnoise = require(glsl-noise/classic/3d) varying float noise; uniform float time; uniform vec2 resolution; //グネグネの振り幅 const float amplitude = 0.5; //グネグネのスピード const float speed = 0.25; void main() { noise = cnoise( vec3(normal * amplitude + time * speed)); vec3 p = position + normal * noise * 0.2; gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 ); }
フラグメントシェーダー
#ifdef GL_ES precision highp float; #endif uniform float time; void main(){ gl_FragColor = vec4(vUv, sin(time), 1.0); }
まとめ
やってみて感じたポイントとしては、以下2点でした。
- geometryにIcosahedronGeometry(正20面体)を利用すること
- 頂点の歪みは法線(normal)を利用すること
頂点シェーダーだけがんばればグネグネさせることができました。
球体同士がつながるところはどのようにしたらいいのかがわかりませんでした。
フラグメントシェーダーで複数の円をグルグル回転させる
メタボールをやってみたいので、 まずは、フラグメントシェーダーで、複数の円をグルグル回転させてみたいと思います。
#ifdef GL_ES precision highp float; #endif uniform float time; uniform vec2 resolution; #define XSpeed 1.50 #define YSpeed 1.50 #define size 0.1 #define count 10.0 const float PI = 3.1415926535897932384626433832795; void main( void ) { //座標を正規化 vec2 pos =(gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);; float c = 0.0; float rad = (PI*2.0) /count; for( float i = 1.0; i < count+1.0; ++i ) { //X軸の移動 float px = cos( time * XSpeed + (i * rad)) ; //Y軸の移動 float py = sin( time * YSpeed + (i* rad)); //circleの座標 vec2 circlePos = vec2( px , py ); //円のサイズを変更 float d = size / length(pos - circlePos); //円のボケ幅を調整 c += pow( d, 5.0 ); } gl_FragColor = vec4(vec3(c), 1.0 ); }
動作はこちらで確認できます。
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バイトで十分です。 あくまで、データ受け渡し用のテクスチャになります。