Thanks.
I’ve made a few mods, but there are still a few issues. Unfortunately, I don’t have time to polish it up ATM.
I’ve tossed out the blur stage since it slows it down a lot, doesn’t add much, and introduces other problems.
The shadows are now rendered white by shadowonly2.sha, and shadowcombiner.sha, now just subtracts a fraction of the “light” colour.
Anyway here’s the shadowManager class, which is just a slight modification of pro-rsoft’s.
from pandac.PandaModules import GraphicsOutput, Texture, NodePath, Vec3
from pandac.PandaModules import PandaNode, WindowProperties, GraphicsPipe
from pandac.PandaModules import ShaderGenerator, Shader
from pandac.PandaModules import FrameBufferProperties, Vec4
from direct.filter.FilterManager import FilterManager
class ShadowManager():
"""This class manages shadows for a scene."""
def __init__(self, scene = base.render, fov = 40, near = 10, far = 100):
self.scene = scene
# Create the depth buffer plus a texture to store the output in
depthbuffer = self.createOffscreenBuffer(-3)
self.depthmap = Texture()
depthbuffer.addRenderTexture(self.depthmap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
# Create the shadow buffer plus a texture to store the output in
shadowbuffer = self.createOffscreenBuffer(-2)
self.shadowmap = Texture()
shadowbuffer.addRenderTexture(self.shadowmap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
manager = FilterManager(base.win, base.cam)
# blend shadow map with render output
self.rendertex = Texture()
renderquad = manager.renderSceneInto(colortex=self.rendertex)
renderquad.setShader(Shader.load("shadowcombiner.sha"))
renderquad.setShaderInput("render", self.rendertex)
renderquad.setShaderInput("shadow", self.shadowmap)
renderquad.setShaderInput("light", 1,1,1,1)
# Make the shadow caster camera
self.castercam = base.makeCamera(depthbuffer)
self.castercam.node().setScene(self.scene)
self.castercam.node().getLens().setFov(fov)
self.castercam.node().getLens().setNearFar(near, far)
# Put a shader on the shadow caster camera.
lci = NodePath(PandaNode("lightCameraInitializer"))
lci.setShader(loader.loadShader("caster.sha"))
self.castercam.node().setInitialState(lci.getState())
# Make the shadow camera
self.shadowcam = base.makeCamera(shadowbuffer, lens=base.cam.node().getLens())
# Put a shader on the shadow camera.
lci = NodePath(PandaNode("lightCameraInitializer"))
lci.setShader(loader.loadShader("shadowonly2.sha"))
self.shadowcam.node().setInitialState(lci.getState())
self.shadowcam.reparentTo(base.cam)
# Set the shader inputs
self.scene.setShaderInput("light", self.castercam)
self.scene.setShaderInput("depthmap", self.depthmap)
# These are two functions which help creating two different kind of offscreen buffers.
def createOffscreenBuffer(self, sort):
winprops = WindowProperties.size(1024,1024)
props = FrameBufferProperties()
props.setRgbColor(1)
props.setAlphaBits(1)
props.setDepthBits(1)
return base.graphicsEngine.makeOutput(
base.pipe, "offscreenBuffer",
sort, props, winprops,
GraphicsPipe.BFRefuseWindow,
base.win.getGsg(), base.win)
Here’s shadowonly2.sha:
//Cg
void vshader(
float4 vtx_position : POSITION,
float3 vtx_normal : NORMAL,
uniform float4x4 trans_model_to_clip_of_light,
uniform float4x4 mat_modelproj,
uniform float4 mspos_light,
out float4 l_back,
out float4 l_position : POSITION,
out float4 l_shadowcoord : TEXCOORD0,
out float4 l_lightclip : TEXCOORD1
)
{
float4 position = vtx_position;
// vertex position
l_position = mul(mat_modelproj, position);
// backface?
l_back = dot(vtx_normal, normalize(mspos_light - position));
// Calculate light-space clip position.
float4 pushed = position + float4(vtx_normal*0.2, 0);
l_lightclip = mul(trans_model_to_clip_of_light, pushed);
// Calculate shadow-map texture coordinates.
l_shadowcoord = l_lightclip * float4(0.5,0.5,0.5,1.0) + l_lightclip.w * float4(0.5,0.5,0.5,0.0);
}
float2 poissonDisk[16] = {
float2( -0.94201624, -0.39906216 ),
float2( 0.94558609, -0.76890725 ),
float2( -0.094184101, -0.92938870 ),
float2( 0.34495938, 0.29387760 ),
float2( -0.91588581, 0.45771432 ),
float2( -0.81544232, -0.87912464 ),
float2( -0.38277543, 0.27676845 ),
float2( 0.97484398, 0.75648379 ),
float2( 0.44323325, -0.97511554 ),
float2( 0.53742981, -0.47373420 ),
float2( -0.26496911, -0.41893023 ),
float2( 0.79197514, 0.19090188 ),
float2( -0.24188840, 0.99706507 ),
float2( -0.81409955, 0.91437590 ),
float2( 0.19984126, 0.78641367 ),
float2( 0.14383161, -0.14100790 )
};
float PCF_Filter( float2 uv, float z, float r, sampler2D map ){
float sum = 0.0f;
for ( int i = 0; i < 8; ++i ){
float2 offset = poissonDisk[i] * r;
float mapval = tex2D(map,uv+offset);
float diff = z-mapval;
sum += (diff>0);
}
return saturate(sum / 8.0);
}
void fshader(
in float4 l_shadowcoord : TEXCOORD0,
in float4 l_lightclip : TEXCOORD1,
in float l_back,
uniform sampler2D k_depthmap : TEXUNIT0,
out float4 o_color:COLOR
)
{
o_color = 0.0;
if (l_back>0.0){
float3 circleoffs = float3(l_lightclip.xy / l_lightclip.w, 0);
float falloff = saturate(1.0 - dot(circleoffs, circleoffs));
float4 proj = l_shadowcoord / l_shadowcoord.w;
float mapval = tex2D(k_depthmap,proj.xy);
float diff = proj.z-mapval;
float shade = (diff>0) ? PCF_Filter(proj.xy,proj.z,0.2*diff,k_depthmap) : 0;
o_color = falloff * shade;
}
}
And here’s shadowcombiner.sha
//Cg
//
//Cg profile arbvp1 arbfp1
void vshader(
float4 vtx_position : POSITION,
float2 vtx_texcoord0 : TEXCOORD0,
out float4 l_position : POSITION,
out float2 l_texcoord0 : TEXCOORD0,
out float2 l_texcoord1 : TEXCOORD1,
uniform float4 texpad_render,
uniform float4x4 mat_modelproj
)
{
l_position=mul(mat_modelproj, vtx_position);
l_texcoord0 = vtx_position.xz * texpad_render.xy + texpad_render.xy;
l_texcoord1 = vtx_texcoord0;
}
void fshader(
float2 l_texcoord0 : TEXCOORD0,
float2 l_texcoord1 : TEXCOORD1,
out float4 o_color : COLOR,
uniform float4 k_light,
uniform sampler2D k_render : TEXUNIT0,
uniform sampler2D k_shadow : TEXUNIT1
)
{
o_color = saturate(tex2D(k_render, l_texcoord0) - 0.2 * k_light * tex2D(k_shadow, l_texcoord1));
}
Let me know if you have any problems getting it to work… and any improvements you come up with.