すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

oFでshader勉強中「3.サンプルを読む(GLSL編)」

今回はofのサンプルで使われているシェーダプログラムを読み解いていくよ.ちょこっと長くなってしまったけど、実際にサンプルを読むとglslの理解につながりそう.

ではまずprogram listから.

vertex shader

#version 120

uniform float timeValX = 1.0;
uniform float timeValY = 1.0;
uniform vec2 mouse;

//generate a random value from four points
vec4 rand(vec2 A,vec2 B,vec2 C,vec2 D){ 

	vec2 s=vec2(12.9898,78.233); 
	vec4 tmp=vec4(dot(A,s),dot(B,s),dot(C,s),dot(D,s)); 

	return fract(sin(tmp) * 43758.5453)* 2.0 - 1.0; 
} 

//this is similar to a perlin noise function
float noise(vec2 coord,float d){ 

	vec2 C[4]; 

	float d1 = 1.0/d;

	C[0]=floor(coord*d)*d1; 

	C[1]=C[0]+vec2(d1,0.0); 

	C[2]=C[0]+vec2(d1,d1); 

	C[3]=C[0]+vec2(0.0,d1);


	vec2 p=fract(coord*d); 

	vec2 q=1.0-p; 

	vec4 w=vec4(q.x*q.y,p.x*q.y,p.x*p.y,q.x*p.y); 

	return dot(vec4(rand(C[0],C[1],C[2],C[3])),w); 
} 


void main(){

	gl_TexCoord[0] = gl_MultiTexCoord0;
	
        // step:1
	//get our current vertex position so we can modify it
	vec4 pos = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
	
        // step:2
	//generate some noise values based on vertex position and the time value which comes in from our OF app
	float noiseAmntX = noise( vec2(-timeValX + pos.x / 1000.0f, 100.0f), 20.0 );
	float noiseAmntY = noise( vec2(timeValY + pos.y / 1000.0f, pos.x / 2000.0f), 20.0 );

	//generate noise for our blue pixel value
	float noiseB = noise( vec2(timeValY * 0.25, pos.y / 2000.0f), 20.0 );

        // step:3
	//lets also figure out the distance between the mouse and the vertex and apply a repelling force away from the mouse
	vec2 d = vec2(pos.x, pos.y) - mouse;
	float len =  sqrt(d.x*d.x + d.y*d.y);

        // step:4
	if( len < 300 && len > 0  ){
		

		//lets get the distance into 0-1 ranges
		float pct = len / 300.0; 
		
		//this turns our linear 0-1 value into a curved 0-1 value
		pct *= pct;

		//flip it so the closer we are the greater the repulsion
		pct = 1.0 - pct;
		
		//normalize our repulsion vector
		d /= len;
		
		//apply the repulsion to our position
		pos.x += d.x * pct * 90.0f;
		pos.y += d.y * pct * 90.0f;
	}

        // step:5
	//modify our position with the smooth noise
	pos.x += noiseAmntX * 20.0;
	pos.y += noiseAmntY * 10.0;
	
        // step:6
	//finally set the pos to be that actual position rendered
	gl_Position = pos;

        // step:7
	//modify our color
	vec4 col = gl_Color;
	col.b += noiseB;
	
	gl_FrontColor =  col;	
}

fragment shader

#version 120

void main(){
	//this is the fragment shader
	//this is where the pixel level drawing happens
	//gl_FragCoord gives us the x and y of the current pixel its drawing
	
        // step:1
	//we grab the x and y and store them in an int
	float xVal = gl_FragCoord.x;
	float yVal = gl_FragCoord.y;
	
        // step:2
	//we use the mod function to only draw pixels if they are every 2 in x or every 4 in y
	if( mod(xVal, 2.0) == 0.5 && mod(yVal, 4.0) == 0.5 ){
		gl_FragColor = gl_Color;    
    }else{
		gl_FragColor.a = 0.0;
	}	
}

サンプルのくせになかなか量がありますね.. とはいえ.個々の処理はそこまで複雑ではなさそう.まずfragment shaderはコード自体短い. いかにも入門のサンプルといった印象.またvertex shaderも、定義してる関数の(実装はともかく)内さえ理解できれば、なんとなくやってることはわかりそうだ.

まずはわかりやすそうなところからみていこう.

fragment shader

順序はぎゃくですが、読み解けるところからかんがえていきます.
さてfragment shaderですが、やってることは2ステップですね.

1.描画対象のピクセル値取得
いうことなし.gl_fragcoordからxVal,yValに代入.

2.飛び飛びに描画
mod関数を利用,x方向は2.0ごと、y方向は4.0ごとに一点描画を行う.gl_fragcolorの透明度をいじって描画オンオフしているようだ.
つかmodってこんな使い方できるのか.整数値のみだとおもってた.

うん.後半は単純だ.
それにしてもコメントが素晴らしいです.

vertex shader

uniformで外部から渡しているのは名前から察するに時間要素とマウス座標なのかな.ofからはこんな感じで値を登録するらしい.

