Commits

Thomas Perl committed 076bd25

OpenGL Renderer: Implement post-processing and shader effects

Comments (0)

Files changed (3)

src/engine/renderer_blit.py

     def begin(self):
         self.app.screen.display.fill((0, 0, 0))
 
+    def begin_overlay(self):
+        pass
+
     def draw(self, sprite, pos, scale=None, opacity=1., tint=None):
         # Opacity is ignored in this blitting renderer
         # Tint is also ignored in this blitting renderer

src/engine/renderer_opengl.py

 import pygame
 from pygame import transform
 
+import time
+import math
 import random
 import array
 
         # Forward normal attribute requests to the sprite itself
         return getattr(self._sprite, name)
 
+def build_shader(typ, source):
+    shader_id = glCreateShader(typ)
+    glShaderSource(shader_id, source)
+    glCompileShader(shader_id)
+    #print 'Shader Info Log:', glGetShaderInfoLog(shader_id)
+    return shader_id
+
+class ShaderEffect:
+    def __init__(self, vertex_shader, fragment_shader):
+        self.vertex_shader = build_shader(GL_VERTEX_SHADER, vertex_shader)
+        self.fragment_shader = build_shader(GL_FRAGMENT_SHADER, fragment_shader)
+        self.program = glCreateProgram()
+        glAttachShader(self.program, self.vertex_shader)
+        glAttachShader(self.program, self.fragment_shader)
+        glLinkProgram(self.program)
+        #print 'Program Info Log:', glGetProgramInfoLog(self.program)
+
+    def use(self):
+        glUseProgram(self.program)
+
+    def attrib(self, name):
+        return glGetAttribLocation(self.program, name)
+
+    def uniform(self, name):
+        return glGetUniformLocation(self.program, name)
+
+
 class Framebuffer:
     def __init__(self, width, height):
+        self.started = time.time()
         self.width = width
         self.height = height
         self.texture_id = glGenTextures(1)
     def unbind(self):
         glBindFramebuffer(GL_FRAMEBUFFER, 0)
 
-    def rerender(self):
+    def rerender(self, effect=None):
         # render self.texture_id as full screen quad
         texcoords = array.array('f', [
             0, 0,
             1, 1, 0,
         ])
         glColor4f(1., 1., 1., 1.)
+
         glBindTexture(GL_TEXTURE_2D, self.texture_id)
-        glVertexPointer(3, GL_FLOAT, 0, vtxcoords.tostring())
-        glTexCoordPointer(2, GL_FLOAT, 0, texcoords.tostring())
-        glMatrixMode(GL_PROJECTION)
-        glPushMatrix()
-        glLoadIdentity()
-        glMatrixMode(GL_MODELVIEW)
-        glPushMatrix()
-        glLoadIdentity()
+        if effect is None:
+            glVertexPointer(3, GL_FLOAT, 0, vtxcoords.tostring())
+            glTexCoordPointer(2, GL_FLOAT, 0, texcoords.tostring())
+            glMatrixMode(GL_PROJECTION)
+            glPushMatrix()
+            glLoadIdentity()
+            glMatrixMode(GL_MODELVIEW)
+            glPushMatrix()
+            glLoadIdentity()
+        else:
+            vtxcoords_s = vtxcoords.tostring()
+            pos = effect.attrib('position')
+            glEnableVertexAttribArray(pos)
+            glVertexAttribPointer(pos, 3, GL_FLOAT, GL_FALSE, 0, vtxcoords_s)
+
+            texcoords_s = texcoords.tostring()
+            tex = effect.attrib('texcoord')
+            glEnableVertexAttribArray(tex)
+            glVertexAttribPointer(tex, 2, GL_FLOAT, GL_FALSE, 0, texcoords_s)
+
+            effect.use()
+
+            dim = effect.uniform('dimensions')
+            glUniform2f(dim, self.width, self.height)
+
+            tim = effect.uniform('time')
+            glUniform1f(tim, time.time() - self.started)
+
         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
-        glMatrixMode(GL_PROJECTION)
-        glPopMatrix()
-        glMatrixMode(GL_MODELVIEW)
-        glPopMatrix()
+
+        if effect is None:
+            glMatrixMode(GL_PROJECTION)
+            glPopMatrix()
+            glMatrixMode(GL_MODELVIEW)
+            glPopMatrix()
+        else:
+            glUseProgram(0)
 
 class Renderer:
     IS_OPENGL = True
         self.app = app
         self.tmp_sprite = None
         self.framebuffer = None
+        self.framebuffer2 = None
         self.global_offset_x = 0
         self.global_offset_y = 0
         self.global_tint = 1., 1., 1.
         glEnableClientState(GL_VERTEX_ARRAY)
 
         self.framebuffer = Framebuffer(width, height)
