かねてより興味があったGLSLに触る事ができたのでメモ。
Kivy上でShaderを扱う方法
https://github.com/kivy/kivy/tree/master/examples/shader に幾つかShaderを使ったSampleCodeがありますが、そのやり方よりもEffectWidgetを使ったやり方の方が簡単そうなのでこっちを選びました。
簡単な物を作ってみた
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行)。なので結果は以下のようになります。
もし緑成分のみを0にしたいならA行をreturn vec4(color.r, 0.0, color.ba);
とすればいいです。
もっと格好いい物を
その1
今度はWeb上に公開されている格好いいShaderを使ってみます。まずはこれを。(このShaderは本来はマウスカーソルの位置によってShaderの結果が変化するものですが、位置をShaderに伝える処理を私が書いていない為、そうはならないようになっています。)
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)
いつも思うのですが、たったこれだけのコード量でこんな綺麗なアニメーションが書けるGLSLは本当にすごいです。
その1 + PixelateEffect
その2
次はこれです。今度の物はマウスカーソルの位置をShaderに伝える処理を私が書いている為、それによってShaderの結果が変化します。
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)
その3
最後はこれです。
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)
最後に
Web上にあるShaderをEffectWidgetで使うためにはそれ用に書き換えないといけないのが面倒くさいです。冒頭で挙げた https://github.com/kivy/kivy/tree/master/examples/shader のやり方だとその必要がなさそうなので今度試そうと思います。