以下是我认为是kivy中的错误的最小可运行示例。
在此程序中,场景被绘制到FBO。然后使用glsl着色器绘制场景,以应用后处理滤镜,使所有内容都为灰度。
当我的后处理小部件在ScreenManager中时,它只能正确地绘制少量(3)帧的场景。然后它什么都没画(BUG?)。如果它不在ScreenManager中,它会完美运行。
我已经将错误缩小到一行违规代码,这是我修改场景中绘制的非常简单的网格的顶点的地方。我最初几次修改网格它正常工作,但更重要的是,FBO是空白的。关于为什么会出现这种情况的任何想法?
在代码中有很多注释来引导读者,并且在导入之后有几个常量,可用于在最小屏幕管理器(有错误的地方)或在它,你可以看到该程序正常工作。
我试图让代码尽可能清晰和简洁。它仍然有点大。如果您有任何疑问,请询问!
from math import pi,cos,sin,atan2,ceil,sqrt
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.logger import Logger
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen
from kivy.resources import resource_find
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.graphics.transformation import Matrix
### You can configure this script here to demonstrate how the Widget
### hierarchy affects the bug, and how the number of times that the
### Sprite's mesh is updated influences the bug.
### One of the following two lines should be commented out.
WIDGET_SETUP = 'NO_SCREEN_MANAGER' ## NO BUG
#WIDGET_SETUP = 'INSIDE_SCREEN_MANAGER' ## BUG !!!
## I used the constant below to identify the number of frames
## correctly drawn, when in the INSIDE_SCREEN_MANAGER
## configuration. If the following constant is set above this critical
## threshold (>3 on my laptop), then the PostProcessor ceases to draw
## *anything*, including the Rectangles that are part of a separate
## RenderContext. If it is set low enough, then you see the first
## couple of frames drawn. And then the view stops being updated, but
## continues to be displayed correctly.
## A value of '-1' does not limit the number of frames updated.
NUMBER_OF_FRAMES_TO_UPDATE_MESH_VERTICES = -1
## CONFUSINGlY, decreasing the step size (amount of time between
## iterations) makes it so that even the first frame is not drawn.
## Why would this matter? It seems to me like something is happening
## at the end of the ScreenManagers transition to the screen?? This
## is just a wild guess..
DT = 0.2 ## DT = 0.01 makes not even the first frame be drawn.
class Sprite(object) :
def __init__(self,postProcessor) :
self.iteration = 0
## render context for applying a shader to the rendering of
## this sprite's mesh
self.spriteRC = RenderContext(use_parent_projection=False)
self.r = 0.1 # width of square to draw
self.a = 0.0
## uniform values for shader
self.spriteRC['modelview_mat'] = postProcessor.projectionMatrix
self.spriteRC['window_size'] = [float(Window.width),float(Window.height)]
self.spriteRC['color'] = [1.,1.,1.,1.]
## set up texture
self.uvsize = 1.,1.
self.uvpos = 0.,0.
## compile shader ## this shader gives the color according to
## the texture coordinates..
self.spriteRC.shader.vs = """
/* Outputs to the fragment shader */
varying vec2 tex_coord0;
varying vec4 frag_color;
/* vertex attributes */
attribute vec2 vPosition;
attribute vec2 vTexCoords0;
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
void main() {
tex_coord0 = vTexCoords0;
frag_color = vec4(0.,0.,0.,0.); // I don't understand why this line is necessary, but it is.
gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
}"""
self.spriteRC.shader.fs = """
/* Outputs from the vertex shader */
varying vec2 tex_coord0;
void main (void){
gl_FragColor = vec4(tex_coord0.x,tex_coord0.y,tex_coord0.x,1.0);
}"""
if not self.spriteRC.shader.success :
raise Exception('Effect shader compile failed.')
## set up mesh, and add it to the render context
self.mesh = Mesh(mode = 'triangles')
self.mesh.indices=range(6)
self.initializeVertices()
self.iterate(0.0)
self.spriteRC.add(self.mesh)
## add this sprite's render context to the fbo in the postprocessor
postProcessor.addSpriteRenderContext(self.spriteRC)
def initializeVertices(self) :
self.vs = [-self.r,-self.r, self.uvpos[0], self.uvpos[1],
-self.r,+self.r, self.uvpos[0], self.uvpos[1]+self.uvsize[1],
+self.r,-self.r, self.uvpos[0]+self.uvsize[0],self.uvpos[1],
+self.r,+self.r, self.uvpos[0]+self.uvsize[0],self.uvpos[1]+self.uvsize[1],
+self.r,-self.r, self.uvpos[0]+self.uvsize[0],self.uvpos[1],
-self.r,+self.r, self.uvpos[0], self.uvpos[1]+self.uvsize[1],
]
def updateVertices(self) :
""" Changes those parts of the mesh that need to be changed """
xr = cos(self.a)*self.r - sin(self.a)*self.r
yr = sin(self.a)*self.r + cos(self.a)*self.r
self.vs[0::4] = -xr,-yr,+yr,+xr,+yr,-yr
self.vs[1::4] = -yr,+xr,-xr,+yr,-xr,+xr
def iterate(self,dt) :
self.iteration += 1
self.a += 0.05
self.updateVertices()
if (NUMBER_OF_FRAMES_TO_UPDATE_MESH_VERTICES < 0 or
self.iteration < NUMBER_OF_FRAMES_TO_UPDATE_MESH_VERTICES) :
## the following line is what causes the post-processor's
## FBO to break, again only when 1. the post-processor is
## inside a ScreenManager, and after a certain number of
## frames have gone by (3-10 in my experience).
self.mesh.vertices=self.vs
class PostProcessor(Widget) :
def __init__(self, **kwargs):
Logger.debug('world.init()')
self.setupPostProcessorRenderContext()
## draw a colored rectangle on to the fbo. This is independent
## of the sprite RC and demonstrates that when the bug occurs,
## the FBO ceases to draw anything at all
self.rectangleRC = RenderContext(use_parent_projection=False)
self.rectangleRC['modelview_mat'] = self.projectionMatrix
self.rectangleRC.add(Color(0,1,0,0.5))
self.rectangleRC.add(Rectangle(pos=(0.0,0.0),size=(0.5,0.5)))
self.fbo.add(self.rectangleRC)
self.canvas = self.postProcessorRC
super(PostProcessor, self).__init__(**kwargs)
def setupPostProcessorRenderContext(self) :
"""This RenderContext is responsible ONLY for drawing the FBO (and
applying a postprocessing shader to it). Both the rectangleRC
and spriteRC draw directly to the FBO.
"""
self.postProcessorRC=RenderContext()
self.postProcessorRC.shader.vs = """
/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* vertex attributes */
attribute vec2 vPosition;
attribute vec2 vTexCoords0;
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
uniform vec4 color;
uniform float opacity;
void main (void) {
frag_color = color * vec4(1.0, 1.0, 1.0, opacity);
tex_coord0 = vTexCoords0;
gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
}"""
self.postProcessorRC.shader.fs = """
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;
uniform vec2 resolution;
uniform float time;
void main() {
vec4 rgb = texture2D(texture0, tex_coord0);
float c = (rgb.x + rgb.y + rgb.z) * 0.3333;
gl_FragColor = vec4(c,c,c, 1.0);
}"""
if not self.postProcessorRC.shader.success :
raise Exception('Effect shader compile failed.')
with self.postProcessorRC:
# create the fbo
self.fbo = Fbo(size=(float(Window.width), float(Window.height)))
# show our fbo on the PostProcessor Widget render context
## by drawing a rectangle covering most of screen, with
## the texture set to self.fbo
Color(1, 1, 1)
Rectangle(pos=(-0.9,-0.9),
size=(1.8,1.8),
texture=self.fbo.texture)
## the following linear algebra sets the screen up nicely, so
## that the largest window dimension (width or height) ranges
## between -1 and 1, and the smaller dimension ranges between
## smallerDimSize/largerDimSize. This makes a square e.g. 0,0;
## 0,1; 1,1; 1,0 render as a square (rather than a rectangle).
self.projectionMatrix = Matrix()
self.projectionMatrix.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
self.projectionMatrix.scale(1.0/self.xRadius,1.0,1.0)
else :
self.xRadius = 1.0
self.yRadius = float(Window.height)/Window.width
self.projectionMatrix.scale(1.0,1.0/self.yRadius,1.0)
def addSpriteRenderContext(self,spriteRC) :
""" Add the sprite's render context to the FBO."""
self.fbo.add(spriteRC)
def iterate(self,dt) :
""" Clear the FBO every iteration. """
self.fbo.bind()
self.fbo.clear_buffer()
self.fbo.release()
class TestApp(App):
def build(self):
## Initialise the post processor, which should make monochrome
## and draw all things that draw to its FBO.
w = PostProcessor()
## initialize the sprite which should draw a rotating texture
## to the FBO in the post-processor.
s = Sprite(w)
## update the sprite to have its vertices rotate
def iterate(dt):
w.iterate(dt)
s.iterate(dt)
Clock.schedule_interval(iterate, DT)
## create a FLoatLayout that contains the post-processor
fl = FloatLayout()
fl.add_widget(w)
## Widget Setup
if WIDGET_SETUP == 'INSIDE_SCREEN_MANAGER' :
sm = ScreenManager()
scr = Screen(name='Screen 1')
sm.add_widget(scr)
scr.add_widget(fl)
return sm
else :
return fl
if __name__ == '__main__':
TestApp().run()