のぐそんブログ

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

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オペレーターで確認することができる。

f:id:nogson2:20180410233926p:plain

まとめ

最終的にはこんな感じになります。 基礎的な部分ですがよく使う方法らしいので、覚えておくようにしたいと思います。

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つです。

GPGPU SAMPLE

まとめ

GPUComputationRendererはGPGPUの面倒なところを代わりにやってくれるので、私のような初学者には大変助かるライブラリです。 だたし、中でなにをやっているかを正しく理解しないと、GPGPUとはなんなのかがわからなくなりそうです。

TouchDesignerで使いそうなデータ制御の基礎基礎メモ1

あくまで自分用ですが、使いそうだけど、すぐ忘れてしまいそうな基本的なデータの制御です。

一定の時間でカウントアップする

特定の処理をループしたい場合など、一定時間でカウントアップした値を作成したい場合はLFO CHOPを利用します。

正の整数だけ利用したい場合は、TypeをRampを選択します。

参考:LFO Saw Wave ?

カウントアップはCount CHOPを利用します。 カウントアップの閾値Trigger Thresholdを利用します。ここでは0.98を設定しています。

閾値を設定するとカウントアップされるようになりますが、どこかの値で0に戻したい場合は、LimitLoop Min/Maxにします。 そして、Limit Maximumに最大値を設定します。

オーディオデータの制御

オーディをデータはそのままだと、使いづらい場合があります。 Audio spectrum CHOPを利用することで、オーディオデータを正の整数にすることができます。 また、analyze CHOPを利用すると、特定の値を取得することができます。 analyze CHOPのFunctionをMaximumにすることで最大値のみ取得することができます。

ランダムな値を作る

ランダムな値をつくるにはNoise CHOPを利用します。

10月-14-2017 19-03-57.gif

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バイトで十分です。 あくまで、データ受け渡し用のテクスチャになります。