Shadow Map ———— PCSS(percentage-closer Soft Shadow)

来源:互联网 发布:淘宝怎么改评价 编辑:程序博客网 时间:2024/06/01 10:18

 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;

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

0 0
原创粉丝点击