mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Move most of rendering logic to ShaderWrapper
This commit is contained in:
parent
c94d8fd3b0
commit
2c737ed540
4 changed files with 134 additions and 129 deletions
|
@ -70,7 +70,6 @@ class Camera(object):
|
||||||
self.init_context(window)
|
self.init_context(window)
|
||||||
self.init_light_source()
|
self.init_light_source()
|
||||||
self.refresh_perspective_uniforms()
|
self.refresh_perspective_uniforms()
|
||||||
self.init_fill_fbo(self.ctx) # Experimental
|
|
||||||
# A cached map from mobjects to their associated list of render groups
|
# A cached map from mobjects to their associated list of render groups
|
||||||
# so that these render groups are not regenerated unnecessarily for static
|
# so that these render groups are not regenerated unnecessarily for static
|
||||||
# mobjects
|
# mobjects
|
||||||
|
@ -87,80 +86,13 @@ class Camera(object):
|
||||||
self.ctx = window.ctx
|
self.ctx = window.ctx
|
||||||
self.fbo = self.ctx.detect_framebuffer()
|
self.fbo = self.ctx.detect_framebuffer()
|
||||||
self.fbo.use()
|
self.fbo.use()
|
||||||
self.set_ctx_blending()
|
|
||||||
|
|
||||||
self.ctx.enable(moderngl.PROGRAM_POINT_SIZE)
|
self.ctx.enable(moderngl.PROGRAM_POINT_SIZE)
|
||||||
|
self.ctx.enable(moderngl.BLEND)
|
||||||
|
|
||||||
# This is the frame buffer we'll draw into when emitting frames
|
# This is the frame buffer we'll draw into when emitting frames
|
||||||
self.draw_fbo = self.get_fbo(samples=0)
|
self.draw_fbo = self.get_fbo(samples=0)
|
||||||
|
|
||||||
def init_fill_fbo(self, ctx: moderngl.context.Context):
|
|
||||||
# Experimental
|
|
||||||
size = self.get_pixel_shape()
|
|
||||||
self.fill_texture = ctx.texture(
|
|
||||||
size=size,
|
|
||||||
components=4,
|
|
||||||
# Important to make sure floating point (not fixed point) is
|
|
||||||
# used so that alpha values are not clipped
|
|
||||||
dtype='f2',
|
|
||||||
)
|
|
||||||
# TODO, depth buffer is not really used yet
|
|
||||||
fill_depth = ctx.depth_renderbuffer(size)
|
|
||||||
self.fill_fbo = ctx.framebuffer(self.fill_texture, fill_depth)
|
|
||||||
self.fill_prog = ctx.program(
|
|
||||||
vertex_shader='''
|
|
||||||
#version 330
|
|
||||||
|
|
||||||
in vec2 texcoord;
|
|
||||||
out vec2 v_textcoord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);
|
|
||||||
v_textcoord = texcoord;
|
|
||||||
}
|
|
||||||
''',
|
|
||||||
fragment_shader='''
|
|
||||||
#version 330
|
|
||||||
|
|
||||||
uniform sampler2D Texture;
|
|
||||||
|
|
||||||
in vec2 v_textcoord;
|
|
||||||
out vec4 frag_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
frag_color = texture(Texture, v_textcoord);
|
|
||||||
frag_color = abs(frag_color);
|
|
||||||
if(frag_color.a == 0) discard;
|
|
||||||
//TODO, set gl_FragDepth;
|
|
||||||
}
|
|
||||||
''',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.fill_prog['Texture'].value = get_texture_id(self.fill_texture)
|
|
||||||
|
|
||||||
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
|
|
||||||
self.fill_texture_vao = ctx.simple_vertex_array(
|
|
||||||
self.fill_prog,
|
|
||||||
ctx.buffer(verts.astype('f4').tobytes()),
|
|
||||||
'texcoord',
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_ctx_blending(self, enable: bool = True) -> None:
|
|
||||||
if enable:
|
|
||||||
self.ctx.enable(moderngl.BLEND)
|
|
||||||
else:
|
|
||||||
self.ctx.disable(moderngl.BLEND)
|
|
||||||
|
|
||||||
def set_ctx_depth_test(self, enable: bool = True) -> None:
|
|
||||||
if enable:
|
|
||||||
self.ctx.enable(moderngl.DEPTH_TEST)
|
|
||||||
else:
|
|
||||||
self.ctx.disable(moderngl.DEPTH_TEST)
|
|
||||||
|
|
||||||
def set_ctx_clip_plane(self, enable: bool = True) -> None:
|
|
||||||
if enable:
|
|
||||||
gl.glEnable(gl.GL_CLIP_DISTANCE0)
|
|
||||||
|
|
||||||
def init_light_source(self) -> None:
|
def init_light_source(self) -> None:
|
||||||
self.light_source = Point(self.light_source_position)
|
self.light_source = Point(self.light_source_position)
|
||||||
|
|
||||||
|
@ -287,39 +219,11 @@ class Camera(object):
|
||||||
|
|
||||||
def render(self, render_group: dict[str, Any]) -> None:
|
def render(self, render_group: dict[str, Any]) -> None:
|
||||||
shader_wrapper = render_group["shader_wrapper"]
|
shader_wrapper = render_group["shader_wrapper"]
|
||||||
primitive = int(shader_wrapper.render_primitive)
|
shader_wrapper.render(self.perspective_uniforms)
|
||||||
shader_wrapper.update_program_uniforms(self.perspective_uniforms)
|
|
||||||
self.set_ctx_depth_test(shader_wrapper.depth_test)
|
|
||||||
self.set_ctx_clip_plane(shader_wrapper.use_clip_plane())
|
|
||||||
|
|
||||||
if shader_wrapper.is_fill:
|
|
||||||
self.render_fill(render_group["vao"], primitive, shader_wrapper.vert_indices)
|
|
||||||
else:
|
|
||||||
render_group["vao"].render(primitive)
|
|
||||||
|
|
||||||
if render_group["single_use"]:
|
if render_group["single_use"]:
|
||||||
self.release_render_group(render_group)
|
self.release_render_group(render_group)
|
||||||
|
|
||||||
def render_fill(self, vao, render_primitive: int, indices: np.ndarray):
|
|
||||||
"""
|
|
||||||
VMobject fill is handled in a special way, where emited triangles
|
|
||||||
must be blended with moderngl.FUNC_SUBTRACT so as to effectively compute
|
|
||||||
a winding number around each pixel. This is rendered to a separate texture,
|
|
||||||
then that texture is overlayed onto the current fbo
|
|
||||||
"""
|
|
||||||
winding = (len(indices) == 0)
|
|
||||||
vao.program['winding'].value = winding
|
|
||||||
if not winding:
|
|
||||||
vao.render(moderngl.TRIANGLES)
|
|
||||||
return
|
|
||||||
self.fill_fbo.clear()
|
|
||||||
self.fill_fbo.use()
|
|
||||||
self.ctx.blend_func = (moderngl.ONE, moderngl.ONE)
|
|
||||||
vao.render(render_primitive)
|
|
||||||
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
|
|
||||||
self.fbo.use()
|
|
||||||
self.fill_texture_vao.render(moderngl.TRIANGLE_STRIP)
|
|
||||||
|
|
||||||
def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str, Any]]:
|
def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str, Any]]:
|
||||||
if mobject.is_changing():
|
if mobject.is_changing():
|
||||||
return self.generate_render_group_list(mobject)
|
return self.generate_render_group_list(mobject)
|
||||||
|
@ -341,9 +245,9 @@ class Camera(object):
|
||||||
shader_wrapper: ShaderWrapper,
|
shader_wrapper: ShaderWrapper,
|
||||||
single_use: bool = True
|
single_use: bool = True
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
shader_wrapper.get_vao()
|
||||||
return {
|
return {
|
||||||
"shader_wrapper": shader_wrapper,
|
"shader_wrapper": shader_wrapper,
|
||||||
"vao": shader_wrapper.get_vao(single_use),
|
|
||||||
"single_use": single_use,
|
"single_use": single_use,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1841,7 +1841,7 @@ class Mobject(object):
|
||||||
def init_shader_data(self, ctx: Context):
|
def init_shader_data(self, ctx: Context):
|
||||||
self.shader_indices = np.zeros(0)
|
self.shader_indices = np.zeros(0)
|
||||||
self.shader_wrapper = ShaderWrapper(
|
self.shader_wrapper = ShaderWrapper(
|
||||||
context=ctx,
|
ctx=ctx,
|
||||||
vert_data=self.data,
|
vert_data=self.data,
|
||||||
shader_folder=self.shader_folder,
|
shader_folder=self.shader_folder,
|
||||||
texture_paths=self.texture_paths,
|
texture_paths=self.texture_paths,
|
||||||
|
|
|
@ -40,6 +40,7 @@ from manimlib.utils.space_ops import midpoint
|
||||||
from manimlib.utils.space_ops import normalize_along_axis
|
from manimlib.utils.space_ops import normalize_along_axis
|
||||||
from manimlib.utils.space_ops import z_to_vector
|
from manimlib.utils.space_ops import z_to_vector
|
||||||
from manimlib.shader_wrapper import ShaderWrapper
|
from manimlib.shader_wrapper import ShaderWrapper
|
||||||
|
from manimlib.shader_wrapper import FillShaderWrapper
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -1176,16 +1177,15 @@ class VMobject(Mobject):
|
||||||
)
|
)
|
||||||
fill_data = np.zeros(0, dtype=fill_dtype)
|
fill_data = np.zeros(0, dtype=fill_dtype)
|
||||||
stroke_data = np.zeros(0, dtype=stroke_dtype)
|
stroke_data = np.zeros(0, dtype=stroke_dtype)
|
||||||
self.fill_shader_wrapper = ShaderWrapper(
|
self.fill_shader_wrapper = FillShaderWrapper(
|
||||||
context=ctx,
|
ctx=ctx,
|
||||||
vert_data=fill_data,
|
vert_data=fill_data,
|
||||||
uniforms=self.uniforms,
|
uniforms=self.uniforms,
|
||||||
shader_folder=self.fill_shader_folder,
|
shader_folder=self.fill_shader_folder,
|
||||||
render_primitive=self.fill_render_primitive,
|
render_primitive=self.fill_render_primitive,
|
||||||
is_fill=True,
|
|
||||||
)
|
)
|
||||||
self.stroke_shader_wrapper = ShaderWrapper(
|
self.stroke_shader_wrapper = ShaderWrapper(
|
||||||
context=ctx,
|
ctx=ctx,
|
||||||
vert_data=stroke_data,
|
vert_data=stroke_data,
|
||||||
uniforms=self.uniforms,
|
uniforms=self.uniforms,
|
||||||
shader_folder=self.stroke_shader_folder,
|
shader_folder=self.stroke_shader_folder,
|
||||||
|
|
|
@ -4,9 +4,12 @@ import copy
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import OpenGL.GL as gl
|
||||||
import moderngl
|
import moderngl
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from manimlib.constants import DEFAULT_PIXEL_HEIGHT
|
||||||
|
from manimlib.constants import DEFAULT_PIXEL_WIDTH
|
||||||
from manimlib.utils.iterables import resize_array
|
from manimlib.utils.iterables import resize_array
|
||||||
from manimlib.utils.shaders import get_shader_code_from_file
|
from manimlib.utils.shaders import get_shader_code_from_file
|
||||||
from manimlib.utils.shaders import get_shader_program
|
from manimlib.utils.shaders import get_shader_program
|
||||||
|
@ -30,7 +33,7 @@ if TYPE_CHECKING:
|
||||||
class ShaderWrapper(object):
|
class ShaderWrapper(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
context: moderngl.context.Context,
|
ctx: moderngl.context.Context,
|
||||||
vert_data: np.ndarray,
|
vert_data: np.ndarray,
|
||||||
vert_indices: Optional[np.ndarray] = None,
|
vert_indices: Optional[np.ndarray] = None,
|
||||||
shader_folder: Optional[str] = None,
|
shader_folder: Optional[str] = None,
|
||||||
|
@ -38,17 +41,15 @@ class ShaderWrapper(object):
|
||||||
texture_paths: Optional[dict[str, str]] = None, # A dictionary mapping names to filepaths for textures.
|
texture_paths: Optional[dict[str, str]] = None, # A dictionary mapping names to filepaths for textures.
|
||||||
depth_test: bool = False,
|
depth_test: bool = False,
|
||||||
render_primitive: int = moderngl.TRIANGLE_STRIP,
|
render_primitive: int = moderngl.TRIANGLE_STRIP,
|
||||||
is_fill: bool = False,
|
|
||||||
):
|
):
|
||||||
self.ctx = context
|
self.ctx = ctx
|
||||||
self.vert_data = vert_data
|
self.vert_data = vert_data
|
||||||
self.vert_indices = (vert_indices or np.zeros(0)).astype(int)
|
self.vert_indices = (vert_indices or np.zeros(0)).astype(int)
|
||||||
self.vert_attributes = vert_data.dtype.names
|
self.vert_attributes = vert_data.dtype.names
|
||||||
self.shader_folder = shader_folder
|
self.shader_folder = shader_folder
|
||||||
self.uniforms = uniforms or dict()
|
self.uniforms = uniforms or dict()
|
||||||
self.depth_test = depth_test
|
self.depth_test = depth_test
|
||||||
self.render_primitive = str(render_primitive)
|
self.render_primitive = render_primitive
|
||||||
self.is_fill = is_fill
|
|
||||||
|
|
||||||
self.vbo = None
|
self.vbo = None
|
||||||
self.ibo = None
|
self.ibo = None
|
||||||
|
@ -99,6 +100,9 @@ class ShaderWrapper(object):
|
||||||
self.render_primitive == shader_wrapper.render_primitive,
|
self.render_primitive == shader_wrapper.render_primitive,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.release()
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
result = copy.copy(self)
|
result = copy.copy(self)
|
||||||
result.vert_data = self.vert_data.copy()
|
result.vert_data = self.vert_data.copy()
|
||||||
|
@ -148,11 +152,33 @@ class ShaderWrapper(object):
|
||||||
self.init_program()
|
self.init_program()
|
||||||
self.refresh_id()
|
self.refresh_id()
|
||||||
|
|
||||||
|
# Changing context
|
||||||
def use_clip_plane(self):
|
def use_clip_plane(self):
|
||||||
if "clip_plane" not in self.uniforms:
|
if "clip_plane" not in self.uniforms:
|
||||||
return False
|
return False
|
||||||
return any(self.uniforms["clip_plane"])
|
return any(self.uniforms["clip_plane"])
|
||||||
|
|
||||||
|
def set_ctx_depth_test(self, enable: bool = True) -> None:
|
||||||
|
if enable:
|
||||||
|
self.ctx.enable(moderngl.DEPTH_TEST)
|
||||||
|
else:
|
||||||
|
self.ctx.disable(moderngl.DEPTH_TEST)
|
||||||
|
|
||||||
|
def set_ctx_clip_plane(self, enable: bool = True) -> None:
|
||||||
|
if enable:
|
||||||
|
gl.glEnable(gl.GL_CLIP_DISTANCE0)
|
||||||
|
|
||||||
|
|
||||||
|
# Related to data and rendering
|
||||||
|
def render(self, camera_uniforms: dict):
|
||||||
|
self.update_program_uniforms(camera_uniforms)
|
||||||
|
self.set_ctx_depth_test(self.depth_test)
|
||||||
|
self.set_ctx_clip_plane(self.use_clip_plane())
|
||||||
|
|
||||||
|
# TODO, generate on the fly?
|
||||||
|
assert(self.vao is not None)
|
||||||
|
self.vao.render(self.render_primitive)
|
||||||
|
|
||||||
def combine_with(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper:
|
def combine_with(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper:
|
||||||
if len(shader_wrappers) > 0:
|
if len(shader_wrappers) > 0:
|
||||||
data_list = [self.vert_data, *(sw.vert_data for sw in shader_wrappers)]
|
data_list = [self.vert_data, *(sw.vert_data for sw in shader_wrappers)]
|
||||||
|
@ -204,30 +230,25 @@ class ShaderWrapper(object):
|
||||||
value = tuple(value)
|
value = tuple(value)
|
||||||
self.program[name].value = value
|
self.program[name].value = value
|
||||||
|
|
||||||
def get_vao(self, single_use: bool = False):
|
def get_vertex_buffer_object(self, refresh: bool = True):
|
||||||
# Data buffer
|
if refresh:
|
||||||
vert_data = self.vert_data
|
self.vbo = self.ctx.buffer(self.vert_data)
|
||||||
indices = self.vert_indices
|
return self.vbo
|
||||||
if len(indices) == 0:
|
|
||||||
self.ibo = None
|
|
||||||
elif single_use or self.is_fill:
|
|
||||||
self.ibo = self.ctx.buffer(indices.astype(np.uint32))
|
|
||||||
else:
|
|
||||||
# The vao.render call is strangely longer
|
|
||||||
# when an index buffer is used, so if the
|
|
||||||
# mobject is not changing, meaning only its
|
|
||||||
# uniforms are being updated, just create
|
|
||||||
# a larger data array based on the indices
|
|
||||||
# and don't bother with the ibo
|
|
||||||
vert_data = vert_data[indices]
|
|
||||||
self.ibo = None
|
|
||||||
self.vbo = self.ctx.buffer(vert_data)
|
|
||||||
|
|
||||||
|
def get_index_buffer_object(self, refresh: bool = True):
|
||||||
|
if refresh and len(self.vert_indices) > 0:
|
||||||
|
self.ibo = self.ctx.buffer(self.vert_indices.astype(np.uint32))
|
||||||
|
return self.ibo
|
||||||
|
|
||||||
|
def get_vao(self, refresh: bool = True):
|
||||||
|
# Data buffer
|
||||||
|
vbo = self.get_vertex_buffer_object(refresh)
|
||||||
|
ibo = self.get_index_buffer_object(refresh)
|
||||||
# Vertex array object
|
# Vertex array object
|
||||||
self.vao = self.ctx.vertex_array(
|
self.vao = self.ctx.vertex_array(
|
||||||
program=self.program,
|
program=self.program,
|
||||||
content=[(self.vbo, self.vert_format, *self.vert_attributes)],
|
content=[(vbo, self.vert_format, *self.vert_attributes)],
|
||||||
index_buffer=self.ibo,
|
index_buffer=ibo,
|
||||||
)
|
)
|
||||||
return self.vao
|
return self.vao
|
||||||
|
|
||||||
|
@ -238,3 +259,83 @@ class ShaderWrapper(object):
|
||||||
self.vbo = None
|
self.vbo = None
|
||||||
self.ibo = None
|
self.ibo = None
|
||||||
self.vao = None
|
self.vao = None
|
||||||
|
|
||||||
|
|
||||||
|
class FillShaderWrapper(ShaderWrapper):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
ctx: moderngl.context.Context,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
super().__init__(ctx, *args, **kwargs)
|
||||||
|
|
||||||
|
size = (2 * DEFAULT_PIXEL_WIDTH, 2 * DEFAULT_PIXEL_HEIGHT)
|
||||||
|
self.fill_texture = ctx.texture(
|
||||||
|
size=size,
|
||||||
|
components=4,
|
||||||
|
# Important to make sure floating point (not fixed point) is
|
||||||
|
# used so that alpha values are not clipped
|
||||||
|
dtype='f2',
|
||||||
|
)
|
||||||
|
# TODO, depth buffer is not really used yet
|
||||||
|
fill_depth = ctx.depth_renderbuffer(size)
|
||||||
|
self.fill_fbo = ctx.framebuffer(self.fill_texture, fill_depth)
|
||||||
|
self.fill_prog = ctx.program(
|
||||||
|
vertex_shader='''
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 texcoord;
|
||||||
|
out vec2 v_textcoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);
|
||||||
|
v_textcoord = texcoord;
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
fragment_shader='''
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
|
||||||
|
in vec2 v_textcoord;
|
||||||
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
frag_color = texture(Texture, v_textcoord);
|
||||||
|
frag_color = abs(frag_color);
|
||||||
|
if(frag_color.a == 0) discard;
|
||||||
|
//TODO, set gl_FragDepth;
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.fill_prog['Texture'].value = get_texture_id(self.fill_texture)
|
||||||
|
|
||||||
|
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
|
||||||
|
self.fill_texture_vao = ctx.simple_vertex_array(
|
||||||
|
self.fill_prog,
|
||||||
|
ctx.buffer(verts.astype('f4').tobytes()),
|
||||||
|
'texcoord',
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, camera_uniforms: dict):
|
||||||
|
# TODO, these are copied...
|
||||||
|
self.update_program_uniforms(camera_uniforms)
|
||||||
|
self.set_ctx_depth_test(self.depth_test)
|
||||||
|
self.set_ctx_clip_plane(self.use_clip_plane())
|
||||||
|
#
|
||||||
|
vao = self.vao
|
||||||
|
assert(vao is not None)
|
||||||
|
winding = (len(self.vert_indices) == 0)
|
||||||
|
vao.program['winding'].value = winding
|
||||||
|
if not winding:
|
||||||
|
vao.render(moderngl.TRIANGLES)
|
||||||
|
return
|
||||||
|
self.fill_fbo.clear()
|
||||||
|
self.fill_fbo.use()
|
||||||
|
self.ctx.blend_func = (moderngl.ONE, moderngl.ONE)
|
||||||
|
vao.render(self.render_primitive)
|
||||||
|
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
|
||||||
|
self.ctx.screen.use()
|
||||||
|
self.fill_texture_vao.render(moderngl.TRIANGLE_STRIP)
|
||||||
|
|
Loading…
Add table
Reference in a new issue