- Create _create_quad_vao() method that creates VAO with vertex buffer - Use explicit vertex attributes (a_position, a_texcoord) in shaders - Bind VAO before glDrawArrays calls - Use ctypes for proper buffer sizing
475 lines
15 KiB
Python
475 lines
15 KiB
Python
import OpenGL.GL as gl
|
|
|
|
class ShaderManager:
|
|
def __init__(self):
|
|
self.program = None
|
|
self.bg_program = None
|
|
self.pp_program = None
|
|
|
|
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)
|
|
|
|
self.program = program
|
|
return program
|
|
|
|
def update_uniforms(self, uniforms: dict):
|
|
if self.program is None:
|
|
return
|
|
|
|
for name, value in uniforms.items():
|
|
loc = gl.glGetUniformLocation(self.program, name)
|
|
if loc == -1:
|
|
continue
|
|
|
|
if isinstance(value, float):
|
|
gl.glUniform1f(loc, value)
|
|
elif isinstance(value, int):
|
|
gl.glUniform1i(loc, value)
|
|
elif isinstance(value, (list, tuple)):
|
|
if len(value) == 2:
|
|
gl.glUniform2f(loc, value[0], value[1])
|
|
elif len(value) == 3:
|
|
gl.glUniform3f(loc, value[0], value[1], value[2])
|
|
elif len(value) == 4:
|
|
gl.glUniform4f(loc, value[0], value[1], value[2], value[3])
|
|
|
|
def setup_background_shader(self):
|
|
vertex_src = """
|
|
#version 330 core
|
|
const vec2 positions[4] = vec2[](
|
|
vec2(-1.0, -1.0),
|
|
vec2( 1.0, -1.0),
|
|
vec2(-1.0, 1.0),
|
|
vec2( 1.0, 1.0)
|
|
);
|
|
void main() {
|
|
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
|
}
|
|
"""
|
|
fragment_src = """
|
|
#version 330 core
|
|
uniform float u_time;
|
|
uniform vec2 u_resolution;
|
|
out vec4 FragColor;
|
|
void main() {
|
|
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
|
vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
|
|
FragColor = vec4(col, 1.0);
|
|
}
|
|
"""
|
|
self.bg_program = self.compile_shader(vertex_src, fragment_src)
|
|
|
|
def render_background(self, width, height, time):
|
|
if not self.bg_program:
|
|
return
|
|
gl.glUseProgram(self.bg_program)
|
|
u_time_loc = gl.glGetUniformLocation(self.bg_program, "u_time")
|
|
if u_time_loc != -1:
|
|
gl.glUniform1f(u_time_loc, float(time))
|
|
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
|
|
if u_res_loc != -1:
|
|
gl.glUniform2f(u_res_loc, float(width), float(height))
|
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
gl.glUseProgram(0)
|
|
|
|
def setup_post_process_shader(self):
|
|
vertex_src = """
|
|
#version 330 core
|
|
const vec2 positions[4] = vec2[](
|
|
vec2(-1.0, -1.0),
|
|
vec2( 1.0, -1.0),
|
|
vec2(-1.0, 1.0),
|
|
vec2( 1.0, 1.0)
|
|
);
|
|
const vec2 uvs[4] = vec2[](
|
|
vec2(0.0, 0.0),
|
|
vec2(1.0, 0.0),
|
|
vec2(0.0, 1.0),
|
|
vec2(1.0, 1.0)
|
|
);
|
|
out vec2 v_uv;
|
|
void main() {
|
|
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
|
v_uv = uvs[gl_VertexID];
|
|
}
|
|
"""
|
|
fragment_src = """
|
|
#version 330 core
|
|
in vec2 v_uv;
|
|
uniform sampler2D u_texture;
|
|
uniform float u_time;
|
|
out vec4 FragColor;
|
|
void main() {
|
|
vec4 color = texture(u_texture, v_uv);
|
|
float scanline = sin(v_uv.y * 800.0 + u_time * 2.0) * 0.04;
|
|
color.rgb -= scanline;
|
|
FragColor = color;
|
|
}
|
|
"""
|
|
self.pp_program = self.compile_shader(vertex_src, fragment_src)
|
|
|
|
def render_post_process(self, texture_id, width, height, time):
|
|
if not self.pp_program:
|
|
return
|
|
gl.glUseProgram(self.pp_program)
|
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
|
|
u_tex_loc = gl.glGetUniformLocation(self.pp_program, "u_texture")
|
|
if u_tex_loc != -1:
|
|
gl.glUniform1i(u_tex_loc, 0)
|
|
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
|
|
if u_time_loc != -1:
|
|
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.deepsea_program: int | None = None
|
|
self._quad_vao: int | None = None
|
|
self._fb_width: int = 0
|
|
self._fb_height: int = 0
|
|
self._fb_scale: int = 1
|
|
|
|
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]:
|
|
if width <= 0 or height <= 0:
|
|
raise ValueError(f"Invalid FBO dimensions: {width}x{height}")
|
|
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 _create_quad_vao(self) -> int:
|
|
import ctypes
|
|
vao = gl.glGenVertexArrays(1)
|
|
gl.glBindVertexArray(vao)
|
|
vertices = (ctypes.c_float * 16)(
|
|
-1.0, -1.0, 0.0, 0.0,
|
|
1.0, -1.0, 1.0, 0.0,
|
|
-1.0, 1.0, 0.0, 1.0,
|
|
1.0, 1.0, 1.0, 1.0
|
|
)
|
|
vbo = gl.glGenBuffers(1)
|
|
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
|
|
gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(vertices), vertices, gl.GL_STATIC_DRAW)
|
|
gl.glEnableVertexAttribArray(0)
|
|
gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, None)
|
|
gl.glEnableVertexAttribArray(1)
|
|
gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, ctypes.c_void_p(8))
|
|
gl.glBindVertexArray(0)
|
|
return vao
|
|
|
|
def setup_fbos(self, width: int, height: int, fb_scale: float = 1.0):
|
|
scale = max(1, int(fb_scale))
|
|
blur_w = max(1, (width * scale) // 4)
|
|
blur_h = max(1, (height * scale) // 4)
|
|
self._fb_width = blur_w
|
|
self._fb_height = blur_h
|
|
self._fb_scale = scale
|
|
scene_w = width * scale
|
|
scene_h = height * scale
|
|
self.scene_fbo, self.scene_tex = self._create_fbo(scene_w, scene_h)
|
|
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
|
|
layout(location = 0) in vec2 a_position;
|
|
layout(location = 1) in vec2 a_texcoord;
|
|
out vec2 v_uv;
|
|
void main() {
|
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
v_uv = a_texcoord;
|
|
}
|
|
"""
|
|
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 * 6.0) * 0.0152;
|
|
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
|
|
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
|
|
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
|
|
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
|
|
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
|
|
sum += texture(u_texture, v_uv) * 0.1432;
|
|
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
|
|
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
|
|
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
|
|
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
|
|
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
|
|
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
|
|
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 * 6.0) * 0.0152;
|
|
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
|
|
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
|
|
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
|
|
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
|
|
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
|
|
sum += texture(u_texture, v_uv) * 0.1432;
|
|
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
|
|
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
|
|
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
|
|
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
|
|
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
|
|
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
|
|
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 compile_deepsea_shader(self):
|
|
vert_src = """
|
|
#version 330 core
|
|
layout(location = 0) in vec2 a_position;
|
|
layout(location = 1) in vec2 a_texcoord;
|
|
out vec2 v_uv;
|
|
void main() {
|
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
v_uv = a_texcoord;
|
|
}
|
|
"""
|
|
frag_src = """
|
|
#version 330 core
|
|
in vec2 v_uv;
|
|
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 = v_uv;
|
|
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)
|
|
self._quad_vao = self._create_quad_vao()
|
|
|
|
def render_deepsea_to_fbo(self, width: int, height: int, time: float):
|
|
if not self.deepsea_program or not self.scene_fbo or not self._quad_vao:
|
|
return
|
|
scene_w = width * self._fb_scale
|
|
scene_h = height * self._fb_scale
|
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
|
|
gl.glViewport(0, 0, scene_w, scene_h)
|
|
gl.glClearColor(0.01, 0.05, 0.12, 1.0)
|
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
gl.glUseProgram(self.deepsea_program)
|
|
u_time_loc = gl.glGetUniformLocation(self.deepsea_program, "u_time")
|
|
if u_time_loc != -1:
|
|
gl.glUniform1f(u_time_loc, time)
|
|
u_res_loc = gl.glGetUniformLocation(self.deepsea_program, "u_resolution")
|
|
if u_res_loc != -1:
|
|
gl.glUniform2f(u_res_loc, float(scene_w), float(scene_h))
|
|
gl.glBindVertexArray(self._quad_vao)
|
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
gl.glBindVertexArray(0)
|
|
gl.glUseProgram(0)
|
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
|
|
|
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.glBindVertexArray(self._quad_vao)
|
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
gl.glBindVertexArray(0)
|
|
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)
|
|
restore_w = width * self._fb_scale
|
|
restore_h = height * self._fb_scale
|
|
gl.glViewport(0, 0, restore_w, restore_h)
|
|
|
|
def prepare_global_blur(self, width: int, height: int, time: float, fb_scale: float = 1.0):
|
|
if not self.scene_fbo:
|
|
if self._fb_scale != int(fb_scale):
|
|
self.setup_fbos(width, height, fb_scale)
|
|
self.render_deepsea_to_fbo(width, height, time)
|
|
self.prepare_blur(width, height, time)
|
|
|
|
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, self.deepsea_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)
|
|
if self._quad_vao:
|
|
gl.glDeleteVertexArrays(1, [self._quad_vao])
|
|
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
|
|
self.deepsea_program = None
|
|
self._quad_vao = None
|