PCSS阴影实现边缘生硬失真问题排查求助
自研3D渲染器PCSS阴影边缘生硬失真问题排查
我正在自研3D渲染器,在阴影实现阶段遇到PCSS算法的问题:高大物体的阴影边缘出现生硬锐利的失真。怀疑错误出在平均遮挡物深度计算环节,尝试过调整搜索宽度(设为0.2时失真有轻微变化),还发现问题会随lightview矩阵调整而改变,但已确认该矩阵计算逻辑符合预期(仅朝向阴影投射物并考虑阴影接收物)。
问题截图
默认效果:
search width = 0.2时效果:
阴影计算Shader代码
#include<random.glsl> #define DEFAULT_BLOCKER_SEARCH_NUM_SAMPLES 256 #ifndef BLOCKER_SEARCH_NUM_SAMPLES #define BLOCKER_SEARCH_NUM_SAMPLES DEFAULT_BLOCKER_SEARCH_NUM_SAMPLES #endif #define DEFAULT_PCF_NUM_SAMPLES 256 #ifndef PCF_NUM_SAMPLES #define PCF_NUM_SAMPLES DEFAULT_PCF_NUM_SAMPLES #endif #define DEFAULT_LIGHT_SIZE 0.01 #ifndef LIGHT_SIZE #define LIGHT_SIZE DEFAULT_LIGHT_SIZE #endif #define POINTLIGHT_PCF_NUM_SAMPLES 128 #define DIRLIGHT_PCF_NUM_SAMPLES 256 float SampleTexture(sampler2D tex, vec2 coords, float compare) { return step(compare, texture(tex, coords.xy).r); } float SmoothPCF(sampler2D shadowMap, float gradientNoise, vec3 projCoords, float radius, int samples) { float shadow = 0.0; for (int i = 0; i < samples; ++i) { vec2 sampleUV = VogelDisk(i, samples, gradientNoise); sampleUV = projCoords.xy + sampleUV * radius; shadow += SampleTexture(shadowMap, sampleUV, projCoords.z); } shadow /= samples; return 1 - shadow; } float CalculateAvgBlockerDepth(sampler2D shadowMap, float gradientNoise, vec3 projCoords, float searchWidth, int samples){ float avgBlockerDepth = 0.0; int numBlockers = 0; for (int i = 0; i < samples; ++i) { vec2 sampleUV = VogelDisk(i, samples, gradientNoise); sampleUV = projCoords.xy + searchWidth * sampleUV; float sampleDepth = texture(shadowMap, sampleUV).r; if (sampleDepth < projCoords.z) { avgBlockerDepth += sampleDepth; numBlockers++; } } if (numBlockers == 0) return 0; return avgBlockerDepth /= numBlockers; } float CalculatePenumbra(float avgBlockerDepth, float searchWidth, float zDepth) { float penumbra = searchWidth * (zDepth - avgBlockerDepth) / avgBlockerDepth; return penumbra; } float PCSS(sampler2D shadowMap, vec3 projCoords) { float gradientNoise = InterleavedGradientNoise(projCoords.xy); float searchWidth = LIGHT_SIZE * clamp(projCoords.z - 0.1, 0.0, 1.0) / projCoords.z * 10.0; float avgBlockerDepth = CalculateAvgBlockerDepth(shadowMap, gradientNoise, projCoords, searchWidth, BLOCKER_SEARCH_NUM_SAMPLES); if (avgBlockerDepth == 0) return 0; float penumbra = CalculatePenumbra(avgBlockerDepth, LIGHT_SIZE, projCoords.z); return SmoothPCF(shadowMap, gradientNoise, projCoords, penumbra, PCF_NUM_SAMPLES); }
LightView矩阵计算代码
生成自定义相对光空间矩阵
glm::mat4 dir_light::generate_custom_relative_lightspace_matrix(const glm::vec3& cmin, const glm::vec3& cmax, const glm::vec3& rmin, const glm::vec3& rmax, const glm::mat4& model) { glm::vec3 center = (cmin + cmax) * 0.5f; float radius = glm::length(cmax - cmin) * 0.5f; glm::vec3 light_pos = center - direction * radius * 2.0f; glm::mat4 light_view = glm::lookAt(light_pos, center, glm::vec3(0, 1, 0)); auto [cmin_ls, cmax_ls] = utilities::calculate_min_max_light_space(cmin, cmax, light_view, model); auto [rmin_ls, rmax_ls] = utilities::calculate_min_max_light_space(rmin, rmax, light_view, model); float l = cmin_ls.x, r = cmax_ls.x; float b = cmin_ls.y, t = cmax_ls.y; float cn = -cmax_ls.z, cf = -cmin_ls.z; float rn = -rmax_ls.z, rf = -rmin_ls.z; float n = std::min(cn, rn), f = std::max(cf, rf); glm::mat4 light_proj = glm::ortho(l, r, b, t, n, f); return light_proj * light_view; }
计算光空间最小最大边界
std::tuple<glm::vec3, glm::vec3> utilities::calculate_min_max_light_space(glm::vec3 min, glm::vec3 max, const glm::mat4& light_view, const glm::mat4& model) { std::vector<glm::vec3> corners = { { min.x, min.y, min.z }, { min.x, min.y, max.z }, { min.x, max.y, min.z }, { min.x, max.y, max.z }, { max.x, min.y, min.z }, { max.x, min.y, max.z }, { max.x, max.y, min.z }, { max.x, max.y, max.z } }; glm::vec3 min_ls(FLT_MAX), max_ls(-FLT_MAX); for (const auto& c : corners) { glm::vec3 light_space_pos = glm::vec3(light_view * model * glm::vec4(c, 1.0f)); min_ls = glm::min(min_ls, light_space_pos); max_ls = glm::max(max_ls, light_space_pos); } return { min_ls, max_ls }; }
已确认矩阵计算逻辑符合预期:仅让光线朝向阴影投射物(cmin, cmax)并考虑阴影接收物(rmin, rmax)。请帮忙排查PCSS实现中的错误,解决阴影边缘失真问题。
内容的提问来源于stack exchange,提问作者FouRed
相关产品推荐
相关产品推荐

