BLOG

Display HSB(HSV) Scalar with ofxFlowTools

ofxFlowToolsは流体映像を生成するopenFrameworksの強力なアドオンだ。様々なパラメータを調整することで、粘りのある流体から墨汁のようなものまでかなり幅広い表現を実現することができる。

私もダンスとコラボした作品(下映像)の中でofxFlowToolsを利用したことがある。

今回、私が制作しているWaving Tentacles(通称イソギンチャクロボット)の触手の屈曲制御にofxFlowToolsを利用することを試みた。当初、ofxFlowTools(OF0.9.0用)のサンプルプロジジェクトのデモモード5「Flow Velocity」のカラー値(下図)を取得すれば、回転角度として利用できると考えたのだが、実験してみるとどうも取得した値がおかしい。実際下図の色もおかしいのだが、最初は気づかなかった。

そこで、ソースのftDisplayScalarShader.hを分析してみると46行目以降の下記コードがScalarを表示している部分だとわかった。

fragmentShader = GLSL150(
	  uniform sampler2DRect FloatTexture;
	  uniform float Scale;
	  					  
	  in vec2 texCoordVarying;
	  out vec4 fragColor;
		  
	  void main(){
		  vec2 st = texCoordVarying;
		  vec4	velocity = texture(FloatTexture, st);
		  velocity.xyz *= vec3(Scale);
		  velocity.w = pow(length(velocity.xyz), 0.33);
		  velocity.xyz += vec3(0.5);
		  fragColor = velocity;
		  }
  );

velocityは、もとをたどるとサンプルプロジェクトのofApp.cppの中でfluidSimulation.getVelocity()を処理してofTexture型のデータを座標変換したもので、さらにもとをたどっていくとvelocitySwapBufferというGL_RG32Fでallocateされたものにいきつく。つまり、velocityは2つの32bit浮動小数点データを保持したものであり、GLSL内でvec4型となっているが入力時点ではzwは利用しておらず、xyのみのベクトルデータだということだ。そして、上記のコードでvelocity.xyz += vec3(0.5);がScalar表示に使われいる部分なのだが、これは角度を算出する計算式ではなく、あくまで擬似的なカラー表示を実現しているだけだった。

そこで、正確なHSBカラー表示を行えるようコードを改造してみた。ベクトルのなす角度の計算は「2つのベクトルのなす角度を求める」を参考にした。HSBからRGBへの変換では「GLSL Shader – Change Hue/Saturation/Brightness」の後半に紹介されているコードを利用した。

fragmentShader = GLSL150(
	  uniform sampler2DRect FloatTexture;
	  uniform float Scale;
                                   
	  in vec2 texCoordVarying;
	  out vec4 fragColor;
								  
	  void main(){
		vec2 st = texCoordVarying;
		vec4	velocity = texture(FloatTexture, st);
		velocity.xyz *= vec3(Scale);
		velocity.w = pow(length(velocity.xyz), 0.33);
                                      
		////////////
		// HSB変換
		////////////                              
		//velocity.xyベクトルと原点から(1, 0)のベクトルの角度を計算
		//cosθ = ( AとBの内積 ) / (Aの長さ * Bの長さ)
		float nakaCos = velocity.x/pow((velocity.x*velocity.x)+(velocity.y*velocity.y), 0.5);
		//acosで角度(ラジアン)計算 πで割ることで正規化
		float nakaAngle = acos(nakaCos)/3.14159265358979;
                                      
		//nakaAbgleは0〜180度の範囲なので、Y座標がプラスのときとマイナスのときで場合分け
		if(velocity.y>0)
			nakaAngle = nakaAngle/2.0;
		else
			nakaAngle = 1.0-nakaAngle/2.0;
                                      
		//Saturationは1.0で固定 Brightnessはvelocity.w
		vec3 nakaHSB = vec3(nakaAngle, 1.0, velocity.w);

		//HSBからRGBへ変換
		vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
		vec3 p = abs(fract(nakaHSB.xxx + K.xyz) * 6.0 - K.www);
		vec3 nakaRGB = nakaHSB.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), nakaHSB.y);
                                      
		fragColor = vec4(nakaRGB, 1.0);
		}
);

この改造後の表示が下図。きれいにカラーホイールが表示できているのがわかる。GLSLは勉強中なのでもっと違う方法あるかもしれないが、参考まで。ちなみにWaving Tentacles 8×8でも、まだこの方法は実現しておらず、普通のOptical Flowを使っている。次のバージョンで実装する予定。