Three.jsで液体っぽいトランジションエフェクトを作った
DC谷口が書いたDigitalcubeとヘプタゴンのアドカレの記事です.
https://scrapbox.io/files/63a4bb5fff7498001edfbbbf.mov
クリックすると画像が切り替わる.
Three.jsというWebGLのラッパーライブラリで画像を切り替えるエフェクトを作りました.今回はフラグメントシェーダーが肝なのでそこをメインに解説していこうと思います.
WebGLとは
- JavaScriptでキャンバスにリアルタイムにCGを描画できるAPI.
- webサイトにインタラクティブなCGを埋め込んだり,UIの装飾をしたりできます.
- WebGLはブラウザに解釈されOpenGL ESというネイティブのグラフィクスAPIに変換されて実行される.ブラウザで動くけどネイティブ並みに高速!
- ネイティブで動いているAPIと使い方はほぼ同じ.webGLがわかればOpenGLもわかるようになる.webGLを学ぶモチベーションの一つはネイティブと遜色ないAPIを学んでCGの描画の原理を理解すること.
今回行った手順
1. 板状のポリゴンを用意
キャンバスいっぱいのサイズの板状のポリゴン(板状の3Dモデル)を用意して,キャンバス全体を覆います.このポリゴンのピクセルを操作してエフェクトを描画します.
2. 板状のポリゴンに貼り付ける画像などを用意してシェーダーに渡す
切り替えのエフェクトに使うノイズ画像と,切り替える2枚の画像をシェーダーに渡します.
https://scrapbox.io/files/634b66b4c32fd7001d34de1d.png
3. フラグメントシェーダーでテクスチャの色をピクセル単位で切り替える
フラグメントシェーダーでピクセルごとに色の切り替えを行います.この短いコードは通常のプログラムと異なり,ピクセルごとにそれぞれ実行されます.(処理するピクセルが100個あったら1フレームで100回実行される)ノイズ画像の明度の値とeffectParametorという時間の値を足して閾値以上になったらそのピクセルの色を切り替える処理を行なっています
code: shader.glsl
precision mediump float;
varying vec2 vUv;
uniform float time;
uniform float effectParametor;
uniform sampler2D uNoiseTexture;
uniform sampler2D uTexture0;
uniform sampler2D uTexture1;
void main() {
float noise = texture2D(uNoiseTexture, vUv).r;
vec4 pTexColor = texture2D(uTexture0, vUv);//切り替え前の画像
vec4 nTexColor = texture2D(uTexture1, vUv);//切り替え後の画像
if(noise + effectParametor >= 1.0){
gl_FragColor = nTexColor;
}else{
gl_FragColor = pTexColor;
}
}
フラグメントシェーダーとは
シェーダーとは,3次元コンピュータグラフィックスにおいて、シェーディング(陰影処理)を行うコンピュータプログラムのこです。3Dモデルはレンダリングパイプライン内でさまざまな処理を経てディスプレイに表示されます.シェーダーはその中で主に陰影の処理を担い,主にGPUで処理されます.
https://scrapbox.io/files/634b759efed4d7001da54ca4.png
(引用元:忘れてしまいました)
その中でも今回使うフラグメントシェーダー(ピクセルシェーダー)は描画するポリゴンの色を決める役割をしています.
https://scrapbox.io/files/634b76e689dacc001d204c67.png
フラグメントシェーダーはGPUでピクセルを高速に並列に処理します.左が同様の処理をCPUでfor文を回して同様の処理を実行したときのイメージを表現した絵です.一つひとつ処理を行なっていくため効率が悪いです.右がGPUで並列に処理されるイメージを表現した絵です.GPUは大量の計算を一気に並列に行うため大量のピクセルに対する処理を高速に行えます.
https://scrapbox.io/files/634b655e39c22400220dd294.pnghttps://scrapbox.io/files/634b655686c9dc002289a1df.png
フラグメントシェーダーの実行例
まだフラグメントシェーダーの挙動についてまだイメージしにくいと思うので実際にコードと出力結果の例を見ていこうと思います.下の例は先ほど説明した手順と同じくキャンバスを覆うように板ポリゴンを配置してそのいたポリゴンのピクセルの色をフラグメントシェーダーで操作しています.
.
例1:
この例では板状のポリゴンを単色で塗りつぶしています.
WebGLのフラグメントシェーダーではgl_FragColorという変数に代入された値がピクセルの色として出力さレます
gl_FragColorにはvec4という名前の四次元ベクトルの変数が代入されます.ベクトルのパラメーターがR, G, B, Aを表現しています.
code: sample1.glsl
precision mediump float;//これは小数点の精度を指定するおまじない
void main(){
gl_FragColor = vec4(1.0,0.0,1.0,1.0);//red, green, blue, alpha.
}
実行結果
実行した結果全てのピクセルがvec4(1.0,0.0,1.0,1.0)の色になりました.
このコードでは全てのピクセルに対して同じ色を出力するので全てのピクセルの色が同じになります.
https://scrapbox.io/files/634d5cac1802af001f02fccd.png
例2:
次の例ではvTexCoordという変数で,ピクセルの座標を取得して位置によって色を変えてみます.
code:sample2.glsl
precision mediump float;
varying vec2 vTexCoord;//テクスチャ座標
void main(){
gl_FragColor = vec4(vTexCoord.x,0.0,vTexCoord.y,1.0);
}
実行結果:
前回と異なり色の値が座標によって変わってグラデーションしています.
右の方がx座標が大きいので赤色が濃くなり,下の方がy座標が大きいので青色が濃くなっています.
このようにたった1行の変数代入だけでグラデーションが出来上がるのはフラグメントシェーダーがピクセルごとに処理されているからです.
https://scrapbox.io/files/634d5c9ae93c0c001fcccfc2.png
例3:
次はテクスチャを表示してみます.
sampler2D textureは平たくいうとテクスチャが入っている変数です.(厳密にいうと違う)
sampler2D型の変数から色を取得する場合は,texture2Dという関数にsampler2D型のtextureと座標を引数として入力して取得します.
code:sample3.glsl
precision mediump float;
varying vec2 vTexCoord;//テクスチャ座標
uniform sampler2D texture;//jsから渡されるテクスチャを指定する番号(テクスチャユニット番号).
void main(){
gl_FragColor = texture2D(texture,vTexCoord);//テクスチャユニット番号とテクスチャ座標からピクセルの色を読む
}
https://scrapbox.io/files/634d5c34db8a49001d1a0025.png
最後に今回使ったフラグメントシェーダーの処理をみていく
下のコードは作成した画像を切り替えるエフェクトのフラグメントシェーダーです..
code: shader.glsl
precision mediump float;
varying vec2 vUv;//vTexCoordと同じ.
uniform float effectParametor;
uniform sampler2D uNoiseTexture;
uniform sampler2D uTexture0;
uniform sampler2D uTexture1;
void main() {
float noise = texture2D(uNoiseTexture, vUv).r;
vec4 pTexColor = texture2D(uTexture0, vUv);//切り替え前の画像
vec4 nTexColor = texture2D(uTexture1, vUv);//切り替え後の画像
if(noise + effectParametor >= 1.0){
gl_FragColor = nTexColor;
}else{
gl_FragColor = pTexColor;
}
}
1. まずeffectParametorという変数で時間の情報を取得します。この変数はユニフォーム変数というもので値はjavascriptから渡されます.画像の切り替えが始まった瞬間は0で徐々に値が増えていき,終了時には1になります.
code:glsl
uniform float effectParametor;
2. 次に液体のようなエフェクトをつけるための用意したノイズ画像を扱う変数を用意します.このノイズはパーリンノイズという手法で生成されています.
code:glsl
uniform sampler2D uNoiseTexture; //エフェクトかけるのに使うノイズテクスチャ
https://scrapbox.io/files/63a4c4ae5bb72b001d8e5525.png
3. 切り替える2枚の画像を扱う変数を用意します.uTexture0とuTexture1に割り当てられる画像は切り替えのエフェクトの再生が終わった後,javaScriptによって切り替わります.
code:glsl
uniform sampler2D uTexture0; //現在のテクスチャ
uniform sampler2D uTexture1; //次のテクスチャ
3. 続いてmain関数の中に入ります.この行では該当するノイズ画像のピクセルの明度を取得しています.今回用意したノイズ画像は白黒の無彩色なのでr, g, bいずれかの値を取得すれば明度がわかります.今回はrの値をとって明度を取得しています.
code:glsl
float noise = texture2D(uNoiseTexture, vUv).r;
4. 切り替え前の画像と切り替え後の画像のピクセルの色を取得し,時間の値(effectPrametor)とnoise関数の明度(noise)の和が閾値を超えているかチェックして色を出し分けています.パーリンノイズの画像を閾値の判定に使っているためピクセルの座標によって色の切り替わるタイミングがばらつき特有のエフェクトになります.
code:glsl
vec4 pTexColor = texture2D(uTexture0, vUv);//切り替え前の画像
vec4 nTexColor = texture2D(uTexture1, vUv);//切り替え後の画像
if(noise + effectParametor >= 1.0){
gl_FragColor = nTexColor;
}else{
gl_FragColor = pTexColor;
}
出来上がったものがこちらです.
唐突ですがこの辺で締めます.読んでいただきありがとうございました!