I have a kivy app where I want everything that is drawn on a particular RenderContext to have a shader post-processing effect applied to it (similar to the effects demonstrated in the EffectWidget example in the kivy-examples in the documentation).
In the constructor of the world
Widget I create the RenderContext
self.prc = RenderContext()
set its projection matrix (this works)
self.prc['projection_mat'] = proj_mat
and then try to set its fragment shader to a minimal copy of the default fragment shader that should just make everything one tenth as opaque (basically dimming the screen).
self.prc.shader.fs = """
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;
void main (void){
gl_FragColor = 0.1*frag_color * texture2D(texture0, tex_coord0);
}
"""
If the code for this shader is incorrect, the program doesn't run, complaining of a shader compile error, which indicates that the shader is being compiled. But, I don't see any effect of the shader. Everything rendered on to prc
is drawn, but at normal opacity. What am I doing wrong? Thanks for your time!
EDIT!
I have been asked to provide a complete runnable example. The following program draws two rectangles. The Rectangle on the left has its own RenderContext and is not affected by the grayscale postprocessing affect (it is drawn as Red). The Rectangle on the right does not have its own RenderContext and it is correctly affected by the postprocessing affect.
Here is the code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import *
from kivy.graphics.opengl import *
from kivy.graphics.shader import *
from kivy.core.window import Window
from kivy.graphics.transformation import Matrix
from kivy.logger import Logger
class World(Widget) :
def __init__(self, **kwargs):
Logger.debug('world.init()')
# Parent RenderContext for subsuming all other render contexts
self.prc=RenderContext()
proj_mat = Matrix()
proj_mat.look_at(0.,0.,1., # eye position coords
0.,0.,0., # looking at these coords
0,1.,0) # a vector that points up
if Window.height > Window.width :
self.xRadius = float(Window.width)/Window.height
self.yRadius = 1.0
proj_mat.scale(1.0/self.xRadius,1.0,1.0)
else :
self.xRadius = 1.0
self.yRadius = float(Window.height)/Window.width
proj_mat.scale(1.0,1.0/self.yRadius,1.0)
self.prc['projection_mat'] = proj_mat
## an effect shader used to make objects monochromatic (grayscale)
self.prc.shader.fs = """
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 vTexCoords0;
/* uniform texture samplers */
uniform sampler2D texture0;
uniform vec2 resolution;
uniform float time;
void main() {
vec4 rgb = texture2D(texture0, vTexCoords0);
float c = (rgb.x + rgb.y + rgb.z) * 0.3333;
gl_FragColor = vec4(c, c, c, 1.0);
}
"""
if not self.prc.shader.success :
raise Exception('Effect shader compile failed.')
self.canvas = self.prc
## Left Rectangle drawn with its own RenderContext
## this is not affected by the effect shader (if it were, it would be drawn as white)
## but it is affected by the parent's projection matrix
self.spriteRC = RenderContext(use_parent_projection=True)
self.spriteRC.add(Color(1,0,0,1))
self.spriteRC.add(Rectangle(pos=(-0.25,0.0),size=(0.1,0.1)))
## Right Rectangle object drawn directly to the canvas
## this **is** affected by the effect shader
self.canvas.add(Color(1,0,0,1))
self.canvas.add(Rectangle(pos=(0.25,0),size=(0.1,0.1)))
self.canvas.add(self.spriteRC)
super(World, self).__init__(**kwargs)
class GameApp(App):
def build(self):
w = World()
fl = FloatLayout()
fl.add_widget(w)
return fl
if __name__ == '__main__':
GameApp().run()
Shader cannot be stacked in the pipeline. Only the latest binded will be used, it's not a Kivy limitation, but that's how OpenGL works. Ie:
self.c1 = RenderContext()
self.c2 = RenderContext()
self.c2.add(Rectangle())
self.c1.add(self.c2)
The rectangle will be processed only by the latest shader, the one in c2.
In order to have a specific shader for the rectangle, and then process the output with the c1 shader as well, use Framebuffer (Fbo are a RenderContext subclass)!
self.c1 = RenderContext()
self.c2 = Fbo(size=...)
self.c2.add(Rectangle())
self.c1.add(self.c2) # this is just for triggering the render from c1 when c2 content changes
self.c1.add(Rectangle(size=self.c2.size, texture=self.c2.texture))
I missed all the Color and others parameters here, but it's just for the demonstration. You can change the shader on the Fbo the same way you do on RenderContext.