2016年5月27日金曜日

Three.jsでオブジェクト単位でフィルタをかける

Three.jsではポストプロセスを使うとフィルタをかけることができるが、シーン全体に適用されてしまうので、オブジェクト単位でフィルタをかけたいときには使えない。そこで、GLSLを使用してオブジェクト単位でフィルタをかける。

GLSLはシェーダーをカスタマイズするための言語。ここでは詳しく触れない(詳しく説明できるだけの知識はない)ので、詳細はWebグラフィックをハックする(最終回)を参照。GLSLには「頂点シェーダー」と「フラグメントシェーダー」があり、それぞれ頂点座標の計算とピクセル単位の描画色の計算を行う。要は、頂点シェーダーでオブジェクトの形状を変化させ、フラグメントシェーダーでオブジェクトの色を変化させることができる。

今回やりたいことは、Three.jsのポストプロセスでフィルタをかけるでやったことを、オブジェクト単位でできるようにすることなので、変化させるのは色だけ。つまり、フラグメントシェーダーをいじってフィルタをかける。ぼかしフィルターとしてボックスフィルター使う。以下はその手順。

1.頂点シェーダーの記述
GLSLはHTMLに<script>~</script>で囲んで記述する。頂点座標を変化させない場合でも、頂点シェーダーの記述は必要。GLSLには組み込み変数があり、頂点シェーダーではgl_Position(頂点座標)という組み込み変数に値を入れる必要がある。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script id="vshader" type="x-shader/x-vertex">
// フラグメントシェーダーへ渡す変数
varying vec2 vUv
 
void main() {
// uv: UV座標(組み込み変数)
vUv = uv;
 
// 頂点位置(position)をモデル行列(modelViewMatrix)でワールド座標に変換
// positionは組み込み変数
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
 
// ビュー行列(projectionMatrix)でスクリーン座標に変換
gl_Position = projectionMatrix * mvPosition;
}
</script>
※座標変換をしているだけで、頂点位置が変化しているわけではない。

2.ボックスフィルター
ボックスフィルターとは、ざっくりとした言い方をすると、あるピクセルとその周辺値の平均値をあるピクセルの値とするフィルター。例えば、あるピクセルとそれに縦横斜めに隣接する8個のピクセルの計9個の値を足して9で割る。この計算をフラグメントシェーダーで行う。

3.フラグメントシェーダーの記述
フラグメントシェーダーではgl_FragColor(描画色)という組み込み変数に値を入れる必要がある。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script id="fshader" type="x-shader/x-fragment">
// テクスチャ
uniform sampler2D texture;
// 頂点シェーダーからわたされた変数(UV座標)
// UV座標は-1~1に正規化されている
varying vec2 vUv;
// ボックスフィルターで値を計算するときに、どの程度離れたUV座標の色との平均値をとるか
float ratio = 256.0;
 
void main() {
// texture2D: テクスチャからUV座標に対応する色を取得する
// textureは、後述するTHREE.ShaderMaterialで設定されたuniform変数
// あるピクセルとその縦横斜めの位置の値の合計を計算
vec3 color = vec3(0.0, 0.0, 0.0);
color += texture2D(texture, vUv).xyz;
color += texture2D(texture, vUv + vec2(-1.0/ratio, 0.0)).xyz;
color += texture2D(texture, vUv + vec2( 1.0/ratio, 0.0)).xyz;
color += texture2D(texture, vUv + vec2(0.0, -1.0/ratio)).xyz;
color += texture2D(texture, vUv + vec2(0.0,  1.0/ratio)).xyz;
color += texture2D(texture, vUv + vec2(-1.0/ratio, 1.0/ratio)).xyz;
color += texture2D(texture, vUv + vec2(1.0/ratio, 1.0/ratio)).xyz;
color += texture2D(texture, vUv + vec2(-1.0/ratio, -1.0/ratio)).xyz;
color += texture2D(texture, vUv + vec2(1.0/ratio, -1.0/ratio)).xyz;
 
// 平均値を求める
color = color / 9.0;
// 描画色を設定
gl_FragColor = vec4(color, 1.0);
}
</script>
本来なら画像の解像度に合わせてratioの値を調整したり、計算するピクセルが画像の端の場合にどの値で計算するかなどを考慮する必要があるが、ここでは省略。それでもそれなりに画像はぼけてみえる。

4.マテリアルの作成
GLSLを使用するにはマテリアルの作成にTHREE.ShaderMaterialを使う。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// テクスチャの作成
var texture = new THREE.TextureLoader().load( '../image/persimmon.jpg' );
 
// マテリアルの作成
// uniforms: シェーダーに渡す変数を指定(ここではテクスチャを指定)
// vertexShader: 頂点シェーダーを指定
// fragmentShader: フラグメントシェーダーを指定
var material = new THREE.ShaderMaterial( {
uniforms: {
texture:   { type: 't', value: texture }
},
vertexShader:   document.getElementById( 'vshader' ).textContent,
fragmentShader: document.getElementById( 'fshader' ).textContent
});

あとは通常のThree.jsの記述と同じ。

結果は以下の通り。

オリジナル

ボックスフィルター


0 件のコメント:

コメントを投稿