Shadows made easy


#1

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


Shaders and Shadows Not Supported?
How to get correctly shadow Matrix
#2

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?


#3

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.


#4

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


#5

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


#6

No, I think it would be:

my_light.shadowViewMatrix * (p3d_ModelViewMatrix * p3d_Vertex)

#7

O’courseeee! Thanks