Shadows made easy

For some time now Panda3D make it easy to have shadows for default lights, not only for a setup using the Auto-Shader but also for custom GLSL shaders. I think this feature is not advertised enough, so I made a tiny sample that uses the depth map and matrix provided by p3d (with some extra blur to make low res shadow maps cute)

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from direct.gui.DirectGui import *

class World():
    def __init__(self):         
        #place the camera
        base.trackball.node().setPos(0, 15, 1)    
        base.trackball.node().setHpr(0, 40, 0) 
        
        #the shaders...
        #vertex shader:
        v_shader='''#version 140

                    struct p3d_LightSourceParameters {
                      vec4 color;
                      vec3 spotDirection; 
                      sampler2DShadow shadowMap; 
                      mat4 shadowMatrix;
                    }; 

                    uniform p3d_LightSourceParameters my_light;
                    uniform mat4 p3d_ModelViewProjectionMatrix;
                    uniform mat3 p3d_NormalMatrix;

                    in vec4 p3d_Vertex;
                    in vec3 p3d_Normal;
                    in vec2 p3d_MultiTexCoord0;

                    out vec2 uv;
                    out vec4 shadow_uv;
                    out vec3 normal;

                    void main()
                        {
                        //position    
                        gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;      
                        //normal      
                        normal = p3d_NormalMatrix * p3d_Normal;
                        //uv
                        uv = p3d_MultiTexCoord0;
                        //shadows
                        shadow_uv = my_light.shadowMatrix * p3d_Vertex;
                        }'''
        #fragment shader
        f_shader='''#version 140

                struct p3d_LightSourceParameters {
                  vec4 color;
                  vec3 spotDirection; 
                  sampler2DShadow shadowMap; 
                  mat4 shadowMatrix;
                }; 

                uniform p3d_LightSourceParameters my_light;
                uniform sampler2D p3d_Texture0;
                uniform vec3 camera_pos;
                uniform float shadow_blur;

                in vec2 uv;
                in vec4 shadow_uv;
                in vec3 normal;

                out vec4 color;

                float textureProjSoft(sampler2DShadow tex, vec4 uv, float bias, float blur)
                    {
                    float result = textureProj(tex, uv, bias);
                    result += textureProj(tex, vec4(uv.xy + vec2( -0.326212, -0.405805)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(-0.840144, -0.073580)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(-0.695914, 0.457137)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(-0.203345, 0.620716)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(0.962340, -0.194983)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(0.473434, -0.480026)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(0.519456, 0.767022)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(0.185461, -0.893124)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(0.507431, 0.064425)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(0.896420, 0.412458)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(-0.321940, -0.932615)*blur, uv.z-bias, uv.w));
                    result += textureProj(tex, vec4(uv.xy + vec2(-0.791559, -0.597705)*blur, uv.z-bias, uv.w));
                    return result/13.0;
                    }    

                void main()
                    {
                    //base color
                    vec3 ambient=vec3(0.1, 0.1, 0.2);    
                    //texture        
                    vec4 tex=texture(p3d_Texture0, uv);        
                    //light ..sort of, not important
                    vec3 light=my_light.color.rgb*max(dot(normalize(normal),-my_light.spotDirection), 0.0);
                    
                    //shadows
                    //float shadow= textureProj(my_light.shadowMap,shadow_uv); //meh :|
                    float shadow= textureProjSoft(my_light.shadowMap, shadow_uv, 0.0001, shadow_blur);//yay! :)
                    
                    //make the shadow brighter
                    shadow=0.5+shadow*0.5;
                    
                    color=vec4(tex.rgb*(light*shadow+ambient), tex.a);
                    
                    }'''                    
        shader = Shader.make(Shader.SL_GLSL,v_shader, f_shader)
                
        #make some floor
        cm = CardMaker('')
        cm.set_frame(-10, 10, -10, 10)        
        floor=render.attach_new_node(cm.generate())
        floor.set_p(-90)
        #set a texture
        floor.set_texture(loader.load_texture('maps/grid.rgb'))
        floor.set_shader(shader)
        
        #load some model
        self.panda=Actor('panda-model', {'walk': 'panda-walk4'})
        self.panda.reparent_to(render)
        self.panda.set_scale(0.005)
        self.panda.loop('walk')
        self.panda.set_shader(shader)
                        
        #light
        my_light = render.attach_new_node(Spotlight("Spot"))
        my_light.node().set_shadow_caster(True, 512, 512)
        my_light.node().set_color((0.9, 0.9, 0.8, 1.0))
        #my_light.node().showFrustum()
        my_light.node().get_lens().set_fov(40)
        my_light.node().get_lens().set_near_far(0.1, 30)
        render.setLight(my_light)
        my_light.set_pos(-20, 0, 20)
        my_light.look_at(0, 0, 0)
        render.set_shader_input('my_light',my_light)
        render.set_shader_input('shadow_blur',0.2)
        
        #make a slider to change the softness
        self.slider = DirectSlider(range=(0, 0.5), value=0.2, scale=0.5, pos=(-0.8,0.0,0.9), command=self.set_softness)
        
    def set_softness(self):
        v=float(self.slider['value'])
        render.set_shader_input('shadow_blur', v)
        
            
if __name__ == '__main__':
    base = ShowBase()
    w = World()
    base.run()        

#edit: removed unneeded //GLSL and made the bias in the shader work as a depth offset not a mipmap bias

2 Likes

Yeah, having some sample code about doing shadows in GLSL is a good idea - esp. in 1.10 it is much simpler than it seems at first. Would you be okay with including something like this in the sample programs?

Sure, feel free to use it if you think it’s good enough for the official samples. The code in the first post is from now on officially CC0/Public Domain.

shadowMatrix has now been deprecated; please use shadowViewMatrix instead to transform from view-space coordinates, which is more efficient.

So, concerning shadowViewMatrix, it would be like:
shadow_uv=light.shadowViewMatrix * p3d_Vertex?
…or?

No, I think it would be:

my_light.shadowViewMatrix * (p3d_ModelViewMatrix * p3d_Vertex)

O’courseeee! Thanks