Kivy - OpenGL



The Kivy framework is equipped with powerful graphics capabilities built on top of OpenGL and SDL instructions. Kivy uses OpenGL ES 2 graphics library, and is based on Vertex Buffer Object and shaders. The "kivy.graphics.opengl" module is a Python wrapper around OpenGL commands.

A shader is a user-defined program designed to run on some stage of a graphics processor. Shaders are written in OpenGL Shading Language (GLSL), which is a high-level shading language with a syntax based on the C programming language.

The two commonly used shaders to create graphics on the web are Vertex Shaders and Fragment (Pixel) Shaders.

  • Vertex shaders − They take the input from the previous pipeline stage (e.g. vertex positions, colors, and rasterized pixels) and customize the output to the next stage.

  • Fragment shaders − They take 2D position of all pixels as input and customize the output color of each pixel.

A detailed discussion on the features and syntax of GLSL is beyond the scope of this tutorial. We shall use an open-source shader file (kaleidoscope.glsl) in this chapter.

#ifdef GL_ES
precision highp float;
#endif

uniform vec2 resolution;
uniform float time;
uniform sampler2D tex0;
uniform sampler2D tex1;

void main(void){
   vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
   vec2 uv;
   float a = atan(p.y,p.x);
   float r = sqrt(dot(p,p));
   
   uv.x = 7.0*a/3.1416;
   uv.y = -time+ sin(7.0*r+time) + .7*cos(time+7.0*a);
   float w = .5+.5*(sin(time+7.0*r)+ .7*cos(time+7.0*a));
   vec3 col = texture2D(tex0,uv*.5).xyz;
   gl_FragColor = vec4(col*w,1.0);
}

In our Kivy application code, we use a .kv file that simply draws a rectangle on the canvas of a FloatLayout widget.

<ShaderWidget>:
   canvas:
      Color:
         rgb: 1, 0, 0
   Rectangle:
      pos: self.pos
      size: self.size

The ShaderWidget rule of this "kv" file corresponds to the ShaderWidget class. It schedules a clock interval to be fired after each second to update the glsl variables in the shader definition.

class ShaderWidget(FloatLayout):
   fs = StringProperty(None)
   def __init__(self, **kwargs):
      self.canvas = RenderContext()
      super(ShaderWidget, self).__init__(**kwargs)
      Clock.schedule_interval(self.update_glsl, 1 / 60.)

The "fs" class variable stores the code from glsl file. The RenderContext() method from kivy.graphics module stores all the necessary information for drawing, i.e., the vertex shader and the fragment shader, etc.

The "fs" StringProperty is bound to the "on_fs()" method which will set the shader.

def on_fs(self, instance, value):
   shader = self.canvas.shader
   old_value = shader.fs
   shader.fs = value
   if not shader.success:
      shader.fs = old_value
      raise Exception('failed')

The update_glsl() method will be called each time the scheduled clock event occurs. It basically updates the fragment color of each pixel on the application window.

def update_glsl(self, *largs):
   self.canvas['time'] = Clock.get_boottime()
   self.canvas['resolution'] = list(map(float, self.size))
   win_rc = Window.render_context
   self.canvas['projection_mat'] = win_rc['projection_mat']
   self.canvas['modelview_mat'] = win_rc['modelview_mat']
   self.canvas['frag_modelview_mat'] = win_rc['frag_modelview_mat']

The App class simple loads the ShaderWidget from the kv file and class definition.

Example

The complete code is given below −

from kivy.clock import Clock
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
from kivy.graphics import RenderContext
from kivy.properties import StringProperty
from kivy.core.window import Window

Window.size=(720,400)
file=open('kaleidoscope.glsl')
shader=file.read()

class ShaderWidget(FloatLayout):
   fs = StringProperty(None)

   def __init__(self, **kwargs):
      self.canvas = RenderContext()
      super(ShaderWidget, self).__init__(**kwargs)
      Clock.schedule_interval(self.update_glsl, 1 / 60.)

   def on_fs(self, instance, value):
      shader = self.canvas.shader
      old_value = shader.fs
      shader.fs = value
      if not shader.success:
         shader.fs = old_value
         raise Exception('failed')

   def update_glsl(self, *largs):
      self.canvas['time'] = Clock.get_boottime()
      self.canvas['resolution'] = list(map(float, self.size)).
      win_rc = Window.render_context
      self.canvas['projection_mat'] = win_rc['projection_mat']
      self.canvas['modelview_mat'] = win_rc['modelview_mat']
      self.canvas['frag_modelview_mat'] = win_rc['frag_modelview_mat']

class 'kaleidoscopeApp(App):
   title='kaleidoscope'
   def build(self):
      return ShaderWidget(fs=shader)

'kaleidoscopeApp().run()

Output

Run this code and check the output −

kivy openGl
Advertisements