最近在Piccolo引擎上实现了PCF与PCSS, 虽然绘制结果中存在着大量的噪点, 但还是想着记录一下实现的流程~
Piccolo引擎实现阴影绘制的代码主要在deferred_lighting.frag中, 其引用了mesh_lighting.inl. 自己是把PCF与PCSS相关的接口都放在了一个新增的头文件utilities.h中, 由mesh_lighting.inl调用绘制阴影. 调用代码如下所示:
shadow = (closest_depth < current_depth) ? 1.0f : -1.0f;
if (shadow <= 0.0f)
{
highp vec3 En = scene_directional_light.color * NoL;
Lo += BRDF(L, V, N, F0, basecolor, metallic, roughness) * En;
}
else
{
highp vec4 coords = vec4(uv.x, uv.y, position_clip.z, 1.0f);
highp float visibility = PCSS(directional_light_shadow, coords);
La *= visibility;
Libl *= visibility;
}
1. PCF
入口函数为
highp float PCF(in sampler2D shadowMap, in highp vec4 coords)
{
highp vec2 uv = coords.xy;
highp float zReceiver = coords.z; // Assumed to be eye-space z in this code
poissonDiskSamples(uv);
return PCF_Filter(shadowMap, uv, zReceiver, 0.002);
}
$\cdot$ Step 1: Poisson圆盘采样
根据UV值进行Poisson圆盘采样, 得到一组用于ShadowMap采样的UV偏移量
void poissonDiskSamples(const in highp vec2 randomSeed)
{
highp float ANGLES_STEP = PI2 * float(NUM_RINGS) / float(NUM_SAMPLES);
highp float INV_NUM_SAMPLES = 1.0 / float(NUM_SAMPLES);
highp float angle = rand_2to1(randomSeed) * PI2;
highp float radius = INV_NUM_SAMPLES;
highp float radiusStep = radius;
for (int i = 0; i < NUM_SAMPLES; ++i)
{
poissonDisk[i] = vec2(cos(angle), sin(angle)) * pow(radius, 0.75);
radius += radiusStep;
angle += ANGLES_STEP;
}
}
期间, 会将UV值作为随机数种子, 与一些Magic Number进行运算后得到一个特定的采样角度.
highp float rand_2to1(highp vec2 uv)
{
// 0 -1
const highp float a = 12.9898, b = 78.233, c= 43758.5453;
highp float dt = dot(uv.xy, vec2(a, b));
highp float sn = mod(dt, PI);
return fract(sin(sn) * c);
}
$\cdot$ Step 2: PCF
利用Poisson圆盘采样得到的一组UV偏移量, 进行PCF.
highp float PCF_Filter(in sampler2D shadowMap, highp vec2 uv, highp float zReceiver, highp float filterRadius)
{
highp float sum = 0.0;
for (int i = 0; i < PCF_NUM_SAMPLES; ++i)
{
highp float depth = unpack(texture(shadowMap, uv + poissonDisk[i] * filterRadius));
if (zReceiver <= depth)
{
sum += 1.0;
}
}
for (int i = 0; i < PCF_NUM_SAMPLES; ++i)
{
highp float depth = unpack(texture(shadowMap, uv + -poissonDisk[i].yx * filterRadius));
if (zReceiver <= depth)
{
sum += 1.0;
}
}
return sum / (2.0 * float(PCF_NUM_SAMPLES));
}
2. PCSS
入口函数为
highp float PCSS(in sampler2D shadowMap, in highp vec4 coords)
{
highp vec2 uv = coords.xy;
highp float zReceiver = coords.z; // Assumed to be eye-space z in this code
// STEP 1: blocker search
poissonDiskSamples(uv);
highp float avgBlockerDepth = findBlocker(shadowMap, uv, zReceiver);
// There are no occluders so early out(this saves filtering)
if (avgBlockerDepth == -1.0)
{
return 1.0;
}
// STEP 2: penumbra size
highp float penumbraRatio = penumbraSize(zReceiver, avgBlockerDepth);
highp float filterSize = penumbraRatio * LIGHT_SIZE_UV * NEAR_PLANE / zReceiver;
// STEP 3: filtering
// return avgBlockerDepth;
return PCF_Filter(shadowMap, coords.xy, zReceiver, filterSize);
}
$\cdot$ Step 1: 寻找遮挡物
通过findBlocker函数得到当前Shading Point的所有遮挡物的平均深度.
highp float findBlocker(in sampler2D shadowMap, highp vec2 uv, highp float zReceiver)
{
// This uses similar triangles to compute what
// area of the shadow map we should search
highp float searchRadius = LIGHT_SIZE_UV * (zReceiver - NEAR_PLANE) / zReceiver;
highp float blockerDepthSum = 0.0;
highp int numBlockers = 0;
for (int i = 0; i < BLOCKER_SEARCH_NUM_SAMPLES; ++i)
{
highp float shadowMapDepth = unpack(texture(shadowMap, uv + poissonDisk[i] * searchRadius));
if (shadowMapDepth < zReceiver)
{
blockerDepthSum += shadowMapDepth;
++numBlockers;
}
}
if (numBlockers == 0)
{
return -1.0;
}
return blockerDepthSum / float(numBlockers);
}
$\cdot$ Step 2: 计算半影大小
highp float penumbraSize(highp float zReceiver, highp float zBlocker)
{
// Parallel plane estimation
return (zReceiver - zBlocker) / zBlocker;
}
highp float penumbraRatio = penumbraSize(zReceiver, avgBlockerDepth);
highp float filterSize = penumbraRatio * LIGHT_SIZE_UV * NEAR_PLANE / zReceiver;
原理可参考下图.

$\cdot$ Step 3: Filtering
将上述步骤得到的filterSize传递给PCF_Filter函数.
最终, 应用了PCSS的阴影绘制结果如下图所示. 可以看出, 阴影边缘处存在着大量的噪点, 后面如果有机会, 再加个降噪的Pass叭~

完整代码可参考: Piccolo with PCF and PCSS.