shader.setUniform1f("timeValX", ofGetElapsedTimef() * 0.1 );
shader.setUniform1f("timeValY", -ofGetElapsedTimef() * 0.18 );
shader.setUniform2f("mouse", mouseX - ofGetWidth()/2, ofGetHeight()/2-mouseY );

コード前半で関数定義を行い、実際の処理は後半のmain内部に書いてある.

今回定義してる関数は
・random
・noise(パーリンノイズらしい)
のふたつのようだ.なんとなく機能はわかるので中の処理はブラックボックスとしておく.sinの引数がvec4ってどういうことだ?とか気になるけど,使えればまぁよしとする.

本題:mainを自分なりにstepに分けてソース中にコメントを付した.順にみていく.

1. 空間座標値取得.
gl_Vertexに入っている対象頂点の情報にプロジェクション行列とモデルビュー行列を適用し,カメラからの主観座標に変換している.

2. X/Y方向ノイズ・ブラーノイズ生成.
前半で定義したノイズ関数によるパーリンノイズ生成.

3. 対象座標とマウス位置との距離計算.
mouseにはどんな値が入っているのだっけ.oF側コードをもう一度見てみる.

shader.setUniform2f("mouse", mouseX - ofGetWidth()/2, ofGetHeight()/2-mouseY );

どうやら画面中心を(0,0)として,マウスポインタのx,y座標が格納されているようだ.1で計算した座標のx,y座標成分との差分をvec2 d に格納し,長さ計算はsqrtで行っている.
dは注目座標からマウス座標へのベクトルであることを覚えておこう.

4. 一定距離内での座標変換
ここが今回のshaderのキモのよう.通常は一定間隔で並んでいる座標を,ある点を中心にゆがませている.ガンマ曲線に近いかな.mapを変えるような感じ.ちょっと詳しく見てみよう.

//lets get the distance into 0-1 ranges
float pct = len / 300.0; 

まずはベクトルdの長lenを正規化している.この値をpct (percentかな?)とする.

//this turns our linear 0-1 value into a curved 0-1 value
pct *= pct;

次にpctを二乗する.正規化されているので、すべてもとより小さくなっているはず.注目座標とマウス座標が近いほどこの値は小さくなることに注目

//flip it so the closer we are the greater the repulsion
pct = 1.0 - pct;

そして1からpctの二乗を引く.さきほどの通り、注目点とマウスが近いほどpct二乗は小さくなる.よってこの引き算結果は二つが近いほど大きくなる.

//normalize our repulsion vector
d /= len;


思い出したかのようにベクトルdを正規化する.注目座標からマウス座標への方向ベクトルになるね.

//apply the repulsion to our position
pos.x += d.x * pct * 90.0f;
pos.y += d.y * pct * 90.0f;

posを計算された方向ベクトルのほうに,ずらす.
この際マウスに近い点ほどpctが大きくなり,大きくずれることになる.
90.0fに意味はあまりない.この値が大きいほどゆがみが大きくなるだけ.

5. ノイズ付与
4.の計算により,マウスポインタを中心にしてズームされたような表現ができる.ただこのままだとマウス動かさない限り出力はかわらない.
そこで各頂点をパーリンノイズを使ってちょこっとずらしている.今回ノイズ成分は時間によって変わるから、結果としてうねうねした動きが表現できるようだ.うまくできてんなぁ.

6. 頂点情報出力
あれやこれやでinputされた本来の座標値から新しい座標値が生成された.てことでこれをgl_positionにセット!

7. 色情報設定
fragment shaderで使われる色情報として、与えられた色にノイズ加えて流し込むだけ...だと思ってたけど、fragmentで呼ぶ変数はgl_color、一方vertexで代入している変数はgl_frontcolorとなっている.しかもそもそもの呼び出している変数がgl_colorで..??あれ??

混乱してきた.

ぐぐった.こんなんでてきた.

gl_Color means different things in different places.

なんですと!
もう少し読んでみる.

It is often useful to have different per-vertex output values based on the particular facing of the triangle. The primary color has a front color and a back color, representing the color for front-facing triangles and back-facing triangles. The vertex shader outputs for these are gl_FrontColor and gl_BackColor.

なるほど!
面に対し表の色と裏の色を設定できるのね.でカリング有効時は描画する画素が表か裏かによって、fragmentにおけるgl_colorの中身がかわるのか.納得だ.

で今回の場合は裏の色は指定せず、表の色だけ指定したということね.

以上のステップを経て、
空間中のどこの頂点がどんな色か.という情報をfragment shaderに流し込むわけね.

そして画面上の各画素について、物体のどこが見えているかを判別、各種頂点情報から色を決定していると.

まとめ

最初全くわからなかったけれども、やってみりゃ読み解けるもんですね.次はこのサンプルをちょっといじって自分なりのエフェクトを作ってみようと思う. 3DCGのエフェクト定石は何で学べばよいのやら.