glslでPhotoshopのアクアボタン作成チュートリアルを再現してみる

![circlebutton.png](https://qiita-image-store.s3.amazonaws.com/0/135381/7642eef9-f27a-7ce7-dd1f-cd042bcabb99.png)

# 概要

※ シャドウというよりは光彩なので、ユーティリティ関数名をshadowからglowに変更しました。中身は同じです。

以下のPhotoshopのアクアボタン作成チュートリアルをglslで再現します

* [Photoshopの練習３０：アクアボタンをつくる](https://www.leader-kenkyusho.com/web-service/index.php?QBlog-20140815-1)

* レイヤー構造
![layer.png](https://qiita-image-store.s3.amazonaws.com/0/135381/a3b4e2f9-9878-7166-7268-a846893b15d7.png)

https://goo.gl/g1pZG1

作り方はフォトショップのチュートリアルと全く同じです。
Photoshopができる人がglslでどうやって再現するのかの参考にして頂ければ幸いです。

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 ディスタンスフィールドを塗りつぶす
```glsl
float fill(float l,float d){return smoothstep(0.,0.01,d-l);}
```

## stroke ディスタンスフィールドの輪郭線を描く

※ 今回のチュートリアルでは使用していません。

![lineType.png](https://qiita-image-store.s3.amazonaws.com/0/135381/252110ae-bc30-1f88-3955-ee4fad3af9d6.png)


### 線(中央)
```glsl
float stroke(float l,float d,float w){return 1.-smoothstep(w*.5,w*.5+0.01,abs(d-l));}
```
### 線(内側)
```glsl
float strokeInner(float l,float d,float w){return stroke(l,d-w*.5,w);}
```

### 線(外側)
```glsl
float strokeOuter(float l,float d,float w){return stroke(l,d+w*.5,w);}
```

## glow ディスタンスフィールドをグラデーションで塗る

### 光彩
```glsl
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で良く使われているグロウはリアルグロウというような効果で、芯に近いほど濃く、芯に遠い程薄くどこまでも伸びていく効果です。後述

### 光彩(内側)
```glsl
float glowInner(float l,float d,float w){
  return glow(l,d,w)*smoothstep(0.,0.01,d-l);
}
```

### 光彩(外側)
```glsl
  float glowOuter(float l,float d,float w){
  return glow(l,d,w)*smoothstep(d-l,d-l+0.01,0.);
}
```

## リアルグロウ(どこまでも限りなく薄く光が伸びていくグロウです)

※ 今回のチュートリアルでは使用していません。

* 参考
 * [グロー表現のクオリティーを高める3つのポイント](http://compojigoku.blog.fc2.com/blog-entry-28.html)
 * [After Effectsで綺麗なグローをつくるテクニック](http://compojigoku.blog.fc2.com/blog-entry-3.html)

glslsandboxなど、画面全体で使う場合には、美しいグロウを簡単に作れて便利なのですが、ボタンなどのUIパーツで使う場合は扱いが難しいです。薄くどこまでも伸びていくというのが曲者で境界線が浮かび上がってしまいます。

### rglow(real glow)
```glsl
float rglow(float l,float d,float w){return  w*.5 / abs(l - d);}
```

### rglowInner(real glowInner)
```glsl
float rglowInner(float l,float d,float w){return saturate(w / -(l - d))+strokeInner(l,d,0.01);}
```

### rglowOuter(real glowOuter)
```glsl
float rglowOuter(float l,float d,float w){return saturate(w /  (l - d))+strokeOuter(l,d,0.01);}
```

# 描画モード(blend mode)

glslでの描画モードのおさらい
すべてのPhotoshopのブレンドモードはglslのライブラリ化されているので、他の描画モードを使いたい方は、[こちら](https://github.com/jamieowen/glsl-blend)を参考にしてみてください。ここでは、基本の3つの描画モードについて解説しておきます。

## 通常
```glsl
l=mix(l,l2,alphamask);
```

あんまり、解説されているのをみたことないですが、glslではmix関数がそれに該当します。

## 覆い焼き(加算)
```glsl
l+=alphamask;
```

本当に足すだけです。glslではエフェクト的な使い方が多いので、だいたいこちらが利用されてますね。

## 乗算
```glsl
l*=alphamask;
```

本当に掛けるだけです。

# グラデーション

基本的に、モノクロの場合線形だったらx成分やy成分そのもの、ディスタンスフィールドそのもの、カラーだったらmixのアルファ成分にそれを入れるだけです。

## 線形(linear)グラデーション
###　モノクロ
```glsl
l=p.x;
l=p.y;
```

###　カラー
```glsl
vec3 color = vec3(0.);
color=mix(color1,color2,p.x);
color=mix(color1,color2,p.y);
```

## 放射状(radial)グラデーション
###　モノクロ
```glsl
l=length(p);
```

###　カラー
```glsl
vec3 color = vec3(0.);
color=mix(color1,color2,length(p));
```

# 解説

## ボタンのベースを作る

```glsl
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);
}
```
![aqua1.png](https://qiita-image-store.s3.amazonaws.com/0/135381/cc825aa9-11ff-cd60-ac98-c7a09f574682.png)

https://goo.gl/KMkWqT

## 上から放射状グラデを掛ける

```glsl
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);
}
```
![aqua2.png](https://qiita-image-store.s3.amazonaws.com/0/135381/710a423b-2b5d-5aaa-4124-72d2b9ba008f.png)

https://goo.gl/a4J8hu

これは、元のベースの円に円のディスタンスフィールドの中心点をベースの円の上に移動したものを掛けるだけです。これが放射状グラデーションになります。

## ベースのボタンより小さい円を描きその円の中を今度は下から放射状にグラデーションを掛ける

```glsl
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);
}
```

![aqua3.png](https://qiita-image-store.s3.amazonaws.com/0/135381/8eb42296-4840-5a40-b47b-7448b7950543.png)

https://goo.gl/bt7Yjw

## ボタンの中央部分(青い部分)

今まではモノクロだったのですが、ここからは色を扱うのでvec3のcolor変数の中に入れます。

```glsl
	color = vec3(l);
