GAMES202实时渲染(1)-实时阴影

内容参考闫令琪课程《games202-高质量实时渲染及作业》、花桑博客

知识点

  • 阴影原理
  • PCF(percentage close filter)原理
  • PCSS(percentage close soft shadow)原理

实现效果

PCSS

多光源效果

原理

阴影的原理

物体之间有遮挡,在被遮挡物的表现上形成阴影

最基本的阴影基于shadow map实现,需要绘制两遍

  • 第一遍绘制

将camera放在光源处,绘制一遍,生成深度图

  • 第二遍绘制
    回到正常的camera位置绘制,每个点着色时,先从深度图里取值,判断当前点是否被遮挡,如果被遮挡则绘制成阴影

PCF

只用shadow map生成的阴影称之为"硬阴影",有两个问题

  1. 在边缘处存在锯齿
  2. 阴影边缘处生硬

左-硬阴影,右-软阴影

shadow map方式计算阴影时,与深度图的值比大小,非0即1。PCF在此基础上,每次会采样多个深度值,求平均值,形成渐变的soft shadow,采样点越多,阴影越软。代码里采样使用泊松采样,为圆形采样。

//phongFragment.glsl
float PCF(sampler2D shadowMap, vec4 coords, float biasC, float filterRadiusUV) {
  //uniformDiskSamples(coords.xy);
  poissonDiskSamples(coords.xy); //使用xy坐标作为随机种子生成
  float visibility = 0.0;
  for(int i = 0; i < NUM_SAMPLES; i++){
    vec2 offset = poissonDisk[i] * filterRadiusUV;
    float shadowDepth = useShadowMap(shadowMap, coords + vec4(offset, 0., 0.), biasC, filterRadiusUV);
    if(coords.z > shadowDepth + EPS){
      visibility++;
    }
  }
  return 1.0 - visibility / float(NUM_SAMPLES);
}

PCSS

仔细观察阴影,会发现物体越接近地面影子越清晰,离地面越高形成的影子越"软"

准确的说,假设光源有一定宽度,光源高度固定,遮挡物离光源越近,阴影越软,或者说半遮挡范围越大

  • W L i g h t W_{Light} WLight:光源的宽
  • d B l o c k e r d_{Blocker} dBlocker:遮挡物离光源的距离
  • d R e c e i v e r d_{Receiver} dReceiver:地面(阴影接受面)离光源的距离
  • W p e n u m b r a W_{penumbra} Wpenumbra:半影宽(软阴影的宽)

PCF对每个点采样多个深度求均值形成软阴影,PCSS在PCF上根据 W p e n u m b r a W_{penumbra} Wpenumbra值,动态计算采样半径,半径越大,阴影就越淡

实际在上面的公式中, d B l o c k e r d_{Blocker} dBlocker是未知的,必须先估算出来,看下图,连接着色点和光源边界,得到shadow map上的一个矩形,求矩形里像素的平均值(深度的平均)。

阴影自遮挡、悬浮问题

自遮挡是因为深度图的精度,第二遍绘制图像时,多个点对应深度图中的一个像素值,

自遮挡可以通过增加偏移解决,偏移的副作用就是悬浮,相当于把整个阴影的深度往后挪了一个偏移值

解决思路:

  • 前面剔除,要求物体是有厚度的,即前后面不同
  • 自适应偏移,根据视锥大小、camera与法线夹角动态计算偏移,夹角越大表示越倾斜,则偏移值越大

代码说明

shadow map

绘制两遍生成阴影。

注意:对比learnopengl教程中,opengl 3.0的API已经支持生成深度图,Webgl 2.0 通过在fragment shader中取z坐标为深度

//WebGLRenderer.js

// Shadow pass
if (this.lights[l].entity.hasShadowMap == true) {
    for (let i = 0; i < this.shadowMeshes.length; i++) {
        this.shadowMeshes[i].draw(this.camera);
    }
}

// Camera pass
for (let i = 0; i < this.meshes.length; i++) {
    this.gl.useProgram(this.meshes[i].shader.program.glShaderProgram);
    this.gl.uniform3fv(this.meshes[i].shader.program.uniforms.uLightPos, this.lights[l].entity.lightPos);
    this.meshes[i].draw(this.camera);
}

生成深度,用RGBA 4通道来存储深度值,提升精度。

EncodeFloatRGBA算法原理参考理解EncodeFloatRGBA与DecodeFloatRGBA

//shadowFragment.glsl

#ifdef GL_ES
precision mediump float;
#endif

uniform vec3 uLightPos;
uniform vec3 uCameraPos;

varying highp vec3 vNormal;
varying highp vec2 vTextureCoord;

vec4 pack (float depth) {
    // 使用rgba 4字节共32位来存储z值,1个字节精度为1/256
    const vec4 bitShift = vec4(1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0);
    const vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0);
    // gl_FragCoord:片元的坐标,fract():返回数值的小数部分
    vec4 rgbaDepth = fract(depth * bitShift); //计算每个点的z值
    rgbaDepth -= rgbaDepth.gbaa * bitMask; // Cut off the value which do not fit in 8 bits
    return rgbaDepth;
}

void main(){

  //gl_FragColor = vec4( 1.0, 0.0, 0.0, gl_FragCoord.z);
  gl_FragColor = pack(gl_FragCoord.z);
}

阴影visibility的计算

//phongFragment.glsl
void main(void) {
  //vPositionFromLight为光源空间下投影的裁剪坐标,除以w结果为NDC坐标
  vec3 shadowCoord = vPositionFromLight.xyz / vPositionFromLight.w;
  //把[-1,1]的NDC坐标转换为[0,1]的坐标
  shadowCoord.xyz = (shadowCoord.xyz + 1.0) / 2.0;

  float visibility;
  visibility = useShadowMap(uShadowMap, vec4(shadowCoord, 1.0));
  //visibility = PCF(uShadowMap, vec4(shadowCoord, 1.0));
  //visibility = PCSS(uShadowMap, vec4(shadowCoord, 1.0));

  vec3 phongColor = blinnPhong();

  gl_FragColor = vec4(phongColor * visibility, 1.0);
  //gl_FragColor = vec4(phongColor, 1.0);
}

pcss实现

//phongFragment.glsl

float PCSS(sampler2D shadowMap, vec4 coords, float biasC){
  float zReceiver = coords.z;

  // STEP 1: avgblocker depth 
  float avgBlockerDepth = findBlocker(shadowMap, coords.xy, zReceiver);

  if(avgBlockerDepth < -EPS)
    return 1.0;

  // STEP 2: penumbra size
  float penumbra = (zReceiver - avgBlockerDepth) * LIGHT_SIZE_UV / avgBlockerDepth;
  float filterRadiusUV = penumbra;

  // STEP 3: filtering
  return PCF(shadowMap, coords, biasC, filterRadiusUV);
}

猜你喜欢

转载自blog.csdn.net/daozi20/article/details/129414708
今日推荐