+        self.framebuffer2 = Framebuffer(width, height)
+
+        default_vertex_shader = """
+            attribute vec4 position;
+            attribute vec2 texcoord;
+
+            varying vec2 tex;
+
+            void main()
+            {
+                gl_Position = position;
+                tex = texcoord;
+            }
+        """
+
+        self.sepia_effect = ShaderEffect(default_vertex_shader, """
+            uniform sampler2D sampler;
+
+            varying vec2 tex;
+
+            void main()
+            {
+                vec4 color = texture2D(sampler, tex);
+                float mean = (color.x + color.y + color.z) / 3.0;
+                gl_FragColor = vec4(mean * 1.2, mean * 1.1, mean, 1);
+            }
+        """)
+
+        self.blur_effect = ShaderEffect(default_vertex_shader, """
+            uniform sampler2D sampler;
+            uniform vec2 dimensions;
+
+            varying vec2 tex;
+
+            void main()
+            {
+                float radius = 10.0 * abs(0.3 - tex.y);
+                vec2 offset = vec2(radius / dimensions.x, radius / dimensions.y);
+                gl_FragColor = 0.3 * texture2D(sampler, tex)
+                             + 0.1 * texture2D(sampler, tex + vec2(0, -offset.y))
+                             + 0.1 * texture2D(sampler, tex + vec2(0, offset.y))
+                             + 0.1 * texture2D(sampler, tex + vec2(-offset.x, 0))
+                             + 0.1 * texture2D(sampler, tex + vec2(offset.x, 0))
+                             + 0.075 * texture2D(sampler, tex + vec2(-offset.x, -offset.y))
+                             + 0.075 * texture2D(sampler, tex + vec2(offset.x, offset.y))
+                             + 0.075 * texture2D(sampler, tex + vec2(-offset.x, offset.y))
+                             + 0.075 * texture2D(sampler, tex + vec2(offset.x, -offset.y));
+            }
+        """)
+
+        self.underwater_effect = ShaderEffect(default_vertex_shader, """
+            uniform sampler2D sampler;
+            uniform vec2 dimensions;
+            uniform float time;
+
+            varying vec2 tex;
+
+            void main()
+            {
+                // Shift texture lookup sideways depending on Y coordinate + time
+                vec2 pos = tex + vec2(6.0*sin(pow(tex.y, 2.0)*20.0+time)/dimensions.x, 0.0);
+                vec4 color = texture2D(sampler, pos);
+
+                // Vignette effect (brightest at center, darker towards edges)
+                float lum = 1.0 - length(tex - vec2(0.5, 0.5));
+
+                // blue-green'ish tint base color
+                vec4 tint = vec4(0.0, 0.01, 0.05, 1.0);
+
+                // Vignette color is also blue-green'ish
+                vec4 vignette = vec4(lum * 0.7, lum * 0.9, lum, 1.0);
+
+                gl_FragColor = tint + vignette * color;
+            }
+        """)
+
 
         glEnable(GL_BLEND)
         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
         return SpriteProxy(sprite)
 
     def begin(self):
+        self.postprocessed = False
         self.framebuffer.bind()
         glClear(GL_COLOR_BUFFER_BIT)
 
         glTexCoordPointer(2, GL_FLOAT, 0, sprite._texcoords.tostring())
         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
 
+    def begin_overlay(self):
+        # Force postprocessing NOW, so overlays will be drawn as-is
+        self.postprocess()
+
+    def postprocess(self):
+        self.framebuffer.unbind()
+
+        effect_pipeline = [self.blur_effect, self.underwater_effect]#, self.sepia_effect]
+        if not effect_pipeline:
+            # Draw framebuffer contents to screen
+            glClear(GL_COLOR_BUFFER_BIT)
+            self.framebuffer.rerender()
+
+        # Apply effects by drawing between framebuffers and
+        # finally rendering the last effect to the screen
+        a, b = self.framebuffer, self.framebuffer2
+        while effect_pipeline:
+            effect = effect_pipeline.pop(0)
+            if effect_pipeline:
+                b.bind()
+            glClear(GL_COLOR_BUFFER_BIT)
+            a.rerender(effect)
+            if effect_pipeline:
+                b.unbind()
+            a, b = b, a
+
+        self.postprocessed = True
+
     def finish(self):
-        self.framebuffer.unbind()
-        glClear(GL_COLOR_BUFFER_BIT)
-        self.framebuffer.rerender()
+        if not self.postprocessed:
+            self.postprocess()
 
         if is_gles:
             sdl.SDL_GL_SwapBuffers()

src/scenes/game.py

 
             self.app.screen.draw_sprite(y-self.time, sprite, points, tint)
 
+        self.app.renderer.begin_overlay()
         self.app.screen.draw_stats(self.app.player.coins_collected,
                                    self.app.player.health)
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.