Shadow Map ———— PCF(percentage-closer filtering)

来源:互联网 发布:淘宝怎么改评价 编辑:程序博客网 时间:2024/05/29 11:39

Shadow Map ———— PCF(percentage-closer filtering)
图片稍后补上

1、PCF解决了什么问题
SSM生成的阴影会出现锯齿(shadow map的精度问题),没有AA。PCF实现了对shadowmap的AA

2、PCF如何实现AA
解决锯齿问题的方法有好多,其中最简单的就是在锯齿附近添加黑白之间的灰色,PCF就是实现了这一种方法。
在SSM算法中,物体的实际深度将会与shadowmap的深度进行比较,如果比shadowmap的深度小,则出现阴影。
这里有个问题,就是这种比较结果永远只有两种可能0或者1,这个导致SSM无法进行AA。
PCF利用采样的方法,对shadowmap的特定像素附近的像素进行采样,并取其平均值,这样就可以得到一个0~1的数,也就实现了AA

3、PCF的缺点:
1、无法提前做预处理,一旦做预处理(blur,插值),都会导致shadowmap的特定像素的深度出现误差,由于最后的结果是根据各个像素进行均值,所以这会导致最终阴影也出现误差
2、传统的PCF每一次采样过滤耗费很大(每次都要遍历附近的几个点,虽然用了泊松分布,但还是不可避免),PCSS算法的出现基本解决了该问题(通过动态计算采样范围,使用FindBlocker剔除非阴影点,额….在下面的PCF算法其实也基本实现了这两个)
3、半影不够逼真…PCSS算法也解决了(通过计算准确的半影范围??有待考究)…嘻嘻~

4、PCF的有关你资料:
http://www.eng.utah.edu/~cs5610/handouts/reeves87.pdf
http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html
http://developer.download.nvidia.com/presentations/2008/GDC/GDC08_SoftShadowMapping.pdf
http://developer.download.nvidia.com/SDK/9.5/Samples/samples.html(这里有DEMO,基本没用什么PCSS的方法)
https://developer.nvidia.com/gameworks-directx-samples(本文参照的DEMO,soft shadow)
Gpu Gem1中文版:就是第二条的中文版啦~

PCF算法解释:

第一步渲染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 PCF_Shadow(float2 uv, float z, float2 dz_duv, float zEye)
{
// Do a blocker search to enable early out
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 no blockers
if (numBlockers == 0)
return 1.0;

//g_lightRadiusUV:
//float lightRadiusUV[2] =
//{
//m_lightRadiusWorld / frustumWidth,
//m_lightRadiusWorld / frustumHeight
//};
//m_lightRadiusWorld:范围0~1
float2 filterRadiusUV = 0.1 * g_lightRadiusUV; //额…

return PCF_Filter(uv, z, dz_duv, filterRadiusUV);
}

//进行PCF采样,一堆加起来
float PCF_Filter(float2 uv, float z0, float2 dz_duv, float2 filterRadiusUV)
{
float sum = 0;

ifdef USE_POISSON //使用泊松分布?

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);
}

else

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);
}
}

endif

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 = ddx(float3(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);
}

0 0
原创粉丝点击