KivyでGLSL #1 EffectWidgetで遊ぶ

かねてより興味があったGLSLに触る事ができたのでメモ。

# Kivy上でShaderを扱う方法

https://github.com/kivy/kivy/tree/master/examples/shader に幾つかShaderを使ったSampleCodeがありますが、そのやり方よりもEffectWidgetを使ったやり方の方が簡単そうなのでこっちを選びました。

# 簡単な物を作ってみた

```python3

from kivy.factory import Factory
from kivy.app import runTouchApp
from kivy.uix.effectwidget import EffectWidget, EffectBase


GLSL_CODE = r'''
vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords)
{
    return vec4(color.rg, 0.0, color.a);  // A
}
'''

effect = EffectBase(glsl=GLSL_CODE)  # B
root = EffectWidget(effects=[effect, ])  # C
root.add_widget(Factory.Label(
    font_size=30,
    text='GLSL TEST'))

runTouchApp(root)
```

やり方は簡単で書いたGLSLのコードを引数にEffectBase(又はAdvancedEffectBase)のInstanceを作り(B行)、それをEffectWidgetのeffectsプロパティに与えるだけです(C行)。(effectsがlist型なのは複数のeffectを重ねがけできるようにする為)。GLSLのコードでやっている事はPixelの色成分の内、青以外はそのまま出力して青だけは0にして出力しています(A行)。なので結果は以下のようになります。

![](https://qiita-image-store.s3.amazonaws.com/0/144673/17f3afbc-9c98-9f14-8625-76c29ce945f3.png)

もし緑成分のみを0にしたいならA行を`return vec4(color.r, 0.0, color.ba);`とすればいいです。

# もっと格好いい物を

### その1

今度はWeb上に公開されている格好いいShaderを使ってみます。まずは[これ](http://glslsandbox.com/e#46234.0)を。(このShaderは本来はマウスカーソルの位置によってShaderの結果が変化するものですが、位置をShaderに伝える処理を私が書いていない為、そうはならないようになっています。)

```python3
from kivy.app import runTouchApp
from kivy.uix.effectwidget import EffectWidget, EffectBase


GLSL_CODE = r'''
uniform vec2 mouse;

vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords)
{
    vec2 r = resolution,
    o = coords.xy - r/2.;
    o = vec2(length(o) / r.y - .3, atan(o.y,o.x));
    vec4 s = .1*cos(1.6*vec4(0,1,2,3) + time + o.y + sin(o.x) * sin(mouse.x * time / 1. + 5.4)*2.),
    e = s.yzwx,
    f = min(o.x-s,e-o.x);
    return dot(clamp(f*r.y,0.,1.), 40.*(s-e)) * (s-.1) - f;
}
'''

effect = EffectBase(glsl=GLSL_CODE)
root = EffectWidget(effects=[effect, ])

runTouchApp(root)

```

![](https://qiita-image-store.s3.amazonaws.com/0/144673/e249645b-f3d5-d8fa-5f54-f07587179c33.png)

いつも思うのですが、たったこれだけのコード量でこんな綺麗なアニメーションが書けるGLSLは本当にすごいです。

### その1 + PixelateEffect

![](https://qiita-image-store.s3.amazonaws.com/0/144673/ee673859-f78f-fbee-3e4b-5f84739756c5.png)

### その2

次は[これ](http://glslsandbox.com/e#46258.0)です。今度の物はマウスカーソルの位置をShaderに伝える処理を私が書いている為、それによってShaderの結果が変化します。

```python3
from kivy.app import runTouchApp
from kivy.uix.effectwidget import EffectWidget, AdvancedEffectBase


GLSL_CODE = r'''
#define PI 3.14159265359

uniform vec2 mouse;

vec3 hsv2rgb(vec3 c) {
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords) {

    vec2 p = coords.xy / resolution.xy;
    vec2 d = normalize(mouse - p);
    float h = 0.5 * atan(d.y, d.x) / PI;
    vec3 c = hsv2rgb(vec3(h + time / PI, 1.0, 1.0));
    float m = smoothstep(0.4, 0.5, fract(10.0 * h)) + smoothstep(0.6, 0.5, fract(10.0 * h));
    return vec4(c, 1.0) * m;
}
'''

effect = AdvancedEffectBase(
    uniforms={'mouse': (0, 0, )},
    glsl=GLSL_CODE)
root = EffectWidget(effects=[effect, ])

# on_touch_moveイベントはマウスのボタンを押し下げている時にしか起きないので、Shaderに位置が伝わるのも当然その時だけ
root.bind(on_touch_move=lambda __, touch: effect.uniforms.__setitem__('mouse', touch.spos))

runTouchApp(root)
```
![](https://qiita-image-store.s3.amazonaws.com/0/144673/b795dd51-508c-b25c-35cb-aad433bc72b1.png)

### その3

最後は[これ](http://glslsandbox.com/e#46243.0)です。

```python3
from kivy.app import runTouchApp
from kivy.uix.effectwidget import EffectWidget, EffectBase


GLSL_CODE = r'''
#ifdef GL_ES
precision mediump float;
#endif

vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords) {
    vec4 return_value;
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    float dist = 0.;
    uv.x = -3.+4.*uv.x;
    uv.y = -1.+2.*uv.y;
    // comment the next line to see the fully zoomed out view
    uv *=pow(.1,4.+cos(.1*time));
    uv.x += .275015;//;
    uv.y += .0060445;//
    //uv /= 5.;
    vec4 col =vec4(1.);
    vec2 z = vec2(0.0);

    int trap=0;
    for(int i = 0; i < 400; i++){
        if(dot(z,z)>4.){trap = i;break;}
        dist = min( 1e20, dot(z,z))+cos(float(i)*12.+3.*time);
        z = mat2(z,-z.y,z.x)*z + uv;
    }
    dist = sqrt(dist);
    float orb = sqrt(float(trap))/64.;
    return_value=vec4(0.,log(dist)*sqrt(dist)-orb-orb,log(dist)*sqrt(dist-abs(sin(time))),1.);
    if(orb == 0.){return_value = vec4(0.);}
    return return_value;
}
'''

effect = EffectBase(glsl=GLSL_CODE)
root = EffectWidget(effects=[effect, ])
runTouchApp(root)
```

![](https://qiita-image-store.s3.amazonaws.com/0/144673/28144b22-9b96-6f7d-01b9-8e5ddfe1f070.png)


# 最後に

Web上にあるShaderをEffectWidgetで使うためにはそれ用に書き換えないといけないのが面倒くさいです。冒頭で挙げた https://github.com/kivy/kivy/tree/master/examples/shader のやり方だとその必要がなさそうなので今度試そうと思います。