「Heartfelt」を分析・KodeLifeで応用してみた。
分析しにくかったシェーダー効果でしたけど、なんとか分析して変数をuniform
で作成してKodeLifeで実装してみました。(なお、不必要なコードも削除)
CHEAP_NORMALS
は除きます。コメントでは2倍見にくくなるよと書いていたんですが、実際にやってみると3倍以上見にくくなります。もしかして水滴のぼかしを追加で行いたいならやっても良いかもしれませんね。- ハート模様の雨レンダリングの分析はしませんでした。
USE_POST_PROCESSING
の分析はします。- 元となるシェーダーは、「表示 - 非営利 - 継承 3.0 非移植」のライセンスによって営利的に利用してはなりません。ですから上のようなシェーダー効果を商用ゲームなどで出せるには一から作り直す必要があります。
- 率直に言うと、アルゴリズム自体はものすごく感動しましたけど、素のままじゃコードが見にくいです。
分析
1. mainImage
vec2 uv = (fragCoord.xy-.5*iResolution.xy) / iResolution.y; vec2 UV = fragCoord.xy/iResolution.xy; vec3 M = iMouse.xyz/iResolution.xyz; float T = iTime+M.x*2.; float t = T*.2; float rainAmount = iMouse.z>0. ? M.y : sin(T*.05)*.3+.7; float maxBlur = mix(3., 6., rainAmount); float minBlur = 2.; float zoom = -cos(T*.2); uv *= .7+zoom*.3; UV = (UV-.5)*(.9+zoom*.1)+.5;
uv
:雨の水滴をレンダリングする時に必要となるUV座標。ノイズマップからテクセルを参照してレンダリングする時に必要となる。uv
を引数として算出されるfocus
変数が最終のcol
を求める時に使用される以外には水滴のノイズマップによる値獲得が主な用途。
UV
:バックグラウンドとなるテクスチャのUV座標を示す。zoom
によって拡大か縮小される。M
:こっちでは分析しない。雨の水滴の時間の流れによる落ちる表現を巻き戻したりする時に使われるらしい。T
:時間変数。オリジナルコードではM
がオフセットとして作用して、調整できるようになっている。t
:T
を細く使うようにしたもの。
rainAmount
:雨の降る量を調整する変数。maxBlur
、minBlur
:水滴のぼかしを調整する。- バックグラウンドのテクスチャがそんなにはっきりじゃないと、あんまり差は出てないようだ。
maxBlur
とminBlur
の値によるレンダリング結果の差は応用で説明する。
zoom
:時間によってズームイン・ズームアウトをするようにする。
#define S(a, b, t) smoothstep(a, b, t) float staticDrops = S(-.5, 1., rainAmount)*2.; float layer1 = S(.25, .75, rainAmount); float layer2 = S(.0, .5, rainAmount);
smoothstep
を使用して3つの変数に値を算出している。glsl
とHLSL
でのsmoothstep
は以下の数式で実装されている。この式は下のグラフの図のような格好で0から1へと増加する。
staticDrops
:動的に生成されて落ちる水滴を除外した静的な水滴の生成量を示す。範囲は]layer1
:雨の動的水滴レイヤー1の生成量を示す。範囲は]。layer2
:雨の動的水滴レイヤー2の生成量を示す。範囲はレイヤー1と同じ。
vec2 c = Drops(uv, t, staticDrops, layer1, layer2); vec2 e = vec2(.001, 0.); float cx = Drops(uv+e, t, staticDrops, layer1, layer2).x; float cy = Drops(uv+e.yx, t, staticDrops, layer1, layer2).x; vec2 n = vec2(cx-c.x, cy-c.x); // expensive normal
c
:Drops
関数で、水滴を表現するのに必要な値をx, yとして獲得する。詳しくはDrops
関数で…- 重要なことは、
CHEAP_NORMALS
を使っても、そうじゃない場合にも水滴が表現される領域は同じだということである。ただし、n
がどんな値を持ち、テクスチャを参照して水滴表現をさせるかが方法によって違う。 c.x
:focus
を得る時のぼかしの程度。c.y
:ぼかしの最大値のオフセット値。
- 重要なことは、
e
:CHEAP_NORMALS
を使わない時に、現在のuv
から座標を微調整するためのエプシロンとして扱われる。この値が大きくなると、水滴に表現する(Transmitされて見える)部分の範囲が大きくなる。- でかくすると不自然に見えるので、出来るだけ範囲は]に収める方が良いと思う。
cx
、cy
:dfDx
、dfDy
の値を類似に得るための単位面積当たりの差異。これでn
を得る。n
:水滴の各表面から、uv
に足してレンダリングするオフセット座標を示す。ある水滴の左の部分は、uv
より右に移動してテクスチャを取り、右はその逆でカラーを取る。
float focus = mix(maxBlur-c.y, minBlur, S(.1, .2, c.x)); vec3 col = textureLod(iChannel0, UV+n, focus).rgb;
focus
:どれだけぼかしされたテクスチャのカラーを得るかを決めるための変数。これをLOD値として使える。col
を得る時に、UV + n
で、バックグラウンドテクスチャのちょっと移動した座標でのピクセルカラーを取る。
2. Drops
関数
vec2 Drops(vec2 uv, float t, float l0, float l1, float l2) { float s = StaticDrops(uv, t)* l0; vec2 m1 = DropLayer2(uv, t) * l1; vec2 m2 = DropLayer2(uv*1.85, t)*l2; float c = s+m1.x+m2.x; c = smoothstep(.3, 1., c); return vec2(c, max(m1.y*l0, m2.y*l1)); }
s
:落ちない水滴を表現する。ある地点で現る・消えるを反復するけど、これがないと水滴のパターンが見えてしまう。m1
:落ちる水滴の跡のWeight値をいう。この値によって、大きめの水滴の滴と落ちる時の跡が残れる。m2
:落ちる水滴の跡のWeight値をいう。m1
とはほぼ同じが、小さめの水滴を表現する。- 小さめの水滴を表現させるため、
DropLayer2
関数の第一引数に掛け算をしている。ノイズマップを参照するためのuv
がLeapすることによってサンプリング数値が下がり、小さめの水滴が表現出来るようになるようだ。
- 小さめの水滴を表現させるため、
c
:上の3つの水滴Weight値のx
値は、合計されて一つの水滴の領域として表現できる。現実で一つの水滴がとある一つの水滴に合うと、大きめの水滴になることを連想すればいいだろう。この値をリターン値のx
として扱う。- 一言で言えば、スクリーン上のピクセルの水滴のWeight値を示す。
3. StaticDrops
関数&DropLayer2
関数
StaticDrops
関数は水滴の表現値(Weight値)だけを返す。中身ではN13
という関数を使用して(類似ノイズ?)水滴の表現ノイズを決めるらしい。
DropLayer2
は落ちる水滴の滴(赤)、水跡(緑)の値を返す。N13以外にNoise、Saw関数を使う。- N13、Saw、ノイズ関数に関しては直接コードを見ること
5. ポストプロセッシング(USE_POST_PROCESSING
)
ポストプロセッシングでは3つのポストプロセッシングを実装している。
- 時間の流れによる雷効果
- 明暗のVignette効果&色調繊維(ColorShift)効果
- 明暗のフェード効果
共通となるコードは以下である。
t = (T+3.)*.5; // make time sync with first light noising float fade = S(0., 10., T); // fade in at the start
float colFade = sin(t*.2)*.5+.5+story; col *= mix(vec3(1.), vec3(.8, .9, 1.3), colFade); // subtle color shift
vec3(.8, .9, 1.3)
は変わる色調のベース色を示す。元コードではstory
という変数によってFade数値が変わるが、応用のコードではuBlueScreen
という、Weight値を別途に用意して調節するようにした。
float lightning = sin(t*sin(t*10.)); // lighting flicker lightning *= pow(max(0., sin(t+sin(t))), 10.); // lightning flash col *= 1.+lightning*fade*mix(1., .1, story*story); // composite lightning
lightning
:雷効果のためのWeight値を表す。t
(時間変数)によって値が下のような図のように変わる。そしてこの値が1.+lightning
で画面が光ったりする。
col *= 1.-dot(UV-=.5, UV); // vignette col *= fade; // composite start and end fade
- ビネットとフェード効果を実装する。
応用
KodeLifeで実装したときには、雨効果に関わる値はuniform
として取り出して調節するようにしました。
ですけど、ライセンスのため商用では使えませんね。 しかしこのシェーダーから細部的な効果らをなんとか参考にして新しい効果を作り出せると思います。
- ノイズマップの生成
- 雨の類似効果(最初から直接つくること)
- 雷の効果
- ビネット・色調調節効果