[solved] Parent is not apparently illuminated by light source, but its children are

This is probably a very basic issue so I’m sorry if its been answered previously.

Imgur

I am adding my first light source to my node graph and confused by the results. I have a node graph that looks like

render -> playing_surface -> balls

The playing_surface is a procedurally drawn rectangle, and each ball is a child node of playing_surface. The table is at a height LPoint3f(0, 0, 0.75) and the method I use to initialize the lights looks like this:

    def init_lights(self):
        height = 3

        plight = PointLight('plight')
        plight.setColor((1, 1, 1, 1))
        plnp = self.render.attachNewNode(plight)
        plnp.setPos(self.shot.table.w*0.5, self.shot.table.l*0.5, height)
        self.render.setLight(plnp)

        plight.setShadowCaster(True, 1024, 1024)
        self.render.setShaderAuto()

To my surprise, the balls look illuminated by the light, however the playing_surface is not, even though the balls are children of that node.

Can someone please “illuminate” what I’m doing wrong?

Thanks so much.

Cheers,
Evan

[edit]

Ah, I see that you do have the auto-shader set–my apologies, I missed that! Disregard the rest of this post, then. ^^;

[/edit]

I’m guessing that you aren’t using shaders, whether via the auto-shader or a custom shader; am I correct?

If so, then you’re by default using the old fixed-function lighting.

That lighting system, you see, is based on vertices: each vertex is lit according to its distance from the point light, and the lighting of a given point on a polygon is then an interpolation of the lighting of that polygon’s vertices.

However, this means that if a model has vertices that are far apart, and the light is placed between those vertices, all vertices will be far from the light and thus dark–resulting in a dark surface, even though part of it may be directly under the light!

Thus your surface likely is being lit–but as I imagine that it’s just a quad, with vertices only at its distant corners, a point-light placed near its centre will barely light it.

There are a few potential solutions that I see offhand (aside from just disabling lighting for the surface). You could:

  • Enable the auto-shader (or even apply a custom shader, if you want)
    • This should switch the rendering to modern shader-based, per-pixel rendering, which should light all of the surface.
  • Increase the number of vertices in the surface
    • With more vertices, the vertices are closer together, and thus it’s more likely that there will be vertices near to the light-source.
  • Use a directional light instead of a point light
  • Since directional lights don’t consider the position of a given object, just its orientation, your surface should be lit.
1 Like

What happens when you disable shadow mapping? If that makes the surface look correctly lit, then you will probably have to adjust the camera lens or apply a depth offset to the surface if you want to fix the shadows. If it’s not related to shadow mapping, then the way you generate your rectangle might be the problem. Do you use CardMaker or the low-level geometry-creation methods? In the latter case, make sure to add vertex normals.

Another possibility is that you are applying a normal map to the surface, and then you would also need to add tangent-space vectors.

2 Likes

Thanks for your suggestions everyone

It is not the shadow-mapping. Probably what’s going on is that I’m borrowing some code from samples/procedural-cube/main.py. Here is my function for making playing_surface:

from panda3d.core import (
    Quat,
    lookAt,
    GeomVertexFormat,
    GeomVertexData,
    Geom,
    GeomTriangles,
    GeomVertexWriter,
    GeomNode,
    LVector3,
)

def make_square(x1, y1, z1, x2, y2, z2, name='square'):
    fmt = GeomVertexFormat.getV3n3cpt2()
    vdata = GeomVertexData('square', fmt, Geom.UHDynamic)

    vertex = GeomVertexWriter(vdata, 'vertex')
    normal = GeomVertexWriter(vdata, 'normal')

    # make sure we draw the sqaure in the right plane
    if x1 != x2:
        vertex.addData3(x1, y1, z1)
        vertex.addData3(x2, y1, z1)
        vertex.addData3(x2, y2, z2)
        vertex.addData3(x1, y2, z2)

        normal.addData3(normalize(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1))
        normal.addData3(normalize(2 * x2 - 1, 2 * y1 - 1, 2 * z1 - 1))
        normal.addData3(normalize(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1))
        normal.addData3(normalize(2 * x1 - 1, 2 * y2 - 1, 2 * z2 - 1))

    else:
        vertex.addData3(x1, y1, z1)
        vertex.addData3(x2, y2, z1)
        vertex.addData3(x2, y2, z2)
        vertex.addData3(x1, y1, z2)

        normal.addData3(normalize(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1))
        normal.addData3(normalize(2 * x2 - 1, 2 * y2 - 1, 2 * z1 - 1))
        normal.addData3(normalize(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1))
        normal.addData3(normalize(2 * x1 - 1, 2 * y1 - 1, 2 * z2 - 1))

    tris = GeomTriangles(Geom.UHDynamic)
    tris.addVertices(0, 1, 3)
    tris.addVertices(1, 2, 3)

    square = Geom(vdata)
    square.addPrimitive(tris)
    square_node = GeomNode(name)
    square_node.addGeom(square)

    return square_node

Since I don’t see anything about normal vectors, that’s gotta be the problem, right? I will try the CardMaker class, since it probably more than suffices for my needs.

Cheers.

1 Like

Ugh, that thing again… no wonder. (Sorry, that sample is kinda my pet peeve.)

It does generate normal vectors:

    normal = GeomVertexWriter(vdata, 'normal')
...
        normal.addData3(normalize(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1))
        normal.addData3(normalize(2 * x2 - 1, 2 * y1 - 1, 2 * z1 - 1))
        normal.addData3(normalize(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1))
        normal.addData3(normalize(2 * x1 - 1, 2 * y2 - 1, 2 * z2 - 1))

But they always point away from the center of the cube (that the square is supposed to be a side of), while they should be perpendicular to the square in this case. Just one of the reasons I don’t like that sample.
If you’re interested in procedural geometry, feel free to check this out. (Shameless plug, sorry (again) :stuck_out_tongue: .)

If you’re going with CardMaker, note that it generates its rectangles in the XZ-plane, so you’ll have to do:

        playing_surface.set_p(-90.)

Cheers!

2 Likes

It does generate normal vectors

Oh great catch. It very clearly defines normals!

If you’re interested in procedural geometry, feel free to check this out

I will definitely check this out! Thanks so much for the info and resource.

If you’re going with CardMaker, note that it generates its rectangles in the XZ-plane, so you’ll have to do: playing_surface.set_p(-90.)

Ya realized that after awhile… For that reason I want to avoid using the CardMaker class, because I don’t want to be positioning my primitives with HPR values–that just seems crazy. I guess it works in this case, because it is a simple 90 degree turn, but even then all child nodes of playing_surface are inheriting the rotation :\

It seems like the path forward is to redraw using your samples as a base. I’ll let you know what it looks like!

You could perhaps get around this by having the surface-node itself be an empty common node, with the surface-geometry being a child of that node alongside the other child-nodes. That way the child-nodes shouldn’t inherit the rotation of the surface-geometry.

1 Like

@Epihaius By explicitly defining the normals as pointing in the <0,0,1> direction, and by turning off cube.setTwoSided(True), which was in the sample, I was able to get some nice illuminations of the table :slight_smile:

2 Likes

Nice work :slight_smile: !

By the way, it totally slipped my mind that it’s possible to “bake” a transformation into the geometry of a NodePath using a call to flatten_light:

        playing_surface.set_p(-90.)
        playing_surface.flatten_light()
        print(playing_surface.get_p())  # should print `0.0`

No more problems with child node transforms :slight_smile: .

2 Likes