Directional Lighting not working when imported with panda3d-gltf, but does when imported with assimp

okay no problem

I think I’ll try to expand my function to convert the material to the standard.

minimiun exampll

Asset:
playground3.bam (42.4 KB)

from direct.showbase.ShowBase import ShowBase, TextureStage, TexGenAttrib
from direct.filter.CommonFilters import CommonFilters
from direct.showbase.InputStateGlobal import inputState
from panda3d.core import (loadPrcFileData, 
                         WindowProperties, 
                         AmbientLight, 
                         DirectionalLight,
                         LightRampAttrib, 
                         Vec3, 
                         MaterialAttrib)
from panda3d.bullet import (BulletDebugNode, 
                            BulletWorld)
import sys
loadPrcFileData("", "show-frame-rate-meter #t")
loadPrcFileData("", "sync-video #t")
loadPrcFileData("", "frame-rate-meter-milliseconds true")
loadPrcFileData("", "show-scene-graph-analyzer-meter true")


class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        props = WindowProperties()
        props.setTitle("bad_lighting")
        base.win.requestProperties(props)
        self._set_up_environment_lighting()
        self._set_up_toon_shading()
        self._setup_level()
        self.accept("q", self.exit)
        self._debug_bullet()

    def exit(self):
        sys.exit()

    def _set_up_toon_shading(self):
        self.separation = 1.2  # Pixels
        self.filters = CommonFilters(self.win, self.cam)
        filterok = self.filters.setCartoonInk(separation=self.separation)
        if (filterok == False):
            print(
                "Toon Shader: Video card not powerful enough to do image postprocessing")
            return
    
    def _set_up_environment_lighting(self):
        base.render.setShaderAuto()
        base.render.setAttrib(LightRampAttrib.makeSingleThreshold(0.2,0.6))
        self.sun = DirectionalLight("sun")
        self.sun.setColor((0.8, 0.8, 0.8, 1))
        base.sunnp = self.render.attachNewNode( self.sun)
        base.sunnp.setZ(base.sunnp, 50)
        base.sunnp.setY(base.sunnp, -30)
        base.sunnp.setP(base.sunnp, -90)
        base.sunnp.node().setShadowCaster(True, 512, 512)
        base.sunnp.node().getLens().setFilmSize(2,2)
        base.sunnp.node().showFrustum()
        base.render.setLight(base.sunnp)
        self.ambient_light = AmbientLight('alight')
        self.ambient_light.setColor((0.9, 0.9, 0.9, 1))
        self.alnp = self.render.attachNewNode(self.ambient_light)
        self.render.setLight(self.alnp)
    
    def _debug_bullet(self):
        debugNode = BulletDebugNode('Debug')
        debugNode.showWireframe(True)
        debugNode.showConstraints(True)
        debugNode.showBoundingBoxes(True)
        debugNode.showNormals(True)
        debugNP = render.attachNewNode(debugNode)
        debugNP.show()
        base.bullet_world.setDebugNode(debugNP.node())
        

    def _setup_level(self):
        base.bullet_world_node_path = self.render.attachNewNode('World')
        base.bullet_world = BulletWorld()
        base.bullet_world.setGravity(Vec3(0, 0, 0)) # (0,0,-14)
        playground = loader.loadModel("playground3.bam")
        for node_path in playground.find_all_matches('**/+GeomNode'):
            geom_states = node_path.node().get_geom_states()
            for geom_state in geom_states:
                if geom_state.has_attrib(MaterialAttrib):
                    mat = geom_state.get_attrib(MaterialAttrib).get_material()
                    mat.clear_emission()
        for node_path in playground.find_all_matches('**/+BulletRigidBodyNode'):
            base.bullet_world.attachRigidBody(node_path.node())
        playground.reparentTo(base.bullet_world_node_path)
        self.taskMgr.add(self.update, sort=-2)

    def update(self, task):
        dt = globalClock.getDt()
        base.bullet_world.doPhysics(dt, 5, 1.0/180.0)
        return task.cont

app = MyApp()
app.run()

remember to install gltf so it overtakes assimp:

pip install panda3d-blend2bam
pip install panda3d-gltf
Panda3D==1.10.13
panda3d-blend2bam==0.21
panda3d-gltf==0.15.0
panda3d-simplepbr==0.10

Hmm, it’s strange that this works with a textured model… I think it is necessary to conduct digital excavations in this direction.

What do you mean? It doesnt work with a textured model to my knolwedge. That example does not rspond to directional light nor shadows. I do appreciate the research!

I mean my example works with a textured model. But it does not work with a model that was exported only with the material. When the auto shader is turned on, the lighting disappears.

1 Like

Yeah, i suspect, but cant confirm that the added fallback textures play a role. I have one instance where applying those textures results in lighting working. But another where it didnt.

With texture, it starts working with my fix function, which removes the emission parameter from materials. If there is no texture, the object turns white.

Oh gotcha. I need to read up on the rendering related stuff so i can go down the rabbit hole.

Based on a quick examination, I find that the problem can be seen even in PView, like so:

  • Load the above-linked bam-file in PView
  • Press “L” to activate lighting.
    • Note that–at least on my machine–the material colours become visible, and the objects are shaded
  • Press “P” to activate per-pixel shading
    • Note that–again, at least on my machine–the objects suddenly become flat grey.

