pcss 软阴影

PCF

Percentage Closer Filtering:PCF是一种用于阴影反锯齿的方法,本身不是软阴影方法。

算法流程说明:

  1. Perform multiple (e.g. 7x7) depth
    comparisons for each fragment
  2. Then, averages results of comparisons
  3. e.g. for point P on the floor,
  1. compare its depth with all pixels
    in the red box, e.g. 3x3
  2. get the compared results, e.g.
    1, 0, 1,
    1, 0, 1,
    1, 1, 0,
  3. take avg. to get visibility, e.g. 0.667

pcf使用的过滤size越大,得到的阴影结果越模糊,范围也被扩大。

code:

vec2 poissonDisk[NUM_SAMPLES];

void poissonDiskSamples( const in vec2 randomSeed ) {
  float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES );
  float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );

  float angle = rand_2to1( randomSeed ) * PI2;
  float radius = INV_NUM_SAMPLES;
  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 += ANGLE_STEP;
  }
}

float PCF(sampler2D shadowMap, vec4 coords,float filterSize) {
  if(coords.x <-1.0 || coords.y>1.0 || coords.y<-1.0 || coords.y>1.0){
    //不在灯光相机视野中,默认完全照亮
    return 1.0;
  }
  float pixelSize = 1.0/2048.0;
  float count = 0.0;
  poissonDiskSamples(vec2(coords.x,coords.y));
  for(int i=0;i<NUM_SAMPLES;i++){
    vec2 uv = poissonDisk[i]*pixelSize*filterSize + coords.xy;
    if(uv.x <-1.0 || uv.y>1.0 || uv.y<-1.0 || uv.y>1.0){
      //不在灯光相机视野中,默认完全照亮
      return 1.0;
    }
    float dNearest = sampleDepth(shadowMap, uv) * Z_RANGE;
    float d = (coords.z+1.0)/2.0*Z_RANGE;//[-1,1]->[0,1.0]
    if(!(d > dNearest+Z_EPS)){//pcf 返回可见性指标
      count += 1.0;
    }
  }
  float res = count/float(NUM_SAMPLES);
  return res;
}

result:

PCF_8X8

PCF_64X64

PCSS

Percentage Closer Soft Shadows

通过观察可知阴影的软硬与阴影距离阴影投掷物的距离有关,距离越近,阴影越实(硬);距离越远,阴影越虚(软)。因此可以在PCF的基础上,动态调整filter size从而实现软阴影效果。

关键在于如何选择filter size,filter size和阴影距离阴影投掷物的距离(d_receiver-d_blocker)和遮挡物距离光源的距离(d_blocker)有关, 如下图所示:

其中W_penumbra就是面光源形成的软阴影范围,也就是pcf使用的filter size。

通过三角相似关系,可以得到下面的数量关系:

\[w_{Pemumbra} = (d_{Reciiver}- d_{Blocker}) * w_{Light}/d_{Blocker} \]

其中W_light为光源宽度,在代码中可以取常量;d_receiver是物体深度,可以在shader中uLightMVP*aPosition计算得到;

d_blocker是遮挡物深度可以从pass1中记录的最近物体深度读取,读取一定范围内的遮挡物的平均深度,这个范围需要取多大?

1.Can be set constant (e.g. 5x5), but can be better with heuristics

2.理论上的blocker search region:

依赖于光源面积大小和观察点距离光源的距离,即图中红虚线构成四棱锥范围。有一个问题是从面光源的不同点看场景会有不同的shadw map,如果只用一个shadw map进行采样,本身这里是精确分析,最后的结果又变成了近似。

最后得到PCSS的算法流程:

The complete algorithm of PCSS

  • Step 1: Blocker search
    (getting the average blocker depth in a certain region)
  • Step 2: Penumbra estimation
    (use the average blocker depth to determine filter size)
  • Step 3: Percentage Closer Filtering

code:

