feat(shader): Add BlurPipeline class for frosted glass FBO setup
- Add BlurPipeline class with downsampled FBO support (scene, blur_a, blur_b) - Implement 2-pass Gaussian blur shaders (horizontal + vertical) - Add setup_fbos(), compile_blur_shaders(), prepare_blur(), cleanup() methods - Add tests for BlurPipeline initialization, FBO setup, shader compilation, blur execution, and cleanup Task: Phase 1, Task 1 of frosted_glass_20260313 track
This commit is contained in:
@@ -150,4 +150,182 @@ void main() {
|
||||
gl.glUniform1f(u_time_loc, float(time))
|
||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||
|
||||
class BlurPipeline:
|
||||
def __init__(self):
|
||||
self.scene_fbo: int | None = None
|
||||
self.scene_tex: int | None = None
|
||||
self.blur_fbo_a: int | None = None
|
||||
self.blur_tex_a: int | None = None
|
||||
self.blur_fbo_b: int | None = None
|
||||
self.blur_tex_b: int | None = None
|
||||
self.h_blur_program: int | None = None
|
||||
self.v_blur_program: int | None = None
|
||||
self._fb_width: int = 0
|
||||
self._fb_height: int = 0
|
||||
|
||||
def _compile_shader(self, vertex_src: str, fragment_src: str) -> int:
|
||||
program = gl.glCreateProgram()
|
||||
def _compile(src, shader_type):
|
||||
shader = gl.glCreateShader(shader_type)
|
||||
gl.glShaderSource(shader, src)
|
||||
gl.glCompileShader(shader)
|
||||
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
|
||||
info_log = gl.glGetShaderInfoLog(shader)
|
||||
if hasattr(info_log, "decode"):
|
||||
info_log = info_log.decode()
|
||||
raise RuntimeError(f"Shader compilation failed: {info_log}")
|
||||
return shader
|
||||
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
|
||||
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
|
||||
gl.glAttachShader(program, vert_shader)
|
||||
gl.glAttachShader(program, frag_shader)
|
||||
gl.glLinkProgram(program)
|
||||
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
|
||||
info_log = gl.glGetProgramInfoLog(program)
|
||||
if hasattr(info_log, "decode"):
|
||||
info_log = info_log.decode()
|
||||
raise RuntimeError(f"Program linking failed: {info_log}")
|
||||
gl.glDeleteShader(vert_shader)
|
||||
gl.glDeleteShader(frag_shader)
|
||||
return program
|
||||
|
||||
def _create_fbo(self, width: int, height: int) -> tuple[int, int]:
|
||||
tex = gl.glGenTextures(1)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, tex)
|
||||
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
||||
fbo = gl.glGenFramebuffers(1)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, fbo)
|
||||
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, tex, 0)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||
return fbo, tex
|
||||
|
||||
def setup_fbos(self, width: int, height: int):
|
||||
blur_w = max(1, width // 4)
|
||||
blur_h = max(1, height // 4)
|
||||
self._fb_width = blur_w
|
||||
self._fb_height = blur_h
|
||||
self.scene_fbo, self.scene_tex = self._create_fbo(width, height)
|
||||
self.blur_fbo_a, self.blur_tex_a = self._create_fbo(blur_w, blur_h)
|
||||
self.blur_fbo_b, self.blur_tex_b = self._create_fbo(blur_w, blur_h)
|
||||
|
||||
def compile_blur_shaders(self):
|
||||
vert_src = """
|
||||
#version 330 core
|
||||
out vec2 v_uv;
|
||||
void main() {
|
||||
vec2 pos = vec2(-1.0, -1.0);
|
||||
vec2 uv = vec2(0.0, 0.0);
|
||||
if (gl_VertexID == 1) { pos = vec2(1.0, -1.0); uv = vec2(1.0, 0.0); }
|
||||
else if (gl_VertexID == 2) { pos = vec2(-1.0, 1.0); uv = vec2(0.0, 1.0); }
|
||||
else if (gl_VertexID == 3) { pos = vec2(1.0, 1.0); uv = vec2(1.0, 1.0); }
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
v_uv = uv;
|
||||
}
|
||||
"""
|
||||
h_frag_src = """
|
||||
#version 330 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_texel_size;
|
||||
out vec4 FragColor;
|
||||
void main() {
|
||||
vec2 offset = vec2(u_texel_size.x, 0.0);
|
||||
vec4 sum = vec4(0.0);
|
||||
sum += texture(u_texture, v_uv - offset * 4.0) * 0.051;
|
||||
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0918;
|
||||
sum += texture(u_texture, v_uv - offset * 2.0) * 0.12245;
|
||||
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1531;
|
||||
sum += texture(u_texture, v_uv) * 0.1633;
|
||||
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1531;
|
||||
sum += texture(u_texture, v_uv + offset * 2.0) * 0.12245;
|
||||
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0918;
|
||||
sum += texture(u_texture, v_uv + offset * 4.0) * 0.051;
|
||||
FragColor = sum;
|
||||
}
|
||||
"""
|
||||
v_frag_src = """
|
||||
#version 330 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_texel_size;
|
||||
out vec4 FragColor;
|
||||
void main() {
|
||||
vec2 offset = vec2(0.0, u_texel_size.y);
|
||||
vec4 sum = vec4(0.0);
|
||||
sum += texture(u_texture, v_uv - offset * 4.0) * 0.051;
|
||||
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0918;
|
||||
sum += texture(u_texture, v_uv - offset * 2.0) * 0.12245;
|
||||
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1531;
|
||||
sum += texture(u_texture, v_uv) * 0.1633;
|
||||
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1531;
|
||||
sum += texture(u_texture, v_uv + offset * 2.0) * 0.12245;
|
||||
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0918;
|
||||
sum += texture(u_texture, v_uv + offset * 4.0) * 0.051;
|
||||
FragColor = sum;
|
||||
}
|
||||
"""
|
||||
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
|
||||
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
|
||||
|
||||
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
|
||||
gl.glUseProgram(program)
|
||||
gl.glActiveTexture(gl.GL_TEXTURE0)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, src_tex)
|
||||
u_tex = gl.glGetUniformLocation(program, "u_texture")
|
||||
if u_tex != -1:
|
||||
gl.glUniform1i(u_tex, 0)
|
||||
u_ts = gl.glGetUniformLocation(program, "u_texel_size")
|
||||
if u_ts != -1:
|
||||
gl.glUniform2f(u_ts, texel_size[0], texel_size[1])
|
||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||
gl.glUseProgram(0)
|
||||
|
||||
def prepare_blur(self, width: int, height: int, time: float):
|
||||
if not self.h_blur_program or not self.v_blur_program:
|
||||
return
|
||||
if not self.blur_fbo_a or not self.blur_fbo_b:
|
||||
return
|
||||
blur_w = max(1, self._fb_width)
|
||||
blur_h = max(1, self._fb_height)
|
||||
texel_x = 1.0 / float(blur_w)
|
||||
texel_y = 1.0 / float(blur_h)
|
||||
gl.glViewport(0, 0, blur_w, blur_h)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_a)
|
||||
gl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||
self._render_quad(self.h_blur_program, self.scene_tex, (texel_x, texel_y))
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_b)
|
||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||
self._render_quad(self.v_blur_program, self.blur_tex_a, (texel_x, texel_y))
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||
gl.glViewport(0, 0, width, height)
|
||||
|
||||
def get_blur_texture(self) -> int | None:
|
||||
return self.blur_tex_b
|
||||
|
||||
def cleanup(self):
|
||||
fbos = [f for f in [self.scene_fbo, self.blur_fbo_a, self.blur_fbo_b] if f is not None]
|
||||
texs = [t for t in [self.scene_tex, self.blur_tex_a, self.blur_tex_b] if t is not None]
|
||||
progs = [p for p in [self.h_blur_program, self.v_blur_program] if p is not None]
|
||||
if fbos:
|
||||
gl.glDeleteFramebuffers(len(fbos), fbos)
|
||||
if texs:
|
||||
gl.glDeleteTextures(len(texs), texs)
|
||||
if progs:
|
||||
for p in progs:
|
||||
gl.glDeleteProgram(p)
|
||||
self.scene_fbo = None
|
||||
self.scene_tex = None
|
||||
self.blur_fbo_a = None
|
||||
self.blur_tex_a = None
|
||||
self.blur_fbo_b = None
|
||||
self.blur_tex_b = None
|
||||
self.h_blur_program = None
|
||||
self.v_blur_program = None
|
||||
|
||||
Reference in New Issue
Block a user