```

先ほどよりも、少し小さい円を描き、その中を同じように、下から放射状にグラデーションを塗ります。但し、今度は水色から青へのグラデーションです。色を扱うようになって1行では収まりきらなくなったので、改行していますが、やっていることは1つ前の工程と同じです。

```glsl
	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)
	);
```
![aqua5.png](https://qiita-image-store.s3.amazonaws.com/0/135381/6bf9e357-f592-dfee-d909-a1f2d90b29b5.png)

https://goo.gl/ZRfjYB

## ボタンの中央部分(青い部分)にシャドウ(光彩 黒)を掛けます。

```glsl
	color = mix(color,vec3(0.),glowInner(length(p),.88,.08));
```
![aqua6.png](https://qiita-image-store.s3.amazonaws.com/0/135381/6e1b2c0f-ebc3-c8f9-4020-9e81c0303568.png)

https://goo.gl/9WC13x

## ボタン上部ハイライト
ボタン上部にハイライトを入れて完成です。
楕円を描き、線形グラデーションで通常合成します。

```glsl
	color = mix(color,vec3(1.),fill(length(p*vec2(.7,1.)+vec2(0.,-.48)),.4)*p.y);
```

![circlebutton.png](https://qiita-image-store.s3.amazonaws.com/0/135381/7642eef9-f27a-7ce7-dd1f-cd042bcabb99.png)

https://goo.gl/hK5Gvy

# コード全体

```glsl
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);
}
```

![circlebutton.png](https://qiita-image-store.s3.amazonaws.com/0/135381/7642eef9-f27a-7ce7-dd1f-cd042bcabb99.png)

https://goo.gl/hK5Gvy