今回は次のキラキラシェーダーの作り方について解説します。
GLSLコーティングの初心者の方もイメージを掴めるように心がけます。
なお、サンプルはWebでGLSLを実行できるエディタ** The Book of Shaders Editor **で確認しました。
キラキラシェーダーの構成要素
キラキラシェーダーはパターン
、カラー
、アルファ
の3種類の要素があります。タイル状に十字を敷き詰めたパターンをアニメーションするカラー・アルファでブレンドすることで作っています。
構成要素 | イメージ |
---|---|
パターン | |
カラー | |
アルファ |
パターンの作成
ST座標
precision mediump float;
uniform vec2 u_resolution;
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy; // 正規化
gl_FragColor = vec4(st, 0., 1.);
}
gl_FragCoord
はピクセル座標で左下原点(0, 0)
で右上が画面サイズ(例えば(300, 300)
)となります。
st
はgl_FragCoord
を画面サイズu_resolution
で割ることで、左下原点(0, 0)
で右上(1, 1)
となるST座標へ変換します。
このサンプルでは座標を可視化するため、gl_FragColor
の赤と緑の値にXとYの値を渡して色を表示しています。なので、値が大きくなる程色がつきます。
例えば、座標(1, 1)
はRGB(1, 1, 0)
となり黄色になります。
タイル座標
...
float repeat_count = 30.; // 繰り返し回数
float aspect = .8; // 縦横比
vec2 scaled_st = repeat_count * vec2(1., aspect) * st; // スケーリング
vec2 difference_st = scaled_st + vec2(0, .3) * mod(floor(scaled_st).x, 5. ); // ずらす
vec2 tile = fract(difference_st); // 0 ~ 1 の間を繰り返す
gl_FragColor = vec4(tile, 0., 1.);
パターンは同じ形が繰り返し描画されるため、ST座標をタイル状に分割します。この例ではY軸方向に少しずらしてタイルの単調さを軽減しています。
scaled_st
で繰り返す回数分座標空間を大きくします。なお十字は縦長のため、アスペクト比をかけています。
difference_st
ではX座標に応じて、Y軸方向へ座標をずらしています。mod
は余剰のため、この例では0 ~ 4
を繰り返します。
fract
は小数点のみ取得するので、0 ~ 1
を繰り返すタイルパターンになります。
十字パターン
vec2 tile_center = abs(tile - .5); // 端 0.5 中心 0.
float circle = step(.6, 1. - length(tile_center)); // 円
float grid = min(1., step(.42, .5 - tile_center.x) + step(.42, .5 - tile_center.y / aspect)); // グリッド
float cross = circle * grid;
gl_FragColor = vec4(vec3(cross), 1.);
tile_center
はタイルの中心からの距離で、タイル中心が(0, 0)
になるように-0.5
しています。
step
は閾値で 0 か 1 に変換します。circle
はタイル中心からの距離を閾値とし円に型抜きします。grid
はX軸とY軸の各々で距離を閾値として格子状に型抜きします。circle
とgrid
のANDを取ることで十字ができます。
(菱形パターン)
vec2 tile_center = abs(tile - .5); // 端 0.5 中心 0.
float tile_dist = 1. - step(.3, tile_center.x + tile_center.y);
float diamond = step(1., tile_dist);
gl_FragColor = vec4(vec3(diamond), 1.);
ここの処理を変更することで色々な図形パターンを作ることができます。
カラーの作成
vec2 tile_pos = floor(difference_st);
vec3 shift = vec3(200.0 * (random(tile_pos * 2.) + u_time * 0.2), 1.0, 1.0);
vec3 random_col = shift_col(vec3(1.0, 0.3, 0.3), shift);
gl_FragColor = vec4(random_col, 1.);
tile_pos
はfloor
でST座標を整数化することでタイル位置を算出しています。tile_pos
をシード値として乱数を取得することで、タイル毎に色をランダムに決定します。またu_time
で時間により色が変化します。
random
は乱数取得、shift_col
は色変更の自作関数です。
highp float random(highp vec2 st){
return fract(sin(dot(st.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
vec3 shift_col(vec3 RGB, vec3 shift) {
vec3 RESULT = vec3(RGB);
highp float VSU = shift.z * shift.y * cos(shift.x * 3.14159265 / 180.0);
highp float VSW = shift.z * shift.y * sin(shift.x * 3.14159265 / 180.0);
RESULT.x = (.299 * shift.z + .701 * VSU + .168 * VSW) * RGB.x
+ (.587 * shift.z - .587 * VSU + .330 * VSW) * RGB.y
+ (.114 * shift.z - .114 * VSU - .497 * VSW) * RGB.z;
RESULT.y = (.299 * shift.z - .299 * VSU - .328 * VSW) * RGB.x
+ (.587 * shift.z + .413 * VSU + .035 * VSW) * RGB.y
+ (.114 * shift.z - .114 * VSU + .292 * VSW) * RGB.z;
RESULT.z = (.299 * shift.z - .3 * VSU + 1.25 * VSW) * RGB.x
+ (.587 * shift.z - .588 * VSU - 1.05 * VSW) * RGB.y
+ (.114 * shift.z + .886 * VSU - .203 * VSW) * RGB.z;
return (RESULT);
}
GLSLには乱数を取得する標準関数がないのでrandom
関数を自作します。Shaderでの乱数の取得は一般的に計算のオーバーフローを使ったやり方で取得するみたいです。
shift_col
は第一引数に色
、第二引数にシフト値
を指定します。シフト値は(色相、彩度、明度)となっており、色相は360で一周し、彩度と明度は倍率となっています。
アルファの作成
float cycle_time = 5.; // 光る周期時間
float on_rat = .6; // 全体に対して光る割合
float PI = 3.14159265359;
highp float animation_offset = random(tile_pos); // アニメーションの開始オフセット
float alpha = sin((animation(on_rat, cycle_time, u_time, animation_offset) - 0.25) * PI * 2.) * .5 + .5;
gl_FragColor = vec4(vec3(alpha), 1.);
sin
を使うことでイーズイン・アウトのアニメーション効果をつけてループ再生します。animation_offset
を乱数にすることで、タイル毎にバラバラに光るようにしています。
animation
は自作の関数です。
highp float animation(highp float animation_ratio, highp float cycle_time, highp float time, highp float offset){
highp float start_time = offset; // 0. ~ 1.
highp float end_time = start_time + animation_ratio;
highp float pos_in_cycle = mod(time, cycle_time) / cycle_time; // 0. -> 1.
highp float pos1 = smoothstep(start_time, end_time, pos_in_cycle);
highp float pos2 = smoothstep(start_time, end_time, pos_in_cycle + 1.); // 境界用
return max(fract(pos1), fract(pos2));
}
アニメーションの開始時間start_time
と終了時間end_time
は0~1+(a)に正規化しています。
時間time
と光る周期時間cycle_time
により、pos_in_cycle
サイクル中の進行位置を出しています。
アニメーション開始時間がサイクル終了よりの場合、end_time
が1以上になりアニメーションがサイクルを跨ぐ場合があります。そのパターンを考慮して、アニメーション進行位置をpos1
とpos2
の2つを使います。
以上、今回のサンプルで取り上げたキラキラシェーダーの解説でした。
元記事ではAndroidアプリでの活用例を紹介しています!
また、ソースコード等の詳細も元記事を参照ください。