For what it’s worth, here is a sample program showing how to use shadows with the built-in parameters in Panda:

```
from panda3d.core import *
load_prc_file_data("", "framebuffer-srgb #t")
#load_prc_file_data("", "gl-support-shadow-filter false")
load_prc_file_data("", "default-fov 90")
import math
from direct.showbase.ShowBase import ShowBase
from direct.interval.IntervalGlobal import Parallel, Sequence
base = ShowBase()
EXPONENT = 128
BRIGHTNESS = 1
DIST = 0.5
shader = Shader.make(Shader.SL_GLSL, """
#version 330
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat3 p3d_NormalMatrix;
in vec4 vertex;
in vec3 normal;
out vec3 vpos;
out vec3 norm;
out vec4 shad[3];
uniform struct {
vec4 position;
vec4 diffuse;
vec4 specular;
vec3 attenuation;
vec3 spotDirection;
float spotCosCutoff;
float spotExponent;
sampler2DShadow shadowMap;
mat4 shadowMatrix;
} p3d_LightSource[3];
void main() {
gl_Position = p3d_ModelViewProjectionMatrix * vertex;
vpos = vec3(gl_ModelViewMatrix * vertex);
norm = normalize(p3d_NormalMatrix * normal);
shad[0] = p3d_LightSource[0].shadowMatrix * vertex;
shad[1] = p3d_LightSource[1].shadowMatrix * vertex;
shad[2] = p3d_LightSource[2].shadowMatrix * vertex;
}
""", """
#version 330
uniform sampler2D p3d_Texture0;
uniform struct {
vec4 ambient;
} p3d_LightModel;
uniform struct {
vec4 ambient;
vec4 diffuse;
vec3 specular;
float shininess;
} p3d_Material;
uniform struct {
vec4 position;
vec4 diffuse;
vec4 specular;
vec3 attenuation;
vec3 spotDirection;
float spotCosCutoff;
float spotExponent;
sampler2DShadow shadowMap;
mat4 shadowMatrix;
} p3d_LightSource[3];
in vec3 vpos;
in vec3 norm;
in vec4 shad[3];
void main() {
gl_FragColor = p3d_LightModel.ambient * p3d_Material.ambient;
for (int i = 0; i < p3d_LightSource.length(); ++i) {
vec3 diff = p3d_LightSource[i].position.xyz - vpos * p3d_LightSource[i].position.w;
vec3 L = normalize(diff);
vec3 E = normalize(-vpos);
vec3 R = normalize(-reflect(L, norm));
vec4 diffuse = clamp(p3d_Material.diffuse * p3d_LightSource[i].diffuse * max(dot(norm, L), 0), 0, 1);
vec4 specular = vec4(p3d_Material.specular, 1) * p3d_LightSource[i].specular * pow(max(dot(R, E), 0), gl_FrontMaterial.shininess);
float spotEffect = dot(normalize(p3d_LightSource[i].spotDirection), -L);
if (spotEffect > p3d_LightSource[i].spotCosCutoff) {
diffuse *= pow(spotEffect, p3d_LightSource[i].spotExponent);
diffuse *= textureProj(p3d_LightSource[i].shadowMap, shad[i]);
gl_FragColor += diffuse / dot(p3d_LightSource[i].attenuation, vec3(1, length(diff), length(diff) * length(diff)));
}
}
gl_FragColor.a = 1;
}
""")
cm = CardMaker("card")
cm.setFrame(-5, 5, -5, 5)
card = render.attachNewNode(cm.generate())
m = Material()
m.set_diffuse((1, 1, 1, 1))
card.setMaterial(m)
red = Spotlight("red")
red.setColor((BRIGHTNESS, 0, 0, 1))
red.setExponent(EXPONENT)
red.getLens().setNearFar(5, 11)
red.getLens().setFov(30)
red_path = render.attachNewNode(red)
red_path.set_pos(1, -10, 0)
red_path.look_at(DIST * math.cos(math.pi * 1.333), 0, DIST * math.sin(math.pi * 1.3333))
green = Spotlight("green")
green.setColor((0, BRIGHTNESS, 0, 1))
green.setExponent(EXPONENT)
green.getLens().setNearFar(5, 11)
green.getLens().setFov(30)
green_path = render.attach_new_node(green)
green_path.set_pos(-1, -10, 0)
green_path.look_at(DIST * math.cos(math.pi * 0.666), 0, DIST * math.sin(math.pi * 0.6666))
green_path.set_x(green_path.get_x() + 1)
blue = Spotlight("blue")
blue.setColor((0, 0, BRIGHTNESS, 1))
blue.setExponent(EXPONENT)
blue.getLens().setNearFar(5, 11)
blue.getLens().setFov(30)
blue_path = render.attach_new_node(blue)
blue_path.set_pos(0, -10, 1)
blue_path.look_at(DIST * math.cos(0), DIST * math.sin(0), 0)
blue_path.set_y(green_path.get_y() + 1)
red.setShadowCaster(True, 4096, 4096)
green.setShadowCaster(True, 4096, 4096)
blue.setShadowCaster(True, 4096, 4096)
#red.show_frustum()
#green.show_frustum()
#blue.show_frustum()
render.set_light(red_path)
render.set_light(green_path)
render.set_light(blue_path)
sphere = loader.loadModel("models/sphere")
spheres = render.attach_new_node("spheres")
sphere.copy_to(spheres).set_pos(1.5, 0, 0)
sphere.copy_to(spheres).set_pos(0, 1.5, 0)
sphere.copy_to(spheres).set_pos(-0.75, -0.75, 1.5)
spheres.set_scale(0.5)
spheres.set_y(-1)
Sequence(
spheres.posInterval(0.7, (0, -3, 0)),
spheres.posInterval(0.7, (0, -1, 0))
).loop()
spheres.hprInterval(1, (360, 360, 360)).loop()
render.set_shader(shader)
base.trackball.node().set_pos(0, 6, 0)
base.trackball.node().set_hpr(0, -60, 30)
base.accept('a', lambda: render.set_shader_auto(True))
base.accept('s', lambda: render.set_shader(shader))
base.accept('n', lambda: render.clear_shader())
base.accept('tab', base.bufferViewer.toggleEnable)
base.run()
```

Sorry that it’s a bit messy and unoptimized - I used this for testing the built-in GLSL shadow inputs and comparing the results against the equivalent autoshader (enabled using A).

In 1.9, there is still p3d_LightSourceParameters, but not p3d_LightSource[n]. You would have to declare a custom input as p3d_LightSourceParameters and then you can pass a NodePath pointing to a light to setShaderInput. [see] However, the shadowMatrix and shadowMap members are missing in 1.9.

As for setShaderInput being slow, I wouldn’t worry about it. Since you can pass a NodePath to setShaderInput, there shouldn’t be a need to call it every frame. tobspr’s use cases typically involve tens of thousands of shader input changes in a frame, where the overhead starts to become noticeable.