pyrender
pyrender copied to clipboard
Core API and Structural Changes
Description
The current implementation of pyrender uses a lot of hard-coded shader logic, such as binding textures and uniforms. This seriously limits the opportunity to use user-specified shaders.
Proposal
Abstract shader logic away from renderer to enable users to specify shaders and custom material to use those shaders, in a meaningful way. One way might be to abstract away shader logic from core Renderer
class and allowing materials to manage shaders.
# Bind textures
tf = material.tex_flags
if tf & TexFlags.NORMAL:
self._bind_texture(material.normalTexture,
'material.normal_texture', program)
if tf & TexFlags.OCCLUSION:
self._bind_texture(material.occlusionTexture,
'material.occlusion_texture', program)
if tf & TexFlags.EMISSIVE:
self._bind_texture(material.emissiveTexture,
'material.emissive_texture', program)
if tf & TexFlags.BASE_COLOR:
self._bind_texture(material.baseColorTexture,
'material.base_color_texture', program)
if tf & TexFlags.METALLIC_ROUGHNESS:
self._bind_texture(material.metallicRoughnessTexture,
'material.metallic_roughness_texture',
program)
if tf & TexFlags.DIFFUSE:
self._bind_texture(material.diffuseTexture,
'material.diffuse_texture', program)
if tf & TexFlags.SPECULAR_GLOSSINESS:
self._bind_texture(material.specularGlossinessTexture,
'material.specular_glossiness_texture',
program)
# Bind other uniforms
b = 'material.{}'
program.set_uniform(b.format('emissive_factor'),
material.emissiveFactor)
if isinstance(material, MetallicRoughnessMaterial):
program.set_uniform(b.format('base_color_factor'),
material.baseColorFactor)
program.set_uniform(b.format('metallic_factor'),
material.metallicFactor)
program.set_uniform(b.format('roughness_factor'),
material.roughnessFactor)
elif isinstance(material, SpecularGlossinessMaterial):
program.set_uniform(b.format('diffuse_factor'),
material.diffuseFactor)
program.set_uniform(b.format('specular_factor'),
material.specularFactor)
program.set_uniform(b.format('glossiness_factor'),
material.glossinessFactor)
which is found in the Renderer._bind_and_draw_primitive
function, will instead call
material = primitive.material
material.update_shader(self._bind_texture, program)
and Material.update_shader
is an abstract function that each type of material will need to implement. This is what the update_shader
function looks like for SpecularGlossinessMaterial
def update_shader(self, bindTex: Callable[[Any, str, ShaderProgram]], program: ShaderProgram):
assert isinstance(
program, ShaderProgram), 'material.update_shader requires ShaderProgram type'
tf = self.tex_flags
if tf & TexFlags.NORMAL:
bindTex(self.normalTexture, 'material.normal_texture', program)
if tf & TexFlags.OCCLUSION:
bindTex(self.occlusionTexture, 'material.occlusion_texture', program)
if tf & TexFlags.EMISSIVE:
bindTex(self.emissiveTexture, 'material.emissive_texture', program)
if tf & TexFlags.DIFFUSE:
bindTex(self.diffuseTexture, 'material.diffuse_texture', program)
if tf & TexFlags.SPECULAR_GLOSSINESS:
bindTex(self.specularGlossinessTexture, 'material.specular_glossiness_texture', program)
b = 'material.{}'
program.set_uniform(b.format('diffuse_factor'), self.diffuseFactor)
program.set_uniform(b.format('specular_factor'), self.specularFactor)
program.set_uniform(b.format('glossiness_factor'), self.glossinessFactor)
while I understand the logic of user having to do alot of work to get something going, this allows user to create a custom ShaderProgram
instance, load their own shaders, and have a programmatic way to use custom shaders and materials for those, without having to rewrite the library.