Pygame中模糊投影为何在半透明Surface上变亮而非变暗?
我对Alpha混合的基础原理可能存在误解。在Pygame中制作模糊投影的步骤如下:
- 绘制白色物体
- 在另一个Surface上绘制其黑色版本
- 对黑色版本应用高斯模糊
- 将模糊后的黑色版本与白色版本依次blit到新Surface上
到这一步都正常,但当把这个Surface叠加到填充了半透明颜色的Surface上,再blit到背景时,阴影的alpha似乎影响了半透明Surface,导致阴影变亮而非黑色。实际项目中必须返回合并后的单一Surface,无法独立blit两个Surface到背景。
错误效果:白色圆形带浅灰色投影
正常效果(半透明Surface完全透明时):白色圆形带黑色投影
尝试调整blit的special_flags改变混合模式,但所有组合都无效。
以下是可复现代码:
import pygame as pg pg.init() screen = pg.display.set_mode((400, 400)) clock = pg.time.Clock() black_circle_surface = pg.Surface((100, 100), pg.SRCALPHA) pg.draw.circle(black_circle_surface, (0, 0, 0), (50, 50), 25) white_circle_surface = pg.Surface((100, 100), pg.SRCALPHA) pg.draw.circle(white_circle_surface, (255, 255, 255), (50, 50), 25) circle_and_shadow_surface = pg.Surface((100, 100), pg.SRCALPHA) circle_and_shadow_surface.blit( pg.transform.gaussian_blur(black_circle_surface, radius=10), (0, 0) ) circle_and_shadow_surface.blit(white_circle_surface, (0, 0)) semi_transparent_surface = pg.Surface((200, 200), pg.SRCALPHA) semi_transparent_surface.fill( (255, 255, 255, 1) # 把1改成0问题就消失 ) semi_transparent_surface.blit( circle_and_shadow_surface, (50, 50), ) running = True while running: for event in pg.event.get(): if event.type == pg.QUIT: running = False screen.fill((20, 70, 80)) screen.blit(semi_transparent_surface, (100, 100)) pg.display.update() clock.tick(60)
问题出在Alpha混合的叠加逻辑上:模糊后的阴影自身带alpha通道(高斯模糊会让边缘alpha值降低),当它被blit到半透明的Surface上时,会和Surface的底色(白色半透明)进行混合,导致黑色阴影被冲淡成灰色。
要解决这个问题,需要在合并阴影、物体和半透明Surface时,调整混合模式,让阴影的黑色能够正确覆盖半透明底色,同时保留原本的透明度效果。具体有两种方法:
方法一:使用BLEND_RGBA_MULT混合模式绘制阴影
在将模糊后的阴影blit到circle_and_shadow_surface时,使用special_flags=pg.BLEND_RGBA_MULT,这样阴影的黑色会和目标Surface的底色(透明)相乘,保留纯黑的颜色信息,避免后续叠加半透明Surface时被冲淡。
修改这部分代码:
circle_and_shadow_surface = pg.Surface((100, 100), pg.SRCALPHA) # 用乘法混合模式绘制阴影 circle_and_shadow_surface.blit( pg.transform.gaussian_blur(black_circle_surface, radius=10), (0, 0), special_flags=pg.BLEND_RGBA_MULT ) circle_and_shadow_surface.blit(white_circle_surface, (0, 0))
方法二:先绘制半透明底色,再叠加阴影和物体
换个顺序操作:先在最终Surface上填充半透明颜色,再依次blit阴影和白色物体,直接得到合并后的结果。这样阴影是直接绘制在半透明底色上,混合逻辑更可控。
修改代码如下:
# 直接创建带半透明底色的最终Surface final_surface = pg.Surface((200, 200), pg.SRCALPHA) final_surface.fill((255, 255, 255, 1)) # 绘制阴影到最终Surface的对应位置 final_surface.blit( pg.transform.gaussian_blur(black_circle_surface, radius=10), (50, 50), special_flags=pg.BLEND_RGBA_MULT ) # 绘制白色物体 final_surface.blit(white_circle_surface, (50, 50)) # 后续直接使用final_surface即可,替换原来的semi_transparent_surface
原理说明
BLEND_RGBA_MULT模式会将源像素和目标像素的RGBA值分别相乘,对于黑色(0,0,0,alpha)来说,相乘后会保留黑色,同时保留阴影的透明度渐变,避免和半透明底色混合时被提亮。- 调整绘制顺序可以让阴影直接和半透明底色进行混合,而非先合并阴影和物体,再与半透明底色二次混合,减少了混合层级带来的颜色偏差。
内容的提问来源于stack exchange,提问作者Jack Gaino

