概要
※ シャドウというよりは光彩なので、ユーティリティ関数名をshadowからglowに変更しました。中身は同じです。
以下のPhotoshopのアクアボタン作成チュートリアルをglslで再現します
作り方はフォトショップのチュートリアルと全く同じです。
Photoshopができる人がglslでどうやって再現するのかの参考にして頂ければ幸いです。
glslでちゃんと描画系のファンクションを用意した事例がなかったので、
以下にお絵かき用関数を用意したので、ご利用ください。
お絵かき用に用意したファンクション
float fill(float l,float d){return smoothstep(0.,0.01,d-l);}
float stroke(float l,float d,float w){return 1.-smoothstep(w*.5,w*.5+0.01,abs(d-l));}
float glow(float l,float d,float w){float m = smoothstep(w,w+0.01,abs(d-l));return 1.-saturate(abs(d-l)*(d/w)*(1.-m)) - m;}
float glowInner(float l,float d,float w){return glow(l,d,w)*smoothstep(0.,0.01,d-l);}
float glowOuter(float l,float d,float w){return glow(l,d,w)*smoothstep(d-l,d-l+0.01,0.);}
float rglow(float l,float d,float w){return w*.5 / abs(l - d);}
float strokeInner(float l,float d,float w){return stroke(l,d-w*.5,w);}
float strokeOuter(float l,float d,float w){return stroke(l,d+w*.5,w);}
float rglowInner(float l,float d,float w){return saturate(w / -(l - d))+strokeInner(l,d,0.01);}
float rglowOuter(float l,float d,float w){return saturate(w / (l - d))+strokeOuter(l,d,0.01);}
fill ディスタンスフィールドを塗りつぶす
float fill(float l,float d){return smoothstep(0.,0.01,d-l);}
stroke ディスタンスフィールドの輪郭線を描く
※ 今回のチュートリアルでは使用していません。
線(中央)
float stroke(float l,float d,float w){return 1.-smoothstep(w*.5,w*.5+0.01,abs(d-l));}
線(内側)
float strokeInner(float l,float d,float w){return stroke(l,d-w*.5,w);}
線(外側)
float strokeOuter(float l,float d,float w){return stroke(l,d+w*.5,w);}
glow ディスタンスフィールドをグラデーションで塗る
光彩
float glow(float l,float d,float w){
float m = smoothstep(w,w+0.01,abs(d-l));
return 1.-saturate(abs(d-l)*(d/w)*(1.-m)) - m;
}
※ glslで良く使われているグロウではないです。こちらは一般的な光彩効果です。glslで良く使われているグロウはリアルグロウというような効果で、芯に近いほど濃く、芯に遠い程薄くどこまでも伸びていく効果です。後述
光彩(内側)
float glowInner(float l,float d,float w){
return glow(l,d,w)*smoothstep(0.,0.01,d-l);
}
光彩(外側)
float glowOuter(float l,float d,float w){
return glow(l,d,w)*smoothstep(d-l,d-l+0.01,0.);
}
リアルグロウ(どこまでも限りなく薄く光が伸びていくグロウです)
※ 今回のチュートリアルでは使用していません。
glslsandboxなど、画面全体で使う場合には、美しいグロウを簡単に作れて便利なのですが、ボタンなどのUIパーツで使う場合は扱いが難しいです。薄くどこまでも伸びていくというのが曲者で境界線が浮かび上がってしまいます。
rglow(real glow)
float rglow(float l,float d,float w){return w*.5 / abs(l - d);}
rglowInner(real glowInner)
float rglowInner(float l,float d,float w){return saturate(w / -(l - d))+strokeInner(l,d,0.01);}
rglowOuter(real glowOuter)
float rglowOuter(float l,float d,float w){return saturate(w / (l - d))+strokeOuter(l,d,0.01);}
描画モード(blend mode)
glslでの描画モードのおさらい
すべてのPhotoshopのブレンドモードはglslのライブラリ化されているので、他の描画モードを使いたい方は、こちらを参考にしてみてください。ここでは、基本の3つの描画モードについて解説しておきます。
通常
l=mix(l,l2,alphamask);
あんまり、解説されているのをみたことないですが、glslではmix関数がそれに該当します。
覆い焼き(加算)
l+=alphamask;
本当に足すだけです。glslではエフェクト的な使い方が多いので、だいたいこちらが利用されてますね。
乗算
l*=alphamask;
本当に掛けるだけです。
グラデーション
基本的に、モノクロの場合線形だったらx成分やy成分そのもの、ディスタンスフィールドそのもの、カラーだったらmixのアルファ成分にそれを入れるだけです。
線形(linear)グラデーション
モノクロ
l=p.x;
l=p.y;
カラー
vec3 color = vec3(0.);
color=mix(color1,color2,p.x);
color=mix(color1,color2,p.y);
放射状(radial)グラデーション
モノクロ
l=length(p);
カラー
vec3 color = vec3(0.);
color=mix(color1,color2,length(p));
解説
ボタンのベースを作る
void main(void){
vec3 color;
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l=0.;
l += fill(length(p),1.);
color = vec3(l);
gl_FragColor = vec4(color, 1.0);
}
上から放射状グラデを掛ける
void main(void){
vec3 color;
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l=0.;
l += fill(length(p),1.)*(1.2-length(p-vec2(0.,1.))*.5);
color = vec3(l);
gl_FragColor = vec4(color, 1.0);
}
これは、元のベースの円に円のディスタンスフィールドの中心点をベースの円の上に移動したものを掛けるだけです。これが放射状グラデーションになります。
ベースのボタンより小さい円を描きその円の中を今度は下から放射状にグラデーションを掛ける
void main(void){
vec3 color=vec3(0.);
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l=0.;
l += fill(length(p),1.)*(1.2-length(p-vec2(0.,1.))*.5);
l = mix(l,1.2-length(p-vec2(0.,-.9))*.5,fill(length(p),.9));
color = vec3(l);
gl_FragColor = vec4(color, 1.0);
}
ボタンの中央部分(青い部分)
今まではモノクロだったのですが、ここからは色を扱うのでvec3のcolor変数の中に入れます。
color = vec3(l);
先ほどよりも、少し小さい円を描き、その中を同じように、下から放射状にグラデーションを塗ります。但し、今度は水色から青へのグラデーションです。色を扱うようになって1行では収まりきらなくなったので、改行していますが、やっていることは1つ前の工程と同じです。
color = mix(
color,
mix(
vec3(0.1,0.3,1.),
vec3(0.4,0.8,1.),
1.3-length(p-vec2(0.,-.8))
)
,fill(length(p),.88)
);
ボタンの中央部分(青い部分)にシャドウ(光彩 黒)を掛けます。
color = mix(color,vec3(0.),glowInner(length(p),.88,.08));
ボタン上部ハイライト
ボタン上部にハイライトを入れて完成です。
楕円を描き、線形グラデーションで通常合成します。
color = mix(color,vec3(1.),fill(length(p*vec2(.7,1.)+vec2(0.,-.48)),.4)*p.y);
コード全体
precision mediump float;
uniform vec2 m; // mouse
uniform float t; // time
uniform vec2 r; // resolution
uniform sampler2D smp; // prev scene
#define saturate(x) clamp(x,0.,1.)
float fill(float l,float d){return smoothstep(0.,0.01,d-l);}
float stroke(float l,float d,float w){return 1.-smoothstep(w*.5,w*.5+0.01,abs(d-l));}
float glow(float l,float d,float w){float m = smoothstep(w,w+0.01,abs(d-l));return 1.-saturate(abs(d-l)*(d/w)*(1.-m)) - m;}
float glowInner(float l,float d,float w){return glow(l,d,w)*smoothstep(0.,0.01,d-l);}
float glowOuter(float l,float d,float w){return glow(l,d,w)*smoothstep(d-l,d-l+0.01,0.);}
float strokeInner(float l,float d,float w){return stroke(l,d-w*.5,w);}
float strokeOuter(float l,float d,float w){return stroke(l,d+w*.5,w);}
void main(void){
vec3 color=vec3(0.);
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l=0.;
l += fill(length(p),1.)*(1.2-length(p-vec2(0.,1.))*.5);
l = mix(l,1.2-length(p-vec2(0.,-.9))*.5,fill(length(p),.9));
color = vec3(l);
color = mix(
color,
mix(
vec3(0.1,0.3,1.),
vec3(0.4,0.8,1.),
1.3-length(p-vec2(0.,-.8))
)
,fill(length(p),.88)
);
color = mix(color,vec3(0.),glowInner(length(p),.88,.08));
color = mix(color,vec3(1.),fill(length(p*vec2(.7,1.)+vec2(0.,-.48)),.4)*p.y);
gl_FragColor = vec4(color, 1.0);
}