different shadowmap
shaodmwap基本原理 shadowmap
PCF
核心理解
核心理解大概是想通在shadowmap这张图对应每个位置(coord.xy)随机周围取点, coord.xy + poissondisk[i] * filtersize这样 (先是课上讲过的问题,不是step模糊也不是检测边缘模糊这种。) shadow map 每个位置(coords.xy)存的是"该处离光源最近的深度"。PCF 做的事是:
以
coords.xy为中心,在周围随机取 N 个点做深度比较,把 N 次 0/1 结果取平均,得到 0~1 的连续 visibility。
采样点位置:
shadowmap:

glsl
coords.xy + poissonDisk[i] * filterSizenote:
- 不是对
step的结果做模糊; - 也不是"检测阴影边缘再做模糊";
- 而是对 shadow map 本身多采样(每个采样点独立做一次遮挡比较),再对比较结果求平均。
只有阴影边界附近的点才会采样到"一半是遮挡、一半不是"的情况,平均出来是灰 → 软边缘。完全在阴影内/外的点,所有采样结果一致,平均还是 0 或 1。
采样点生成(均匀圆盘采样)
glsl
void uniformDiskSamples( const in vec2 randomSeed ) {
float randNum = rand_2to1(randomSeed);
float sampleX = rand_1to1( randNum );
float sampleY = rand_1to1( sampleX );
float angle = sampleX * PI2;
float radius = sqrt(sampleY);
for( int i = 0; i < NUM_SAMPLES; i ++ ) {
poissonDisk[i] = vec2( radius * cos(angle), radius * sin(angle) );
sampleX = rand_1to1( sampleY );
sampleY = rand_1to1( sampleX );
angle = sampleX * PI2;
radius = sqrt(sampleY);
}
}- 用
coords.xy作随机种子 → 每个像素的采样模式固定(不会每帧闪烁)。 - 半径范围
0 ~ 1(UV 单位),实际使用时要乘filterSize缩小。
滤波循环(每点一次比较,求平均)
glsl
float PCF(sampler2D shadowMap, vec4 coords) {
float z[NUM_SAMPLES];
float tmp[NUM_SAMPLES];
float sum = 0.0;
uniformDiskSamples(coords.xy);
for(int i = 0; i < NUM_SAMPLES; ++i){
z[i] = unpack(texture2D(shadowMap, coords.xy + poissonDisk[i] * uFilterScale));
tmp[i] = step(coords.z - uBias, z[i]);
sum += tmp[i];
}
return sum / float(NUM_SAMPLES);
}逐行含义:
| 代码 | 含义 |
|---|---|
coords.xy + poissonDisk[i] * uFilterScale | 第 i 个采样点的 UV = 中心 + 偏移 × 滤波半径 |
unpack(texture2D(...)) | 读出该 texel 存的最近深度 z[i] |
step(coords.z - uBias, z[i]) | 该采样点的遮挡判定(bias 容忍量化误差) |
sum / float(NUM_SAMPLES) | N 次判定取平均 → 0~1 visibility |
偏移量的单位(texel space)
shadow map 是 2048 × 2048,一个 texel 在 UV 空间约 1/2048 ≈ 0.0005。
uFilterScale = 0.003≈ 6 个 texel 的滤波半径。- 偏移必须乘在偏移上(
poissonDisk[i] * scale),不能乘在采样结果(深度值)上——乘错对象会导致深度被破坏、全图噪声。

PCSS
总体思路
理解大概也是是以coords.xy圆心的的周围某个半径(我用的numrings)类去查下周围的点, 然后avg(shadowmapz)(if block), 再根据avg计算prenumbra 再pcf(prenumbra~filtersize) -> 最终visibility
以 coords.xy 为圆心,在固定半径内查周围点 → 求 blocker 的平均深度 → 由平均深度算半影宽度 → 用半影宽度做 PCF 的滤波半径 → 得到最终 visibility。
STEP 1: 找 blocker,求平均深度 avgBlocker
STEP 2: 由 avgBlocker 算半影宽度 r(纯算术,不采样)
STEP 3: 用 r 做滤波半径跑 PCF → 最终 visibilitySTEP 1:平均 blocker 深度
glsl
float radius = float(NUM_RINGS) * 0.5;
for(int i = 0; i < NUM_RINGS; ++i){
z[i] = unpack(texture2D(shadowMap, coords.xy + (float(i) - radius) * 0.01));
if(findBlocker(shadowMap, coords.xy + (float(i) - radius) * 0.01, coords.z) == 1.){
sum += z[i]; // 累加 blocker 的深度
count += 1.; // 计数
}
}
if(count == 0.) return 1.; // 周围没有遮挡物 → 完全被照亮
avgblocker += sum / count; // blocker 平均深度要点:
- 搜索范围:
(float(i) - radius) * 0.01,i 从 0 到NUM_RINGS-1,覆盖中心两侧的网格点。 - blocker 判定:
z < zReceiver(shadow map 里的深度比接收面浅 → 有东西挡在前面)。 - 读深度和判 blocker 必须在同一个采样点(同一 UV),否则平均出来的深度与判定脱节。
count == 0说明该片元完全没被挡 → 直接返回 1(亮),跳过后续。
STEP 2:半影宽度
glsl
r = (coords.z - avgblocker) / avgblocker * lightWidth;相似三角形关系:
- 接收面离 blocker 越远(分子越大)→ 半影越宽 → 阴影边缘越软。
- 接收面贴着 blocker(分子→0)→ 半影→0 → 阴影边缘锐利(接触点处)。
STEP 3:用半影宽度做 PCF
glsl
sum = 0.; // 重要:复用 sum 前必须清零
uniformDiskSamples(coords.xy);
for(int i = 0; i < NUM_SAMPLES; ++i){
z[i] = unpack(texture2D(shadowMap, coords.xy + poissonDisk[i] * r * uFilterScale));
tmp[i] = step(coords.z - uBias, z[i]);
sum += tmp[i];
}
return sum / float(NUM_SAMPLES);- 与 PCF 结构完全相同,只是滤波半径变成了
r * uFilterScale(半影宽度 × 基础尺度)。 - 半影宽度随距离变化 → 阴影软硬随距离变化
完整函数
glsl
float PCSS(sampler2D shadowMap, vec4 coords){
float z[NUM_RINGS];
float tmp[NUM_RINGS];
float sum = 0.0;
float avgblocker = 0.0;
float r = 0.0;
float count = 0.;
float lightWidth = 10.;
float radius = float(NUM_RINGS) * 0.5;
// STEP 1: avgblocker depth
for(int i = 0; i < NUM_RINGS; ++i){
z[i] = unpack(texture2D(shadowMap, coords.xy + (float(i) - radius) * 0.01));
if(findBlocker(shadowMap, coords.xy + (float(i) - radius) * 0.01, coords.z) == 1.){
sum += z[i];
count += 1.;
}
}
if(count == 0.) return 1.;
avgblocker += sum / count;
// STEP 2: penumbra size
r = (coords.z - avgblocker) / avgblocker * lightWidth;
// STEP 3: filtering
sum = 0.;
uniformDiskSamples(coords.xy);
for(int i = 0; i < NUM_SAMPLES; ++i){
z[i] = unpack(texture2D(shadowMap, coords.xy + poissonDisk[i] * r * uFilterScale));
tmp[i] = step(coords.z - uBias, z[i]);
sum += tmp[i];
}
return sum / float(NUM_SAMPLES);
}