diff --git a/examples/pong/images/bat_light.png b/examples/pong/images/bat_light.png new file mode 100644 index 0000000..a4d3c1b Binary files /dev/null and b/examples/pong/images/bat_light.png differ diff --git a/examples/pong/images/point_light.png b/examples/pong/images/point_light.png new file mode 100644 index 0000000..281948e Binary files /dev/null and b/examples/pong/images/point_light.png differ diff --git a/examples/pong/lights.svg b/examples/pong/lights.svg new file mode 100644 index 0000000..f80d6bc --- /dev/null +++ b/examples/pong/lights.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/examples/pong/pong.py b/examples/pong/pong.py index 91cbbf3..899f250 100644 --- a/examples/pong/pong.py +++ b/examples/pong/pong.py @@ -2,15 +2,30 @@ import random from pygame.math import Vector2 from wasabi2d.keyboard import keys +from wasabi2d import chain mode_1080p = 1920, 1080 scene = w2d.Scene( *mode_1080p, fullscreen=True, - background='#444455' ) scene.layers[0].set_effect('dropshadow', opacity=0.5) +scene.layers[99].set_effect('additive') +scene.chain = [ + chain.Light( + light=[ + chain.Layers([99]), + ], + diffuse=[ + chain.Fill('#444455'), + chain.LayerRange(stop=10), + ], + ambient='#444444' + ) +] + + center = Vector2(scene.width, scene.height) / 2 red_score = scene.layers[-1].add_label( @@ -31,27 +46,36 @@ color='cyan' ) -red = scene.layers[0].add_sprite( - 'bat_red', - pos=(50, center.y) -) +red = w2d.Group( + [ + scene.layers[0].add_sprite('bat_red'), + scene.layers[99].add_sprite('bat_light', color=(1, 0, 0, 0.5)), + ], + pos=(50, center.y)) + red.up_key = keys.Q red.down_key = keys.A -blue = scene.layers[0].add_sprite( - 'bat_blue', +blue = w2d.Group( + [ + scene.layers[0].add_sprite('bat_blue'), + scene.layers[99].add_sprite('bat_light', color=(0, 1, 1, 0.5)), + ], pos=(scene.width - 50, center.y) ) blue.up_key = keys.I blue.down_key = keys.K -ball = scene.layers[0].add_sprite( - 'ball', +ball = w2d.Group( + [ + scene.layers[0].add_sprite('ball'), + scene.layers[99].add_sprite('point_light', scale=5, color=(1, 1, 1, 0.3)), + ], pos=center ) SPEED = 1000 -BALL_RADIUS = ball.width / 2 +BALL_RADIUS = ball[0].width / 2 def start(): @@ -63,7 +87,7 @@ def start(): def collide_bat(bat): - bounds = bat.bounds.inflate(BALL_RADIUS, BALL_RADIUS) + bounds = bat[0].bounds.inflate(BALL_RADIUS, BALL_RADIUS) if bounds.collidepoint(ball.pos): x, y = ball.pos vx, vy = ball.vel diff --git a/wasabi2d/chain.py b/wasabi2d/chain.py index 1833f30..aa9b4de 100644 --- a/wasabi2d/chain.py +++ b/wasabi2d/chain.py @@ -219,6 +219,62 @@ def draw(self, scene): ) +LIGHT_PROG = """ \ +#version 330 core + +in vec2 uv; +out vec4 f_color; +uniform sampler2D diffuse; +uniform sampler2D light; +uniform vec4 ambient; + +vec4 unpremultiply(vec4 val) { + if (val.a < 1e-6) { + return vec4(0.0, 0.0, 0.0, 0.0); + } + return vec4(val.rgb / val.a, val.a); +} + +vec4 sample_unmul(sampler2D sampler) { + //return unpremultiply(texture(sampler, uv)); + return texture(sampler, uv); +} + +void main() +{ + vec4 diffuse_frag = sample_unmul(diffuse); + vec4 light_frag = sample_unmul(light); + + f_color = (light_frag + ambient) * diffuse_frag; +} +""" + + +@dataclass +class Light(ChainNode): + """Light one 'diffuse' layer using the 'light' layer.""" + + light: ChainNode + diffuse: ChainNode + ambient: Tuple[float, float, float, float] + + def __post_init__(self): + self.light = to_node(self.light) + self.diffuse = to_node(self.diffuse) + self.ambient = convert_color(self.ambient) + + def draw(self, scene): + """Draw the effect.""" + with rendered_node(scene, self.diffuse) as diffuse, \ + rendered_node(scene, self.light) as light: + scene.camera.run_shader( + LIGHT_PROG, + diffuse=diffuse, + light=light, + ambient=self.ambient, + ) + + class Fill(ChainNode): """Fill the screen with a single colour. diff --git a/wasabi2d/effects/additive.py b/wasabi2d/effects/additive.py new file mode 100644 index 0000000..5583047 --- /dev/null +++ b/wasabi2d/effects/additive.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + +import moderngl + +from ..shaders import blend_func + + +@dataclass +class Additive: + ctx: moderngl.Context + + def _set_camera(self, camera: 'wasabi2d.scene.Camera'): + """Nothing to do here.""" + + def draw(self, draw_layer): + """Draw the wrapped effects with additive blending.""" + with blend_func( + self.ctx, + src=moderngl.SRC_ALPHA, + dest=moderngl.ONE, + src_a=moderngl.ONE, + dest_a=moderngl.ONE, + ): + draw_layer() diff --git a/wasabi2d/scene.py b/wasabi2d/scene.py index 9176fd3..04fecee 100644 --- a/wasabi2d/scene.py +++ b/wasabi2d/scene.py @@ -106,7 +106,7 @@ def __init__( self.layers = LayerGroup(ctx, self.camera) ctx.enable(moderngl.BLEND) - self.ctx.blend_func = ( + self.ctx.extra['blend_func'] = self.ctx.blend_func = ( moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA, moderngl.ONE, moderngl.ONE_MINUS_SRC_ALPHA, ) diff --git a/wasabi2d/shaders.py b/wasabi2d/shaders.py index bd3c1f6..2cdafa4 100644 --- a/wasabi2d/shaders.py +++ b/wasabi2d/shaders.py @@ -167,11 +167,9 @@ def blend_func( src_a = blend_aliases.get(src_a, src_a) dest_a = blend_aliases.get(dest_a, dest_a) - ctx.blend_func = src, dest, src_a, dest_a + prev_blend = ctx.extra['blend_func'] + ctx.extra['blend_func'] = ctx.blend_func = src, dest, src_a, dest_a try: yield finally: - ctx.blend_func = ( - moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA, - moderngl.ONE, moderngl.ONE_MINUS_SRC_ALPHA - ) + ctx.blend_func = prev_blend