在Kivy中,更改绘制到FBO的网格顶点会在FBO位于屏幕管理中时破坏FBO

2024-10-03 04:35:08 发布

您现在位置:Python中文网/ 问答频道 /正文

下面是我认为kivy中的一个bug的最小可运行示例。在

在这个程序中,一个场景被画到一个固定基地组织。然后使用glsl着色器绘制场景,以应用使所有内容变为灰度的后处理过滤器。在

当我的后处理小部件在屏幕管理器中时,它只正确地绘制了少量(3)帧的场景。然后它什么也没画出来(BUG?)。如果它不在屏幕管理器中,它运行得非常完美。在

我已经把错误缩小到一行有问题的代码,这是我修改在场景中绘制的一个非常简单的网格顶点的地方。前几次我修改了网格,它工作正常,但除此之外,FBO是空白的。你知道为什么会这样吗?在

代码内部有许多注释来指导读者,在导入之后是几个常量,它们可以用来在程序在最小屏幕管理器中运行(如果存在bug)或在外部进行切换,在那里可以看到程序正常工作。在

我试着让代码尽可能的清晰和简洁。还是有点大。如果你有问题请问!在

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()

Tags: ofthetofromimportselfisuniform