文件及函数参与分析
ShadowMap
该文件中的 CalcLightMVP() 函数计算得到了后面需要使用的 lightMVP。
在 PhongMaterial.js 中的 buildPhongMaterial() 函数中的 new PhongMaterial() 使用
调用 buildPhongMaterial() 时
return new PhongMaterial(color, specular, light, translate, scale, vertexShader, fragmentShader);
在 PhongMaterial 实例化时
let lightMVP = light.CalcLightMVP(translate, scale);
在 ShadowMaterial.js 中类似也是在bulid函数中和实例化时使用
之后再 loadOBJ.js 中结合定义的 transform 和 scale 赋值传入 material
let material, shadowMaterial;
let Translation = [transform.modelTransX, transform.modelTransY, transform.modelTransZ];
let Scale = [transform.modelScaleX, transform.modelScaleY, transform.modelScaleZ];
let light = renderer.lights[0].entity;
switch (objMaterial) {
case 'PhongMaterial':
material = buildPhongMaterial(colorMap, mat.specular.toArray(), light,Translation, Scale,
"./src/shaders/phongShader/phongVertex.glsl",
"./src/shaders/phongShader/phongFragment.glsl");
shadowMaterial = buildShadowMaterial(light, Translation, Scale,
"./src/shaders/shadowShader/shadowVertex.glsl",
"./src/shaders/shadowShader/shadowFragment.glsl");
break;
}
transform 和 scale 是在 engine.js 中定义好的
function setTransform(t_x, t_y, t_z, s_x, s_y, s_z) {
return {
modelTransX: t_x,
modelTransY: t_y,
modelTransZ: t_z,
modelScaleX: s_x,
modelScaleY: s_y,
modelScaleZ: s_z,
};
}
在 phongFragment.glsl 中的 main() 会使用在 DirectionalLight.js 中创建的 fbo 并将其解包成阴影贴图中的深度之后使用 useShadowMap() 计算 visibility
float useShadowMap(sampler2D shadowMap, vec4 shadowCoord)
{
float lightDepth = unpack(texture2D(shadowMap, shadowCoord.xy)); // 阴影贴图中的最小深度
float shadingDepth = shadowCoord.z; // 当前点的深度
float bias = getBias();
return lightDepth + EPS <= shadingDepth - bias ? 0.2 : 0.9; //此处的光照系数
}
传入的参数 shadowCoord 在 main() 中需要归一化
//shadowCoord 归一化 这东西的xy本质上是uv z是深度
vec3 shadowCoord = vPositionFromLight.xyz / vPositionFromLight.w;
getBias()
float getBias()
{
vec3 lightDir = normalize(uLightPos - vFragPos);
vec3 normal = normalize(vNormal);
float m = 300.0 / 2048.0 / 2.0; //正交矩阵宽高/shadowmap分辨率/2 DirectionalLight.js 中CalcLightMVP()计算的矩阵
float bias = max(m * (1.0 - dot(normal, lightDir)), m);
return bias;
}
PCF
相对于直接使用 shadowmap 添加(可变)偏差以减少自遮挡,如果之前shadowmap记录的深度小还不够,得明显的小才行,如果中间有东西就不算了。黄色的那一小段不算。越接近垂直打光偏差空间越小,反之越大,使用夹角判断。
同样的在phongFragment.glsl中的main()函数中使用 PCF() 计算visibility
visibility = PCF(uShadowMap, vec4(shadowCoord, 1.0));
/**
* @param {sampler2D} 提取来自方向光(DirectionalLight)中创建的FBO中的深度信息
* @param {vec4}
* @param {float} 取样范围
*/
float PCF(sampler2D shadowMap, vec4 coords)
{
// 给定步长和shadowMap分辨率,初始输出值、卷积核心当前的深度
float stride = 10.0;
float shadowMapSize = 2048.0; //在 engine.js 里可以看
float visibility = 0.0;
float shadingDepth = coords.z;
// 使用泊松分布获取采样点
poissonDiskSamples(coords.xy);
// 对所有点进行深度比较并进行累加
for(int i = 0; i < NUM_SAMPLES; i++)
{
vec4 shadowColor = texture2D(shadowMap, coords.xy + poissonDisk[i] * stride / shadowMapSize);
float lightDepth = unpack(shadowColor);
float res = lightDepth + EPS <= shadingDepth ? 0.0 : 1.0;
visibility += res;
}
return visibility / float(NUM_SAMPLES);
}
泊松分布这里我只用了圆盘采样 记得写到 PCF() 上面
//泊松圆盘采样 https://blog.youkuaiyun.com/qq_21476953/article/details/118440245
//https://blog.youkuaiyun.com/xiaoyaolangwj/article/details/119180217
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;
}
}
PCSS
PCSS同样在main()中计算 visibility 使用
float findBlocker( sampler2D shadowMap, vec2 uv, float zReceiver )
{
// zReceiver 就是传入的当前coords的深度值
int blockNumber = 0;
float blockDepth = 0.0;
float shadowMapSize = 2048.0;
float stride = 50.0;
// 泊松采样得到点
poissonDiskSamples(uv);
// 判断是否在阴影里 以决定是否累加
for(int i = 0; i < NUM_SAMPLES; i++)
{
vec4 shadowColor = texture2D(shadowMap, uv + poissonDisk[i] * stride / shadowMapSize);
float shadowDepth = unpack(shadowColor);
if(zReceiver > shadowDepth + 0.01)
{
blockNumber++;
blockDepth += shadowDepth;
}
}
// 被光照的地方要返回1不然场景是全黑的
if(blockNumber == 0)
{
return 1.0;
}
return float(blockDepth) / float(blockNumber);
}
float PCSS(sampler2D shadowMap, vec4 coords)
{
// STEP 1: avgblocker depth
float depthBlocker = findBlocker(shadowMap, coords.xy, coords.z);
float weighLight = 1.0; // 视作点光源
float depthReceiver = coords.z;
// STEP 2: penumbra size
float weighPenumbra = weighLight *(depthReceiver - depthBlocker) / depthBlocker;
// STEP 3: filtering
float stride = 20.0;
float shadowMapSize = 2048.0; //在 engine.js 里可以看
float visibility = 0.0;
float shadingDepth = coords.z;
for(int i = 0; i < NUM_SAMPLES; i++)
{
vec4 shadowColor = texture2D(shadowMap, coords.xy + poissonDisk[i] * stride / shadowMapSize * weighPenumbra);
float lightDepth = unpack(shadowColor);
float res = lightDepth + EPS <= shadingDepth ? 0.0 : 1.0;
visibility += res;
}
return visibility / float(NUM_SAMPLES);
}