However, converting the bam-file to an egg-file via “bam2egg” seems to cause the problem to go away…

My suspicion, then, is that there’s a problem with the material, and that the light-ramping is perhaps a false lead.

Via the minimal example given above I did try the rendering of the model both with and without the emission-clearing code, but in neither case did the problem seem to go away.

Hmm… But investigating the material doesn’t seem to lead anywhere…

And I notice something else: In the “bam” version, there’s a strange broad, flat block under the half-sphere, which flickers in a way that suggests that it somehow has collision geometry that’s visible by default–which is unexpected.

And I note that in the “egg” version, that surface goes away; viewing the “egg” version in PView, I see that the collision-geometry is still there, but is invisible by default, as I would expect.

So, perhaps the problem is that this strange object is in some way interfering with things? What happens if you re-export your model without that platform…?

The problem is indicated, the gltf loader does not analyze texture inputs for the presence of maps. And it always creates empty texture stages, so panda does not load textures and replaces them with the default value.

In this connection, the lighting breaks down, since instead of a normal map, one vector is always obtained, since the calculation is performed from these data (0.5, 0.5, 1, 0)

The empty emission map also breaks the rendering.

But this is happening with a “bam” file, not a “glTF” file…

It doesn’t matter, so panda3d-gltf/panda3d-blend2bam creates it.

Ah, I see: you’re saying that “blend2bam” uses panda3d-gltf, and so it’s going through the glTF pipeline. Hm–it could be so. Indeed, a quick printing out of texture-stages does indicate a bunch of them–all of which have a colour of (0, 0, 0, 1), which may be part of the problem.

I have created a module that converts the output file panda3d-gltf/panda3d-blend2bam in accordance with the standard auto shader.

fix_gltf_bam.py

from panda3d.core import MaterialAttrib, Vec4, TextureAttrib, RenderState, TextureStage

def render_stage_convert(node, use_modulate = False, use_normal = False, use_selector = False, use_emission = False):
    for node_path in node.find_all_matches('**/+GeomNode'):
        geom_node = node_path.node()
        new_render_state = RenderState.make_empty()
        for index_geom in range(geom_node.get_num_geoms()):
            render_state = geom_node.get_geom_state(index_geom)
            if render_state.has_attrib(MaterialAttrib):
                material = render_state.get_attrib(MaterialAttrib).get_material()
                #material.set_diffuse(Vec4(*material.get_base_color()))
                material.set_twoside(False)
                material.set_refractive_index(1)
                material.set_shininess(0)
                material.clear_metallic()
                #material.clear_diffuse()
                #material.clear_base_color()
                material.clear_ambient()
                material.clear_emission()
                new_render_state = new_render_state.add_attrib(MaterialAttrib.make(material))
            if render_state.has_attrib(TextureAttrib):
                new_texture_attrib = TextureAttrib.make_default()
                texture_attrib = render_state.get_attrib(TextureAttrib)
                count_stages = texture_attrib.get_num_on_stages()
                for index_texture_stage in range(count_stages):
                    texture_stage = texture_attrib.get_on_stage(index_texture_stage)
                    texture = render_state.get_attrib(TextureAttrib).get_on_texture(texture_stage)
                    sampler = render_state.get_attrib(TextureAttrib).get_on_sampler(texture_stage)
                    if texture_stage.get_mode() == TextureStage.M_modulate:
                        if use_modulate:
                            new_texture_attrib = new_texture_attrib.add_on_stage(texture_stage, texture, sampler)
                    if texture_stage.get_mode() == TextureStage.M_normal:
                        if use_normal:
                            new_texture_attrib = new_texture_attrib.add_on_stage(texture_stage, texture, sampler)
                    if texture_stage.get_mode() == TextureStage.M_selector:
                        if use_selector:
                            new_texture_attrib = new_texture_attrib.add_on_stage(texture_stage, texture, sampler)
                    if texture_stage.get_mode() == TextureStage.M_emission:
                        if use_emission:
                            new_texture_attrib = new_texture_attrib.add_on_stage(texture_stage, texture, sampler)
                if new_texture_attrib.get_num_on_stages() > 0:
                    new_render_state = new_render_state.add_attrib(new_texture_attrib)
                geom_node.set_geom_state(index_geom, new_render_state)

Use flags to disable unnecessary texture stages.

render_stage_convert(gltf, use_modulate = False, use_normal = False, use_selector = False, use_emission = False)

If you want to use a base texture for the color:

render_stage_convert(gltf, use_modulate = True, use_normal = False, use_selector = False, use_emission = False)

And so on, as for the selector mode, it is unlikely that it will ever come in handy, but I left it.

Usage example based on code from user @CeyaSpaceCowboy
source.zip (22.4 KB)

@CeyaSpaceCowboy

Don’t forget to check the Backface Culling box in the material. This will save you from shadow acne.
Also consider using anti-aliasing.

https://docs.panda3d.org/1.10/python/programming/render-attributes/antialiasing

I replaced your model with mine. I also set the ambient light to this value (0.2, 0.2, 0.2, 1) And I adjusted the lens size so that it covered my model. setFilmSize(200,200)

1 Like

Looking at this one, I thought it was possible to implement it as a NodePath method. Flags can be calculated automatically without any problems.

1 Like

Thanks man, ill try this soon.

holy crap it works. CHEERS @serega-kkz

Don’t rush to use it, I’m working on improving it at the moment.