vec2 findBlocker( sampler2D shadowMap,  vec2 uv, float zReceiver ) {
  if(uv.x <-1.0 || uv.y>1.0 || uv.y<-1.0 || uv.y>1.0){
    //不在灯光相机视野中,默认没有遮挡物
    return vec2(0.0,0.0);
  }
  //uniformDiskSamples(uv);
  
  float pixelSize = 1.0/2048.0;
	float db = 0.0;
  float ds = 0.0;
  //启发式的得到搜索范围,单位:像素
  float searchSize = 0.0;
  searchSize = LIGHT_WIDTH*(zReceiver-NEAR_PLANE)/zReceiver;
  searchSize = searchSize/200.0/pixelSize;
  searchSize *= 1.5;
  //固定
  //searchSize = 64.0;// 64.0;

  float count = 0.0;
  poissonDiskSamples(vec2(uv.x*9999.0+8888.0,uv.y*7777.0+12345.0));
  for(int i = 0; i < NUM_SAMPLES; i++){
    vec2 uv2 = poissonDisk[i]*pixelSize*searchSize + uv;
    if(uv2.x <-1.0 || uv2.y>1.0 || uv2.y<-1.0 || uv2.y>1.0){
      //不在灯光相机视野中,默认没有遮挡物
      return vec2(0.0,0.0);
    }
    float d = sampleDepth(shadowMap, uv2);
    d = d*Z_RANGE;
    if(d < zReceiver-5.0 ){
      db += d;
      count += 1.0;
    }
  }
  db = db / count;
  ds = zReceiver - db;
  //db = db / float(NUM_SAMPLES);
  //ds = ds / float(NUM_SAMPLES);
  return vec2(db,ds);
}

float PCF(sampler2D shadowMap, vec4 coords,float filterSize) {
  if(coords.x <-1.0 || coords.y>1.0 || coords.y<-1.0 || coords.y>1.0){
    //不在灯光相机视野中,默认完全照亮
    return 1.0;
  }
  float pixelSize = 1.0/2048.0;
  float count = 0.0;
  poissonDiskSamples(vec2(coords.x,coords.y));
  for(int i=0;i<NUM_SAMPLES;i++){
    vec2 uv = poissonDisk[i]*pixelSize*filterSize + coords.xy;
    if(uv.x <-1.0 || uv.y>1.0 || uv.y<-1.0 || uv.y>1.0){
      //不在灯光相机视野中,默认完全照亮
      return 1.0;
    }
    float dNearest = sampleDepth(shadowMap, uv) * Z_RANGE;
    float d = (coords.z+1.0)/2.0*Z_RANGE;//[-1,1]->[0,1.0]
    if(!(d > dNearest+10.0)){//pcf 返回可见性指标
      count += 1.0;
    }
  }
  float res = count/float(NUM_SAMPLES);
  return res;
}

float PCSS(sampler2D shadowMap, vec4 coords){
  float d_r = (coords.z+1.0)/2.0*Z_RANGE;// d_receiver
  float pixelSize = 1.0/2048.0;
  float d_b = 0.0;//d_blocker
  float d_s = 0.0;//average d_reciver - d_blocker
  float filterSize = 16.0;
  // STEP 1: avgblocker depth
  vec2 vB = findBlocker(shadowMap,coords.xy,d_r);
  d_b = vB.x;
  d_s = vB.y;
  //return d_b/Z_RANGE;
  //return d_s/Z_RANGE;
  if(d_s<=EPS){
    //不受遮挡,可见性为1
    return 1.0;
  }
  // STEP 2: penumbra size
  //float wp = pow(d_s / d_b,2.0) * LIGHT_WIDTH; // d_s / d_b* LIGHT_WIDTH;//  
  float wp = d_s / d_b* LIGHT_WIDTH;
  //float wp = (dr/db-1.0) * L_WIDTH;
  float penumbraPixelSize = wp/200.0/pixelSize;//200是正交相机里设定的窗口范围
  //return penumbraPixelSize/100.0;
  // STEP 3: filtering
  //filterSize = penumbraPixelSize+8.0;
  filterSize = penumbraPixelSize;
  float res = PCF(shadowMap, coords, filterSize);
  return res;
}


result:

PCF 64X64

PCSS

posted @ 2024-01-17 21:37  bluebean  阅读(8)  评论(0编辑  收藏  举报