///////////////////////////////////////////////////////////////////////////////// // // shadowreceiverfp.cg // // Hamilton Chong // (c) 2006 // // This is an example fragment shader for shadow receiver objects. // ///////////////////////////////////////////////////////////////////////////////// sampler2D ShadowMap : TEXUNIT0; // Define outputs from vertex shader. struct Vertex { float4 position : POSITION; // fragment position in post projective space float4 shadowCoord : TEXCOORD0; // fragment position in shadow map coordinates float diffuse : TEXCOORD1; // diffuse shading value }; struct Fragment { float4 color : COLOR0; }; Fragment main(Vertex In, uniform float uSTexWidth, uniform float uSTexHeight) { Fragment Out; // compute the shadow coordinates for texture lookup // NOTE: texture_viewproj_matrix maps z into [0,1] range, not [-1,1], so // have to make sure shadow caster stores depth values with same convention. float4 scoord = In.shadowCoord / In.shadowCoord.w; // -- Bilinear Filtering of Sample -------------------------------------------- // One could use scoord.xy to look up the shadow map for depth testing, but // we'll be implementing a simple "percentage closest filtering" algorithm instead. // This mimics the behavior of turning on bilinear filtering on NVIDIA hardware // when also performing shadow comparisons. This causes bilinear filtering of // depth tests. Note that this is NOT the same as bilinear filtering the depth // values and then doing the depth comparison. The two operations are not // commutative. PCF is explicitly about filtering the test values since // testing filtered z values is often meaningless. // Real percentage closest filtering should sample from the entire footprint // on the shadow map, not just seek the closest four sample points. Such // an improvement is for future work. // NOTE: Assuming OpenGL convention for texture lookups with integers in centers. // DX convention is to have integers mark sample corners float2 tcoord; tcoord.x = (scoord.x * uSTexWidth) - 0.5; tcoord.y = (scoord.y * uSTexHeight) - 0.5; float x0 = floor(tcoord.x); float x1 = ceil(tcoord.x); float fracx = frac(tcoord.x); float y0 = floor(tcoord.y); float y1 = ceil(tcoord.y); float fracy = frac(tcoord.y); // sample coordinates in [0,1]^2 domain float2 t00, t01, t10, t11; float invWidth = 1.0 / uSTexWidth; float invHeight = 1.0 / uSTexHeight; t00 = float2((x0+0.5) * invWidth, (y0+0.5) * invHeight); t10 = float2((x1+0.5) * invWidth, (y0+0.5) * invHeight); t01 = float2((x0+0.5) * invWidth, (y1+0.5) * invHeight); t11 = float2((x1+0.5) * invWidth, (y1+0.5) * invHeight); // grab the samples float2 z00 = tex2D(ShadowMap, t00).xy; float2 z01 = tex2D(ShadowMap, t01).xy; float2 z10 = tex2D(ShadowMap, t10).xy; float2 z11 = tex2D(ShadowMap, t11).xy; // bilinear filter the sample data float2 d0 = ((1.0 - fracx) * z00) + (fracx * z10); float2 d1 = ((1.0 - fracx) * z01) + (fracx * z11); float2 datum = ((1.0 - fracy) * d0) + (fracy * d1); // -- Variance Shadow Mapping --------------------------------------------------- float zVariance = datum.y - (datum.x * datum.x); float zDeviation = scoord.z - datum.x; zDeviation = (zDeviation < 0.0) ? 0.0 : zDeviation; float visibility = zVariance / (zVariance + (zDeviation * zDeviation)); float ztest = (scoord.z < datum.x) ? 1.0:0.0; // filtering depth ok, because used only for small variance visibility = (zVariance > 0.0) ? visibility : ztest; // if variance too small, we get garbage //0.0000001 // determine that all geometry within pixel border of shadow map (and outside) is lit float filterBorder = max(invWidth, invHeight); visibility = (all(abs(scoord.xy-0.5)<=0.5-filterBorder)) ? visibility : 1.0; // ------------------------------------------------------------------------------ visibility *= In.diffuse; Out.color = float4(visibility, visibility, visibility, 0.0); return Out; }