?Shadow Map ?———— PCSS(percentage-closer Soft Shadow)
?
1、PCSS解决了什么问题
传统的PCF每一次采样过滤耗费很大(每次都要遍历附近的几个点,虽然用了泊松分布,但还是不可避免),PCSS算法基本解决了该问题(通过动态计算采样范围,使用FindBlocker剔除非阴影点)
传统的PCF半影不够逼真…PCSS算法通过计算准确的半影范围解决了
4、PCSS的有关你资料:
http://www.cg.tuwien.ac.at/research/publications/2013/SCHWAERZLER-2013-FPCSS/SCHWAERZLER-2013-FPCSS-draft.pdf
http://developer.download.nvidia.com/presentations/2008/GDC/GDC08_SoftShadowMapping.pdf
http://developer.download.nvidia.com/SDK/10.5/direct3d/Source/PercentageCloserSoftShadows/doc/PercentageCloserSoftShadows.pdf
http://developer.download.nvidia.com/shaderlibrary/docs/shadow_PCSS.pdf
http://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf
https://developer.nvidia.com/gameworks-directx-samples(本文参照的DEMO,soft shadow)
?
PCSS算法解释:
?
第一步渲染shadowmap
Z_VSOut LightRender_VS ( Geometry_VSIn IN )
{
Z_VSOut OUT;
float4 WorldPos = mul(IN.Pos, g_world);
OUT.HPosition = mul(WorldPos, g_lightViewProj);
return OUT;
}
?
第二步、ZPrePass,减少多余的绘制
Geometry_VSOut EyeRender_VS (Geometry_VSIn IN)
{
Geometry_VSOut OUT;
float4 WorldPos = mul(IN.Pos, g_world);
OUT.HPosition = mul(WorldPos, g_viewProj);
OUT.WorldPos = WorldPos;
OUT.Normal = IN.Normal;
OUT.LightPos = mul(WorldPos, g_lightViewProjClip2Tex);
return OUT;
}
DepthStencilState ZTestLess_DS
{
DepthEnable = TRUE;
DepthFunc = LESS;
DepthWriteMask = ALL;
};
第三步:渲染场景
Geometry_VSOut EyeRender_VS (Geometry_VSIn IN)
{
Geometry_VSOut OUT;
float4 WorldPos = mul(IN.Pos, g_world);?????//将顶点转到光源摄像机的世界矩阵
OUT.HPosition = mul(WorldPos, g_viewProj);
OUT.WorldPos = WorldPos;
OUT.Normal = IN.Normal;
OUT.LightPos = mul(WorldPos, g_lightViewProjClip2Tex);? ?????//将顶点转到对应的shadowmap:
????????????????????????????????????????????????????????????????????????????????????????????????? ??//将顶点转到光源摄像机的WVP后,此时顶点:(-1,1),为了转到shadowmap纹理坐标(0,1),还需要乘以一个Clip2Tex矩阵
return OUT;
}
float4 EyeRender_PS (uniform int shadowTechnique, Geometry_VSOut IN) : SV_Target
{
float2 uv = IN.LightPos.xy / IN.LightPos.w;??//该顶点对应的shadowmap像素
float z = IN.LightPos.z / IN.LightPos.w;????? ??//深度的计算,在PS而不在VS是为了不然插值导致误差
float2 dz_duv = DepthGradient(uv, z);????? ? ?//为了解决Depth Bias问题(待会儿再说)
float4 color = Shade(IN.WorldPos, IN.Normal);? ??//计算颜色,根据法线,光照,纹理等一堆东西计算,简单的光照模型
if (IsBlack(color.rgb)) return color;????????????????????? ??//如果颜色本身就是黑色…呵呵,直接做阴影
// Eye-space z from the light’s point of view
float zEye = mul(IN.WorldPos, g_lightView).z;? ??//顶点的z转换到光源的View位置(相对于光源的位置),z*WV(还没有进行投影)
float shadow = 1.0f;
switch (shadowTechnique)
{
case 1:
shadow = PCSS_Shadow(uv, z, dz_duv, zEye);????? ??//PCSS….
break;
case 2:
shadow = PCF_Shadow(uv, z, dz_duv, zEye);????? ??//这是PCF忽略
break;
}
return color * shadow;?????????
}
float PCSS_Shadow(float2 uv, float z, float2 dz_duv, float zEye)
{
// ————————
// STEP 1: blocker search
// ————————
float accumBlockerDepth = 0;
float numBlockers = 0;
//根据顶点到光源的距离,动态计算需要采样的范围(相似三角形如图)
//float2 SearchRegionRadiusUV(float zWorld)
//{
//return g_lightRadiusUV * (zWorld - g_lightZNear) / zWorld;
//}
float2 searchRegionRadiusUV = SearchRegionRadiusUV(zEye);
?
//寻找最合适的遮挡物,这个感觉对PCF没有太多用途…
//把目标点变换到Light space之后,找到周围点中的遮挡该目标点的点,记录其与光源的距离。
//在搜索了一定的遮挡点之后,我们会根据这些遮挡点计算出一个平均遮挡距离。如果目标点不在阴影中,平均遮挡距离为0,PCSS算法直接返回1.0。
FindBlocker(accumBlockerDepth, numBlockers, g_shadowMap, uv, z, dz_duv, searchRegionRadiusUV);
// Early out if not in the penumbra
if (numBlockers == 0)? ? //完全没有遮挡物
return 1.0;
else if (numBlockers == BLOCKER_SEARCH_COUNT)? ? //完全被遮挡
return 0.0;
// ————————
// STEP 2: penumbra size
// ————————
//取遮挡点的平均值:为何要选取多个遮挡点:
float avgBlockerDepth = accumBlockerDepth / numBlockers;?? ?
//现在的avgBlockerDepth在0~1之间,我们要将它映射到Znear~Zfar
float avgBlockerDepthWorld = ZClipToZEye(avgBlockerDepth);
//计算半影大小:
//float2 PenumbraRadiusUV(float zReceiver, float zBlocker)
//{
//return g_lightRadiusUV * (zReceiver - zBlocker) / zBlocker;
//}
float2 penumbraRadiusUV = PenumbraRadiusUV(zEye, avgBlockerDepthWorld);
//Clamp filter width to be >= MinRadius for antialiasing
//float2 ProjectToLightUV(float2 sizeUV, float zWorld)
//{
//return sizeUV * g_lightZNear / zWorld;
//}
float2 filterRadiusUV = ProjectToLightUV(penumbraRadiusUV, zEye);
// ————————
// STEP 3: filtering
// ————————
return PCF_Filter(uv, z, dz_duv, filterRadiusUV);
}
//进行PCF采样,一堆加起来
float PCF_Filter(float2 uv, float z0, float2 dz_duv, float2 filterRadiusUV)
{
float sum = 0;
for (int i = 0; i < PCF_POISSON_COUNT; ++i)
{
float2 offset = PCF_POISSON[i] * filterRadiusUV;????? ??//将半径乘以泊松分布的值就是偏移值
float z = BiasedZ(z0, dz_duv, offset);????? ??//处理Bias问题
?
//SampleCmpLevelZero:小于Z的才会采样?https://msdn.microsoft.com/zh-cn/library/windows/apps/dn263156.aspx
//
//SamplerComparisonState PCF_Sampler
//{
//ComparisonFunc = LESS;????这里决定了SampleCmpLevelZero的比较方法:则比较测试通过时固有函数会返回零;这表示像素位于阴影中。
//Filter = COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
//AddressU = Border;
//AddressV = Border;
//BorderColor = float4(MAX_LINEAR_DEPTH, 0, 0, 0);
//};
sum += g_shadowMap.SampleCmpLevelZero(PCF_Sampler, uv + offset, z);????? ??
}?
float2 stepUV = filterRadiusUV / PCF_FILTER_STEP_COUNT;
for (float x = -PCF_FILTER_STEP_COUNT; x <= PCF_FILTER_STEP_COUNT; ++x)
{
for (float y = -PCF_FILTER_STEP_COUNT; y <= PCF_FILTER_STEP_COUNT; ++y)
{
float2 offset = float2(x, y) * stepUV;
float z = BiasedZ(z0, dz_duv, offset);
sum += g_shadowMap.SampleCmpLevelZero(PCF_Sampler, uv + offset, z);
}
}
return sum / PCF_COUNT;????? ??//取得平均值
}
4、关于Depth bias问题(http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/Isidoro-ShadowMapping.pdf)
//float2 uv = IN.LightPos.xy / IN.LightPos.w; ?//该顶点对应的shadowmap像素
//float z = IN.LightPos.z / IN.LightPos.w;??????????//深度的计算,在PS而不在VS是为了不然插值导致误差
//计算改uv所处地方的z的变化情况(偏导数)
//ddx,ddy是系统的api用于算Screen Space的偏导数…
//这里就是利用传说中的雅可比矩阵将偏导数算出来~
float2 DepthGradient(float2 uv, float z)????
{
float2 dz_duv = 0;
float3 duvdist_dx = ddfloat3(uv,z));
float3 duvdist_dy = ddy(float3(uv,z));
dz_duv.x = duvdist_dy.y * duvdist_dx.z;
dz_duv.x -= duvdist_dx.y * duvdist_dy.z;
dz_duv.y = duvdist_dx.x * duvdist_dy.z;
dz_duv.y -= duvdist_dy.x * duvdist_dx.z;
float det = (duvdist_dx.x * duvdist_dy.y) - (duvdist_dx.y * duvdist_dy.x);
dz_duv /= det;
return dz_duv;
}
//利用z的偏导数动态计算准确的bias
float BiasedZ(float z0, float2 dz_duv, float2 offset)
{
return z0 + dot(dz_duv, offset);
}