feat(shader): Add Deep Sea background shader for BlurPipeline
- Add compile_deepsea_shader() with animated underwater-like GLSL shader - Add render_deepsea_to_fbo() to render background to scene FBO - Deep Sea shader features: FBM noise, animated blobs, caustic lines, vignette - Add deepsea_program to cleanup() for proper resource management - Add 2 new tests for Deep Sea shader compilation and FBO rendering Task: Phase 1, Task 2 of frosted_glass_20260313 track
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Phase 1: Robust Shader & FBO Foundation
|
## Phase 1: Robust Shader & FBO Foundation
|
||||||
- [x] Task: Implement: Create `ShaderManager` methods for downsampled FBO setup (scene, temp, blur). [d9148ac]
|
- [x] Task: Implement: Create `ShaderManager` methods for downsampled FBO setup (scene, temp, blur). [d9148ac]
|
||||||
- [ ] Task: Implement: Develop the "Deep Sea" background shader and integrate it as the FBO source.
|
- [~] Task: Implement: Develop the "Deep Sea" background shader and integrate it as the FBO source.
|
||||||
- [ ] Task: Implement: Develop the 2-pass Gaussian blur shaders with a wide tap distribution.
|
- [ ] Task: Implement: Develop the 2-pass Gaussian blur shaders with a wide tap distribution.
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Robust Foundation' (Protocol in workflow.md)
|
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Robust Foundation' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ class BlurPipeline:
|
|||||||
self.blur_tex_b: int | None = None
|
self.blur_tex_b: int | None = None
|
||||||
self.h_blur_program: int | None = None
|
self.h_blur_program: int | None = None
|
||||||
self.v_blur_program: int | None = None
|
self.v_blur_program: int | None = None
|
||||||
|
self.deepsea_program: int | None = None
|
||||||
self._fb_width: int = 0
|
self._fb_width: int = 0
|
||||||
self._fb_height: int = 0
|
self._fb_height: int = 0
|
||||||
|
|
||||||
@@ -273,6 +274,94 @@ void main() {
|
|||||||
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
|
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
|
||||||
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
|
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
|
||||||
|
|
||||||
|
def compile_deepsea_shader(self):
|
||||||
|
vert_src = """
|
||||||
|
#version 330 core
|
||||||
|
void main() {
|
||||||
|
vec2 pos = vec2(-1.0, -1.0);
|
||||||
|
if (gl_VertexID == 1) pos = vec2(1.0, -1.0);
|
||||||
|
else if (gl_VertexID == 2) pos = vec2(-1.0, 1.0);
|
||||||
|
else if (gl_VertexID == 3) pos = vec2(1.0, 1.0);
|
||||||
|
gl_Position = vec4(pos, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
frag_src = """
|
||||||
|
#version 330 core
|
||||||
|
uniform float u_time;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
f = f * f * (3.0 - 2.0 * f);
|
||||||
|
float a = hash(i);
|
||||||
|
float b = hash(i + vec2(1.0, 0.0));
|
||||||
|
float c = hash(i + vec2(0.0, 1.0));
|
||||||
|
float d = hash(i + vec2(1.0, 1.0));
|
||||||
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float v = 0.0;
|
||||||
|
float a = 0.5;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
v += a * noise(p);
|
||||||
|
p *= 2.0;
|
||||||
|
a *= 0.5;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
||||||
|
float t = u_time * 0.3;
|
||||||
|
vec3 col = vec3(0.01, 0.05, 0.12);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
float phase = t * (0.1 + float(i) * 0.05);
|
||||||
|
vec2 blob_uv = uv + vec2(sin(phase), cos(phase * 0.8)) * 0.3;
|
||||||
|
float blob = fbm(blob_uv * 3.0 + t * 0.2);
|
||||||
|
col = mix(col, vec3(0.02, 0.20, 0.40), blob * 0.4);
|
||||||
|
}
|
||||||
|
float line_alpha = 0.0;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
float fi = float(i);
|
||||||
|
float offset = mod(t * 15.0 + fi * (u_resolution.x / 12.0), u_resolution.x);
|
||||||
|
float line_x = offset / u_resolution.x;
|
||||||
|
float dist = abs(uv.x - line_x);
|
||||||
|
float alpha = smoothstep(0.02, 0.0, dist) * (0.1 + 0.05 * sin(t + fi));
|
||||||
|
line_alpha += alpha;
|
||||||
|
}
|
||||||
|
col += vec3(0.04, 0.35, 0.55) * line_alpha;
|
||||||
|
float vignette = 1.0 - length(uv - 0.5) * 0.8;
|
||||||
|
col *= vignette;
|
||||||
|
FragColor = vec4(col, 1.0);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.deepsea_program = self._compile_shader(vert_src, frag_src)
|
||||||
|
|
||||||
|
def render_deepsea_to_fbo(self, width: int, height: int, time: float):
|
||||||
|
if not self.deepsea_program or not self.scene_fbo:
|
||||||
|
return
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
|
||||||
|
gl.glViewport(0, 0, width, height)
|
||||||
|
gl.glClearColor(0.01, 0.05, 0.12, 1.0)
|
||||||
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||||
|
gl.glUseProgram(self.deepsea_program)
|
||||||
|
u_time = gl.glGetUniformLocation(self.deepsea_program, "u_time")
|
||||||
|
if u_time != -1:
|
||||||
|
gl.glUniform1f(u_time, time)
|
||||||
|
u_res = gl.glGetUniformLocation(self.deepsea_program, "u_resolution")
|
||||||
|
if u_res != -1:
|
||||||
|
gl.glUniform2f(u_res, float(width), float(height))
|
||||||
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
gl.glUseProgram(0)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||||
|
|
||||||
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
|
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
|
||||||
gl.glUseProgram(program)
|
gl.glUseProgram(program)
|
||||||
gl.glActiveTexture(gl.GL_TEXTURE0)
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
||||||
@@ -313,7 +402,7 @@ void main() {
|
|||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
fbos = [f for f in [self.scene_fbo, self.blur_fbo_a, self.blur_fbo_b] if f is not None]
|
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]
|
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]
|
progs = [p for p in [self.h_blur_program, self.v_blur_program, self.deepsea_program] if p is not None]
|
||||||
if fbos:
|
if fbos:
|
||||||
gl.glDeleteFramebuffers(len(fbos), fbos)
|
gl.glDeleteFramebuffers(len(fbos), fbos)
|
||||||
if texs:
|
if texs:
|
||||||
@@ -329,3 +418,4 @@ void main() {
|
|||||||
self.blur_tex_b = None
|
self.blur_tex_b = None
|
||||||
self.h_blur_program = None
|
self.h_blur_program = None
|
||||||
self.v_blur_program = None
|
self.v_blur_program = None
|
||||||
|
self.deepsea_program = None
|
||||||
|
|||||||
@@ -43,6 +43,37 @@ def test_blur_pipeline_compile_shaders():
|
|||||||
assert pipeline.h_blur_program is not None
|
assert pipeline.h_blur_program is not None
|
||||||
assert pipeline.v_blur_program is not None
|
assert pipeline.v_blur_program is not None
|
||||||
|
|
||||||
|
def test_blur_pipeline_render_deepsea_to_fbo():
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
tex_counter = iter([10, 20, 30])
|
||||||
|
fbo_counter = iter([1, 2, 3])
|
||||||
|
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
|
||||||
|
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
|
||||||
|
mock_gl.glCreateProgram.return_value = 300
|
||||||
|
mock_gl.glCreateShader.return_value = 400
|
||||||
|
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
||||||
|
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
||||||
|
from src.shader_manager import BlurPipeline
|
||||||
|
pipeline = BlurPipeline()
|
||||||
|
pipeline.setup_fbos(800, 600)
|
||||||
|
pipeline.compile_deepsea_shader()
|
||||||
|
pipeline.render_deepsea_to_fbo(800, 600, 0.0)
|
||||||
|
assert mock_gl.glBindFramebuffer.called
|
||||||
|
assert mock_gl.glUseProgram.called
|
||||||
|
assert mock_gl.glDrawArrays.called
|
||||||
|
|
||||||
|
def test_blur_pipeline_deepsea_shader_compilation():
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glCreateProgram.return_value = 500
|
||||||
|
mock_gl.glCreateShader.return_value = 600
|
||||||
|
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
||||||
|
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
||||||
|
from src.shader_manager import BlurPipeline
|
||||||
|
pipeline = BlurPipeline()
|
||||||
|
pipeline.compile_deepsea_shader()
|
||||||
|
assert mock_gl.glCreateProgram.called
|
||||||
|
assert pipeline.deepsea_program is not None
|
||||||
|
|
||||||
def test_blur_pipeline_prepare_blur():
|
def test_blur_pipeline_prepare_blur():
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
mock_gl.glGenFramebuffers.return_value = None
|
mock_gl.glGenFramebuffers.return_value = None
|
||||||
|
|||||||
Reference in New Issue
Block a user