首先是MVP矩阵,这里可以直接调用webgl的函数进行计算。
这里比较有趣的是正交投影区间的选取,要保证能够覆盖整个平面,后面可以通过修改函数来可视化看的见的范围。
选取的投影平面大小会影响阴影锯齿大小
CalcLightMVP(translate, scale) {
let lightMVP = mat4.create();
let modelMatrix = mat4.create();
let viewMatrix = mat4.create();
let projectionMatrix = mat4.create();
// Model transform
mat4.translate(modelMatrix,modelMatrix,translate);
mat4.scale(modelMatrix,modelMatrix,scale);
// View transform
mat4.lookAt(viewMatrix,this.lightPos,this.focalPoint,this.lightUp);
// Projection transform
let t=100;
let b=-100;
let l=-100;
let r=-l
mat4.ortho(projectionMatrix,l,r,b,t,1e-2,400);
mat4.multiply(lightMVP, projectionMatrix, viewMatrix);
mat4.multiply(lightMVP, lightMVP, modelMatrix);
return lightMVP;
}
接下来是useShadowMap函数
需要注意的是之前在shadowFragment.glsl中,float类型的深度值已经被打包成rgba值存在texture里了,这里要先在纹理中取出深度值,然后再用unpack转回float值。
+0.01是为了避免自遮挡
float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){
//get 2D position in texture,it is a rgba value
vec4 rgbaDepth = texture2D(shadowMap,shadowCoord.xy);
//use the function to transform rgba value to real shadow depth
float depth = unpack(rgbaDepth);
//shading point's z depth is just z
float shadingPointDepth = shadowCoord.z;
// 这里的深度是指光源到点的z轴长度,是正值。
float ret = shadingPointDepth < depth+0.01 ? 1.0:0.0;
return ret;
}
这里借鉴了另一篇博客的方法,所谓选取滤波范围其实只是把随机生成的采样点放大或者缩小,使其落在指定的范围内,采样数恒定不变,但采样范围发生变化。
float PCF(sampler2D shadowMap, vec4 coords) {
//use coords as seed to initalize
uniformDiskSamples(coords.xy);
float textureSize = 2048.0;
float filterStride = 5.0;
float filterRange = filterStride/textureSize;
int unBlockCount=0;
for(int i=0;i<NUM_SAMPLES;i++){
vec2 sampleCoord = poissonDisk[i] * filterRange +coords.xy;
vec4 rgbaDepth = texture2D(shadowMap,sampleCoord);
float depth = unpack(rgbaDepth);
float shadingPointDepth = coords.z;
if(shadingPointDepth < depth+0.01){
unBlockCount++;
}
}
return float(unBlockCount)/float(NUM_SAMPLES);
}
下面是两种随机采样方式的对比
泊松
uniform
这样乍得一看,两种随机采样方式得到的结果似乎差别并不明显,但其实不然。
泊松
uniform
可以看到,uniform的模式下,阴影的过渡部分有明显的层级,而泊松则平滑许多
还有个有意思的现象,阴影上有均匀分布的白条,从光源的方向观察可以看到他们在不同距离的平面上是可以对齐的,推测产生的原因应该是因为渲染的物体处于shadowmap像素之间的格子上,在选取临近像素时采样出现问题。
findblocker要注意的是在shadowmap深度值和zreciver比较的时候,要-0.01而不是+,因为这里我们要筛选出来比阴影物体更加远的片元,要反向防止自遮挡现象,可以手动试下把这里改成+会出现什么现象。
LIGHT_WIDTH可以自己设置值,一般来说越大阴影的模糊现象更明显,越小则接近点光源,不会出现太明显的软阴影。
float findBlocker( sampler2D shadowMap, vec2 uv, float zReceiver ) {
poissonDiskSamples(uv);
//uniformDiskSamples(uv);
float textureSize = 400.0;
float filterStride = 20.0;
float filterRange = 1.0/textureSize*filterStride;
int shadowCount=0;
float depthSum=0.0;
for(int i=0;i<NUM_SAMPLES;i++){
vec2 sampleCoord = poissonDisk[i] * filterRange + uv;
vec4 rgbaDepth = texture2D(shadowMap,sampleCoord);
float depth = unpack(rgbaDepth);
if(zReceiver > depth-0.01){
depthSum+=depth;
shadowCount+=1;
}
}
return depthSum/float(shadowCount);
}
float PCSS(sampler2D shadowMap, vec4 coords){
// STEP 1: avgblocker depth
float depthBlocker = findBlocker(shadowMap,coords.xy,coords.z);
float depthReciver = coords.z;
// STEP 2: penumbra size
uniformDiskSamples(coords.xy);
float textureSize = 400.0;
float filterStride = 5.0;
float wp = (depthReciver-depthBlocker)/depthBlocker*LIGHT_WIDTH;
float filterRange = filterStride/textureSize*wp;
// // STEP 3: filtering
int unBlockCount=0;
for(int i=0;i<NUM_SAMPLES;i++){
vec2 sampleCoord = poissonDisk[i] * filterRange +coords.xy;
vec4 rgbaDepth = texture2D(shadowMap,sampleCoord);
float depth = unpack(rgbaDepth);
if(depthReciver < depth+0.01){
unBlockCount++;
}
}
return float(unBlockCount)/float(NUM_SAMPLES);
//return wp;
}
效果
下面是一些过程中奇怪的现象