Skip to content

Commit

Permalink
Add per-layer post-processing effects
Browse files Browse the repository at this point in the history
  • Loading branch information
lordmauve committed Sep 11, 2019
1 parent 8b8bc01 commit 10b2bde
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 18 deletions.
4 changes: 2 additions & 2 deletions examples/breakout/breakout.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@


ball = Actor(
scene.layers[0].add_circle(
scene.layers[1].add_circle(
radius=BALL_SIZE,
color='#cccccc',
fill=False,
),
pos=(WIDTH / 2, HEIGHT / 2),
)
scene.layers[1].set_effect('trails', fade=0.2)
bat = Actor(
scene.layers[0].add_rect(
width=120,
Expand Down
7 changes: 4 additions & 3 deletions sprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
scene = Scene(antialias=8)
scene.background = (0, 0.03, 0.1)

ship = scene.layers[1].add_sprite(
ship = scene.layers[2].add_sprite(
'ship',
pos=(scene.width / 2, scene.height / 2)
)
Expand Down Expand Up @@ -63,14 +63,15 @@
poly.stroke_width = 0


particles = scene.layers[0].add_particle_group(
scene.layers[1].set_effect('bloom', threshold=0.9, radius=10)
particles = scene.layers[1].add_particle_group(
texture='smoke',
grow=3,
max_age=2,
gravity=(0, 100),
drag=0.5,
)
particles.add_color_stop(0, 'red')
particles.add_color_stop(0, (4, 0, 0, 1))
particles.add_color_stop(0.3, 'yellow')
particles.add_color_stop(1.0, 'gray')
particles.add_color_stop(2, (0.3, 0.3, 0.3, 0))
Expand Down
Empty file added wasabi2d/effects/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions wasabi2d/effects/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Base functionality for post-processing effects."""
import numpy as np
import moderngl


POSTPROCESS_VERT_PROGRAM = '''
#version 330
in vec2 in_vert;
in vec2 in_uv;
out vec2 uv;
void main() {
gl_Position = vec4(in_vert.xy, 0.0, 1.0);
uv = in_uv;
}
'''


class PostprocessPass:
"""A full-screen postprocessing effect pass.
This will render a full-screen quad using a single fragment shader.
"""
# TODO: let the shadermanager itself manage these, so that they
# are shared between layers etc

QUAD_INDICES = np.array([0, 1, 2, 0, 2, 3], dtype='i4')
QUAD_VERTS_UVS = np.array([
[-1, -1, 0, 0],
[1, -1, 1, 0],
[1, 1, 1, 1],
[-1, 1, 0, 1],
], dtype='f4')

def __init__(
self,
ctx: moderngl.Context,
shadermgr: 'wasabi2d.layers.ShaderManager',
fragment_shader: str,
send_uvs: bool = True):
self.ctx = ctx
self.prog = shadermgr.get(
vertex_shader=POSTPROCESS_VERT_PROGRAM,
fragment_shader=fragment_shader,
)

indices = ctx.buffer(self.QUAD_INDICES)
vs_uvs = ctx.buffer(self.QUAD_VERTS_UVS)

if send_uvs:
attribs = (vs_uvs, '2f4 2f4', 'in_vert', 'in_uv')
else:
attribs = (vs_uvs, '2f4 8x', 'in_vert')
self.vao = ctx.vertex_array(
self.prog,
[attribs],
index_buffer=indices
)

def render(self, **uniforms):
"""Assign the given uniforms and then render."""
texnum = 0
for k, v in uniforms.items():
if isinstance(v, moderngl.Framebuffer):
v = v.color_attachments[0]

if isinstance(v, moderngl.Texture):
v.use(texnum)
v = texnum
texnum += 1

self.prog[k].value = v
self.vao.render(moderngl.TRIANGLES, 6)

175 changes: 175 additions & 0 deletions wasabi2d/effects/bloom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from typing import Tuple, List
from dataclasses import dataclass

import moderngl
import numpy as np

from .base import PostprocessPass


def gaussian(x, mu, sig):
"""Calculate a gaussian function."""
return np.exp(-np.power(x - mu, 2.) / (2 * np.power(sig, 2.)))


THRESHOLD_PROG = """ \
#version 330 core
in vec2 uv;
out vec4 f_color;
uniform sampler2D image;
void main()
{
vec4 in_col = texture(image, uv);
float lum = dot(vec3(0.3, 0.6, 0.1), in_col.rgb) * in_col.a;
f_color = in_col * pow(lum, 2);
}
"""


COPY_PROG = """ \
#version 330 core
in vec2 uv;
out vec4 f_color;
uniform sampler2D image;
void main()
{
f_color = texture(image, uv);
}
"""

# Shader code adapted from https://learnopengl.com/Advanced-Lighting/Bloom
# First pass, blur vertically
BLOOM_PROG_1 = """ \
#version 330 core
in vec2 uv;
out vec4 f_color;
uniform sampler2D image;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel
vec3 result = texture(image, uv).rgb * weight[0]; // current fragment's contribution
for(int i = 1; i < 5; ++i)
{
result += texture(image, uv + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
result += texture(image, uv - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
}
f_color = vec4(result, 1);
}
"""


# Second pass, perform horizontal blur and composite original
BLOOM_PROG_2 = """ \
#version 330 core
in vec2 uv;
out vec4 f_color;
uniform sampler2D vblurred;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
vec2 tex_offset = 1.0 / textureSize(vblurred, 0); // gets size of single texel
vec3 result = texture(vblurred, uv).rgb * weight[0]; // current fragment's contribution
for(int i = 1; i < 5; ++i)
{
result += texture(vblurred, uv + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
result += texture(vblurred, uv - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
}
f_color = vec4(result, 1);
}
"""


@dataclass
class Bloom:
"""A light bloom effect."""
ctx: moderngl.Context
shadermgr: 'wasabi2d.layers.ShaderManager'
threshold: float = 1.0
radius: float = 10.0

camera: 'wasabi2d.scene.Camera' = None
_pass1: PostprocessPass = None
_pass2: PostprocessPass = None
_fb1: moderngl.Framebuffer = None
_fb2: moderngl.Framebuffer = None

def _set_camera(self, camera: 'wasabi2d.scene.Camera'):
"""Resize the effect for this viewport."""
self.camera = camera
self._fb1, = camera._get_temporary_fbs(1, 'f2')
self._thresholded = self.ctx.framebuffer([
self.ctx.texture(
(camera.width // 8, camera.height // 8),
4,
dtype='f2'
)
])
self._fb2 = self.ctx.framebuffer([
self.ctx.texture(
(camera.width // 8, camera.height),
4,
dtype='f2'
)
])
self._threshold_pass = PostprocessPass(
self.ctx,
self.shadermgr,
THRESHOLD_PROG,
)
self._pass1 = PostprocessPass(
self.ctx,
self.shadermgr,
BLOOM_PROG_1
)
self._pass2 = PostprocessPass(
self.ctx,
self.shadermgr,
BLOOM_PROG_2
)
self._copy = self._mkpass(COPY_PROG)

def _mkpass(self, shader):
return PostprocessPass(self.ctx, self.shadermgr, shader)

def enter(self, t, dt):
self._fb1.use()
self._fb1.clear()

def exit(self, t, dt):
self._thresholded.use()
self._thresholded.clear()
self._threshold_pass.render(image=self._fb1)

self._fb2.use()
self._fb2.clear()
self._pass1.render(
image=self._thresholded,
)
self.ctx.screen.use()

self._copy.render(image=self._fb1)
self.ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE
self._pass2.render(
vblurred=self._fb2,
)
self.ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA
78 changes: 78 additions & 0 deletions wasabi2d/effects/trails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""An effect where we keep trails from previous frames."""
from typing import Tuple, List
from dataclasses import dataclass

import moderngl

from .base import PostprocessPass


FADE_PROG = """ \
#version 330 core
out vec4 f_color;
uniform float fade;
uniform float dt;
void main()
{
f_color = vec4(0, 0, 0, 1.0 - pow(fade, dt));
}
"""


COMPOSITE_PROG = """ \
#version 330 core
in vec2 uv;
out vec4 f_color;
uniform sampler2D fb;
void main()
{
f_color = texture(fb, uv);
}
"""


@dataclass
class Trails:
"""A trails effect."""
ctx: moderngl.Context
shadermgr: 'wasabi2d.layers.ShaderManager'
fade: float = 0.9

camera: 'wasabi2d.scene.Camera' = None
_pass: PostprocessPass = None
_fb: moderngl.Framebuffer = None

def _set_camera(self, camera: 'wasabi2d.scene.Camera'):
"""Resize the effect for this viewport."""
self.camera = camera
self._fb = camera._make_fb('f2')
self._fade_pass = PostprocessPass(
self.ctx,
self.shadermgr,
FADE_PROG,
send_uvs=False
)
self._composite_pass = PostprocessPass(
self.ctx,
self.shadermgr,
COMPOSITE_PROG,
)

def enter(self, t, dt):
self._fb.use()
self._fade_pass.render(
fade=self.fade,
dt=dt
)

def exit(self, t, dt):
self.ctx.screen.use()
self._composite_pass.render(fb=self._fb)
Loading

0 comments on commit 10b2bde

Please sign in to comment.