作业1主要是对GAMES202-Lecture3中的知识进行应用,涉及到了ShadowMap生成、基本硬阴影绘制、PCF、PCSS等知识
ShadowMap生成
作业中对于ShadowMap的生成,仅要求书写模型空间到光源空间的MVP矩阵的书写
对于modelMatrix,我们能获得的参数有模型的Scale和Translate,没有旋转相关参数,我们按照模型变换顺序先进行Scale的矩阵变换然后进行TransLate的矩阵变换,得到modelMatrix。对于viewMatrix,我们能获得的参数有观察点和光源位置以及光源的Up向量,根据观察点和光源位置我们可以确定view的朝向,然后将光源的Up向量与朝向进行正交化并叉乘得到第三个基向量,将三个基向量正交化并对组成的矩阵取逆得到viewMatrix(由于z轴正方向和摄像机正反向是反的,viewMatrix需要对朝向向量取反)。对于projectionMatrix,我们采用正交变换,但这边没有参数,只能是根据给到的参数进行推测,根据正交变换实际是一次平移加上缩放获得变换矩阵。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| let lightMVP = mat4.create(); let modelMatrix = mat4.create(); let viewMatrix = mat4.create(); let projectionMatrix = mat4.create(); // Model transform let s = mat4.create(); let t = mat4.create(); s[0] = scale[0] s[5] = scale[1] s[10] = scale[2] mat4.multiply(modelMatrix, s, modelMatrix); t[12] = translate[0] t[13] = translate[1] t[14] = translate[2] mat4.multiply(modelMatrix, t, modelMatrix); // View transform let vt = mat4.create(); let vr = mat4.create(); vt[12] = -1 * this.lightPos[0] vt[13] = -1 * this.lightPos[1] vt[14] = -1 * this.lightPos[2] mat4.multiply(viewMatrix, vt,viewMatrix); let t_up = normalize(this.lightUp); let g = normalize(vecMinus(this.focalPoint,this.lightPos)); //对t_up进行正交化 t_up = normalize(vecMinus(t_up , vecChen(vecDot(g, t_up) , g))); let gxt = cross(g,t_up); vr[0] = gxt[0] vr[1] = t_up[0] vr[2] = -1.0 * g[0] vr[4] = gxt[1] vr[5] = t_up[1] vr[6] = -1.0 * g[1] vr[8] = gxt[2] vr[9] = t_up[2] vr[10] = -1.0 * g[2] mat4.multiply(viewMatrix, vr, viewMatrix); // Projection transform //realnear = - near ,realfar = -far let aspect = 1 let size = 100 let near = 1 let far = 500 projectionMatrix[0] = 1 / (aspect * size) projectionMatrix[5] = 1 / size projectionMatrix[10] = -2 / (far - near) projectionMatrix[14] = -1 * (far + near) / (far - near) mat4.multiply(lightMVP, projectionMatrix, viewMatrix); mat4.multiply(lightMVP, lightMVP, modelMatrix);
|
硬阴影绘制以及自遮挡问题的解决
将顶点坐标通过上述光源空间的MVP矩阵转换到光源空间坐标后进行归一化获得NDC坐标,再得到0-1的UV坐标,采样ShadowMap中数值并转换为深度,与当前深度对比获得visibility值并与shading结果相乘得到硬阴影效果
按照物体法线和光线的角度计算Bias后
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| float getBias() {
return 0.005 + 0.003* (1.0 - dot(normalize(vNormal) , normalize(uLightPos - vFragPos)));
}
float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){
vec4 depth = texture2D(shadowMap,shadowCoord.xy);
float real_depth = unpack(depth);
if(real_depth + getBias() < shadowCoord.z) return 0.0;
return 1.0;
}
|
PCF
PCF是在硬阴影的基础上进行ShadowMap的多次采样并取结果平均值作为visibility值达到一点的软化效果
笔者对正方形采样、以及闫大神提供的泊松圆盘随机采样和均匀圆盘采样都进行了测试
正方形
泊松圆盘
均匀圆盘
PCSS
在PCF的基础上,我们将FileSize即采样区域设置为动态的,依据Lecture3得到的采样区域由penumbra大小决定,其公式为penumbraSize = lightWidth * (ReceiveDepth - avgblockerDepth) / avgblockerDepth,我们设光源为的宽度为1,ReceiveDepth是已知的,需要求遮挡深度的平均值,这边的遮挡深度采样点由点与光源距离决定,距离越远,采样区域越大,将这三步合并我们得到以下过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| float PCSS(sampler2D shadowMap, vec4 coords){
float lightWidth = 1.0;
float mapSize = 2048.0;
float blockSize = 5.0;
// STEP 1: avgblocker depth
float sizeX = length(uLightPos - vFragPos) / 100.0;
float avgblockerDepth = GetAvgblockerDepth(shadowMap,coords,blockSize,coords.z,mapSize * sizeX);
if(avgblockerDepth == 1.0) {//非遮挡区域直接按照返回1.0
return 1.0;
}
// STEP 2: penumbra size
float penumbraSize = lightWidth * (coords.z - avgblockerDepth) / avgblockerDepth;
// STEP 3: filtering
// penumbraSize 越大采样区域越大,filtersize越大,即mapSize越小,阴影越软
mapSize = mapSize / (penumbraSize * 20.0);
return GetFileterRitaByUniform(shadowMap,coords,coords.z,mapSize);
//return GetFileterRitaByPoss(shadowMap,coords,coords.z,mapSize);
//return GetFileterRita(shadowMap,coords,3.0,coords.z,mapSize);
}
|
随机采样为均匀圆盘随机采样的效果图如下,可以看到随着距离增大,阴影有明显的变软效果
后续改进
泊松和均匀圆盘采样的效果一直很不好,后面小伙伴告知需要调大采样个数,测试后发现效果确实好了不少